WPF 动态生成DataGrid及动态绑定解决方案
一、场景
有过WPF项目经验的朋友可能都知道,如果一个DataGrid要绑定静态的数据是非常的简单的(所谓静态是指绑定的数据源的类型是静态的),如下图所示,想要显示产品数据,只需绑定到一个产品列表即可,这个大家都清楚,所以这个要讲的肯定不是这个。

但是现在有一个新的需求,根据所选择产品的不同,要动态生成第二个表格中的不同数据,以便进行编辑,如下图1、2所示,当选择的产品不同时,第二个表格显示的内容是完全不一样的。


这样就会产生一个问题,无法直接对第二个表格进行绑定,因为它的数据源类型都是不一样的,无法按照传统方法进行绑定。如何解决,先自己思考一下,也许会有更好的解决方案。
二、思路
1、定义Domain
既然无法知道要绑定的数据类型是什么,因为它是动态的,无法事先预知的,根据所选择的产品(因为产品数据都存储于DB中,所以将类型定义于Domain项目中)不同而变化的。那么可以在 Product 中定义 SKUFields 属性用于代表产品可显示的字段。产品定义如下所示。
/// <summary>
/// 产品
/// </summary>
[Table("Product")]
public class Product
{
public int ProductId { get; set; }
public string ProductDesc { get; set; }
public bool IsRFID { get; set; }
public bool IsTID { get; set; }
public string CustomerItemCode { get; set; }
public string ProductCode { get; set; }
public string SuggestedPrinter { get; set; } /// <summary>
/// 产品对应的文件名称
/// </summary>
public string ProfileName { get; set; } /// <summary>
/// 产品对应的SKU字段列表
/// </summary>
public ICollection<SKUField> SKUFields { get; set; }
}
/// <summary>
/// SKU 字段
/// </summary>
public class SKUField
{
/// <summary>
/// 产品ID
/// </summary>
public int ProductId { get; set; } /// <summary>
/// 字段ID
/// </summary>
public int SKUFieldId { get; set; } /// <summary>
/// 字段名称
/// </summary>
public string FieldName { get; set; } /// <summary>
/// 字段标题,用于显示
/// </summary>
public string FieldTitle { get; set; } /// <summary>
/// 字段是否可编辑
/// </summary>
public bool IsEditable { get; set; } /// <summary>
/// 字段的值
/// </summary>
public object DefaultValue { get; set; } /// <summary>
/// 字段值是否可选
/// </summary>
public bool IsValueSelectable { get; set; } /// <summary>
/// 字段的类型,如int、string或其他
/// </summary>
public Type FieldType { get; set; } /// <summary>
/// 如果IsValueSelectable = True,那么SKUFieldValues代表可选择的值列表
/// </summary>
public ICollection<SKUFieldValue> SKUFieldValues { get; set; } }
/// <summary>
/// 用于定义SKU字段的取值
/// </summary>
public class SKUFieldValue
{
public int SKUFieldId { get; set; } public int SKUFieldValueId { get; set; } public string FieldValue { get; set; }
}
然后在项目中定义 IProductRepository 接口,用于定义获取产品的方法。
public interface IProductRepository
{
/// <summary>
/// 获取默认的产品列表
/// </summary>
/// <returns></returns>
IEnumerable<Product> GetDefaultProducts(); /// <summary>
/// 获取特定客户账号的产品列表
/// </summary>
/// <param name="account">客户账号如YTST02DY、G99999CG</param>
/// <returns></returns>
IEnumerable<Product> GetProductsByAccount(string account);
}
Domain项目结构所图所示。

2、定义服务,用于获取所有产品信息
第二部建立 Application 项目,用于提供 WPFUI 项目所需要的服务,这里我不打算使用真实的数据源来提供数据,所以只是使用 Mock 来模拟服务。
/// <summary>
/// 产品接口,用于提供产品数据
/// </summary>
public interface IProductService
{
IEnumerable<Product> GetAllProducts();
}
/// <summary>
/// 真实的服务,使用EF框架来获取数据,但是这里不合适这个服务,为了方便演示
/// </summary>
public class ProductService : IProductService
{
public IEnumerable<Product> GetAllProducts()
{
using (var context = new SQLiteDataContext())
{
return context.Products.ToList();
}
}
}
/// <summary>
/// 模拟的服务,为了方便演示
/// </summary>
public class MockProductService : IProductService
{
public IEnumerable<Product> GetAllProducts()
{
var product2 = GetProduct2();
var product3 = GetProduct3();
var product4 = GetProduct4(); return new List<Product>()
{
product2,
product3,
product4
};
} public Profile GetProfile(Product product)
{
string filePath = Path.Combine(string.Format(@"Resources\Products\{0}", product.ProductCode), product.ProfileName); Profile profile = new Profile(filePath, TempFolder.CreateTempFolder()); return profile;
} private Product GetProduct2()
{
var product = new Product()
{
ProductId = ,
IsRFID = true,
IsTID = true,
CustomerItemCode = "FP-PT#003",
ProductDesc = "Coated Stock",
ProductCode = "88CEMPH006",
ProfileName = "88CEMPH006.spkg",
SuggestedPrinter = "SMLPrinter"
}; product.SKUFields = new List<SKUField>(); SKUField skuField1 = new SKUField();
skuField1.FieldName = "Qty";
skuField1.FieldTitle = "Order Qty";
skuField1.FieldType = typeof(System.Int32);
skuField1.IsEditable = true;
product.SKUFields.Add(skuField1); SKUField skuField2 = new SKUField();
skuField2.FieldName = "Size";
skuField2.FieldTitle = "Size";
skuField2.FieldType = typeof(System.String);
skuField2.IsEditable = true;
skuField2.IsValueSelectable = true;
skuField2.SKUFieldValues = new List<SKUFieldValue>()
{
new SKUFieldValue() { FieldValue = "Large" },
new SKUFieldValue() { FieldValue = "Middle" },
new SKUFieldValue() { FieldValue = "Small" },
};
product.SKUFields.Add(skuField2); SKUField skuField3 = new SKUField();
skuField3.FieldName = "Retail";
skuField3.FieldTitle = "Retail";
skuField3.FieldType = typeof(System.String);
skuField3.IsEditable = true;
product.SKUFields.Add(skuField3); return product;
} private Product GetProduct3()
{
var product = new Product()
{
ProductId = ,
IsRFID = false,
IsTID = false,
CustomerItemCode = "FP-PT#004",
ProductDesc = "Coated Stock",
ProductCode = "88CEMNH006",
ProfileName = "88CEMNH006.spkg",
SuggestedPrinter = "SML FP300R (Copy 1)",
}; product.SKUFields = new List<SKUField>(); SKUField skuField1 = new SKUField();
skuField1.FieldName = "Qty";
skuField1.FieldTitle = "Order Qty";
skuField1.FieldType = typeof(System.Int32);
skuField1.IsEditable = true;
product.SKUFields.Add(skuField1); SKUField skuField2 = new SKUField();
skuField2.FieldName = "Size";
skuField2.FieldTitle = "Size";
skuField2.FieldType = typeof(System.String);
skuField2.IsEditable = true;
skuField2.IsValueSelectable = true;
skuField2.SKUFieldValues = new List<SKUFieldValue>()
{
new SKUFieldValue() { FieldValue = "Large" },
new SKUFieldValue() { FieldValue = "Middle" },
new SKUFieldValue() { FieldValue = "Small" },
};
product.SKUFields.Add(skuField2); SKUField skuField3 = new SKUField();
skuField3.FieldName = "Style";
skuField3.FieldTitle = "Style";
skuField3.FieldType = typeof(System.String);
skuField3.IsEditable = true;
skuField3.IsValueSelectable = true;
skuField3.SKUFieldValues = new List<SKUFieldValue>()
{
new SKUFieldValue() { FieldValue = "" },
new SKUFieldValue() { FieldValue = "" },
new SKUFieldValue() { FieldValue = "" },
};
product.SKUFields.Add(skuField3); SKUField skuField4 = new SKUField();
skuField4.FieldName = "CollectionName";
skuField4.FieldTitle = "Collection Name";
skuField4.FieldType = typeof(System.String);
skuField4.IsEditable = false;
skuField4.DefaultValue = "100% COTTON";
product.SKUFields.Add(skuField4); return product;
} private Product GetProduct4()
{
var product = new Product()
{
ProductId = ,
IsRFID = false,
IsTID = false,
CustomerItemCode = "FP-PT#004",
ProductDesc = "Coated Stock",
ProductCode = "88CEMNH004",
ProfileName = "88CEMNH004.spkg",
SuggestedPrinter="Fax",
}; product.SKUFields = new List<SKUField>(); SKUField skuField1 = new SKUField();
skuField1.FieldName = "Qty";
skuField1.FieldTitle = "Order Qty";
skuField1.FieldType = typeof(System.Int32);
skuField1.IsEditable = true;
product.SKUFields.Add(skuField1); SKUField skuField2 = new SKUField();
skuField2.FieldName = "Size";
skuField2.FieldTitle = "Size";
skuField2.FieldType = typeof(System.String);
skuField2.IsEditable = true;
skuField2.IsValueSelectable = true;
skuField2.SKUFieldValues = new List<SKUFieldValue>()
{
new SKUFieldValue() { FieldValue = "Large" },
new SKUFieldValue() { FieldValue = "Middle" },
new SKUFieldValue() { FieldValue = "Small" },
};
product.SKUFields.Add(skuField2); SKUField skuField3 = new SKUField();
skuField3.FieldName = "Style";
skuField3.FieldTitle = "Style";
skuField3.FieldType = typeof(System.String);
skuField3.IsEditable = true;
skuField3.IsValueSelectable = true;
skuField3.SKUFieldValues = new List<SKUFieldValue>()
{
new SKUFieldValue() { FieldValue = "" },
new SKUFieldValue() { FieldValue = "" },
new SKUFieldValue() { FieldValue = "" },
};
product.SKUFields.Add(skuField3); SKUField skuField4 = new SKUField();
skuField4.FieldName = "CollectionName";
skuField4.FieldTitle = "Collection Name";
skuField4.FieldType = typeof(System.String);
skuField4.IsEditable = false;
skuField4.DefaultValue = "100% COTTON";
product.SKUFields.Add(skuField4); return product;
}
}
项目结构如图所示

3、定义WPF项目,用于显示UI
Note: 项目有使用CM、Ninject框架
项目结构如下

(1) 定义Model类
UISKURecord 用于定义第二个表格的数据源中的一行数据,包含 UISKUField 列表,用于代表不确定的列。
public class UISKURecord
{
private readonly ObservableCollection<UISKUField> _uiSKUFields =
new ObservableCollection<UISKUField>(); public ObservableCollection<UISKUField> UISKUFields
{
get
{
return _uiSKUFields;
}
} public void AddSKUField(UISKUField uiSKUField)
{
_uiSKUFields.Add(uiSKUField);
}
}
UISKUField 用于定义要显示的属性,继承自SKUField中,添加了两个用于绑定UI的属性。
public class UISKUField : SKUField, INotifyPropertyChanged
{
/// <summary>
/// 两种情况下UI文本框绑定此属性
/// 1. IsEditable = False
/// 2. IsEditable = True But IsValueSelectable = False
/// </summary>
public object Value { get; set; } /// <summary>
/// 当IsValueSelectable = True时,UI显示下拉列表供用户选择值
/// 此时下拉列表SelectedItem绑定此属性
/// </summary>
private SKUFieldValue _selectedUISKUFieldValue; public SKUFieldValue SelectedUISKUFieldValue
{
get { return _selectedUISKUFieldValue; }
set
{
_selectedUISKUFieldValue = value;
OnPropertyChanged();
}
} #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
} #endregion
}
SKUFieldExtensionMethods用于定义将服务提供的数据类型转化成UI绑定所使用的数据类型。
public static class SKUFieldExtensionMethods
{
public static UISKUField ToUISKUField(this SKUField skuField)
{
UISKUField uiSKUField = new UISKUField(); uiSKUField.Value = skuField.DefaultValue;
uiSKUField.FieldName = skuField.FieldName;
uiSKUField.FieldTitle = skuField.FieldTitle;
uiSKUField.IsEditable = skuField.IsEditable;
uiSKUField.IsValueSelectable = skuField.IsValueSelectable; if (uiSKUField.IsValueSelectable)
{
uiSKUField.SKUFieldValues = skuField.SKUFieldValues;
uiSKUField.SelectedUISKUFieldValue = uiSKUField.SKUFieldValues.FirstOrDefault();
} return uiSKUField;
}
}
(2) 定义 ViewModel 类
public class MainViewModel : PropertyChangedBase
{ #region Field
private IProductService _productService = null;
#endregion #region Ctor public MainViewModel(IProductService productService)
{
_productService = productService; SKURecords = new ObservableCollection<UISKURecord>(); Products = new ObservableCollection<Product>(_productService.GetAllProducts());
} #endregion #region Prop
private ObservableCollection<Product> _products; /// <summary>
/// 所有产品
/// </summary>
public ObservableCollection<Product> Products
{
get { return _products; }
set
{
_products = value;
SelectedProduct = value.FirstOrDefault();
NotifyOfPropertyChange(() => Products);
}
} private Product _selectedProduct; /// <summary>
/// 所选产品
/// </summary>
public Product SelectedProduct
{
get { return _selectedProduct; }
set
{
_selectedProduct = value; NotifyOfPropertyChange(() => SelectedProduct);
//切换产品时,先清空SKU表格中的数据,再添加一行
SKURecords.Clear(); AddSKU();
}
} private ObservableCollection<UISKURecord> _skuRecords; public ObservableCollection<UISKURecord> SKURecords
{
get { return _skuRecords; }
set
{
_skuRecords = value;
NotifyOfPropertyChange(() => SKURecords);
}
} private UISKURecord _selectedSKURecord; public UISKURecord SelectedSKURecord
{
get { return _selectedSKURecord; }
set
{
_selectedSKURecord = value;
NotifyOfPropertyChange(() => SelectedSKURecord);
}
} #endregion #region ICommand Method public void AddSKU()
{
UISKURecord skuRecord = new UISKURecord(); foreach (var skuField in _selectedProduct.SKUFields)
{
skuRecord.AddSKUField(skuField.ToUISKUField());
} SKURecords.Add(skuRecord);
}
#endregion }
(3) 定义View
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
Title="MainWindow" Height="" Width="">
<Window.Resources>
<DataTemplate x:Key="customerDataGridTextBlockColumnDataTemplate">
<TextBlock Text="{Binding Path = Value}"></TextBlock>
</DataTemplate> <DataTemplate x:Key="customerDataGridTextColumnDataTemplate">
<TextBox Text="{Binding Path = Value}"></TextBox>
</DataTemplate> <DataTemplate x:Key="customerDataGridComboboxColumnDataTemplate">
<ComboBox ItemsSource="{Binding Path = SKUFieldValues}"
DisplayMemberPath="FieldValue"
SelectedItem="{Binding SelectedUISKUFieldValue}"> </ComboBox>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height=""/>
</Grid.RowDefinitions> <DataGrid x:Name="ProductsDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding Products}"
SelectionChanged="ProductsDataGrid_SelectionChanged"
SelectedItem="{Binding SelectedProduct}" Margin="">
<DataGrid.Columns>
<DataGridTextColumn Header="Product Code" Width="" Binding="{Binding Path=ProductCode}"/>
<DataGridTextColumn Header="Customer Item Code" Width="" Binding="{Binding Path=CustomerItemCode}"/>
<DataGridTextColumn Header="Product Desc" Width="" Binding="{Binding Path=ProductDesc}"/>
</DataGrid.Columns>
</DataGrid> <DataGrid x:Name="SKUsDataGrid"
Grid.Row=""
AutoGenerateColumns="False"
ItemsSource="{Binding SKURecords}"
SelectedItem="{Binding SelectedSKURecord}"
Margin=""
CanUserAddRows="False"
CanUserDeleteRows="False">
</DataGrid> <StackPanel Grid.Row="" Orientation="Horizontal" HorizontalAlignment="Left" Margin="10 0 0 0">
<Button x:Name="AddSKU" Content="Add" Width="" Height="" Margin="0,0,0,2" VerticalAlignment="Bottom"/>
<Button x:Name="RemoveSKU" Content="Remove" Width="" Height="" Margin="5 0 0 0"/>
</StackPanel>
</Grid>
</Window>
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
MainViewModel _viewModel; public MainWindow()
{
InitializeComponent(); this.DataContext = _viewModel = new MainViewModel();
} private void ProductsDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SKUsDataGrid.Columns.Clear(); var viewModel = (MainViewModel)DataContext; var fields = _viewModel.SKURecords.First().SKUProperties.ToList(); for (int i = ; i < fields.Count; i++)
{
var field = fields[i]; //列不可编辑
if (!field.IsEditable)
{
var column = new CustomBoundColumn();
column.IsReadOnly = true;
column.Header = field.FieldTitle;
column.Binding = new Binding(string.Format("SKUProperties[{0}]", i));
column.TemplateName = "customerDataGridTextBlockColumnDataTemplate";
column.Width = ; SKUsDataGrid.Columns.Add(column);
}
else
{
if (!field.IsValueSelectable)
{
var column = new CustomBoundColumn();
column.IsReadOnly = false;
column.Header = field.FieldTitle;
column.Binding = new Binding(string.Format("SKUProperties[{0}]", i));
column.TemplateName = "customerDataGridTextColumnDataTemplate";
column.Width = ; SKUsDataGrid.Columns.Add(column);
}
else
{
var column = new CustomBoundColumn();
column.IsReadOnly = false;
column.Header = field.FieldTitle;
column.Binding = new Binding(string.Format("SKUProperties[{0}]", i));
column.TemplateName = "customerDataGridComboboxColumnDataTemplate";
column.Width = ; SKUsDataGrid.Columns.Add(column);
}
}
}
} }
public class CustomBoundColumn : DataGridBoundColumn
{
public string TemplateName { get; set; } protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var binding = new Binding(((Binding)Binding).Path.Path);
binding.Source = dataItem; var content = new ContentControl(); content.ContentTemplate = (DataTemplate)cell.FindResource(TemplateName); content.SetBinding(ContentControl.ContentProperty, binding); return content;
} protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
return GenerateElement(cell, dataItem);
}
}
WPF 动态生成DataGrid及动态绑定解决方案的更多相关文章
- WPF 动态生成对象属性 (dynamic)
原文:WPF 动态生成对象属性 (dynamic) 项目中列行的数据 都需要动态生成 所以考虑到对象绑定 可需要一个动态生成属性的意思 缺点 加载速度会慢 很明显的慢 解决办法 尽量减轻动态属性的量 ...
- PHP+Mysql+easyui点击左侧tree菜单对应表名右侧动态生成datagrid加载表单数据(二)
关于tree菜单生成,参考我的另一篇博文地址tree 菜单 实现功能:点击左侧tree菜单中的table,右侧通过datagrid加载出该表对用的所有数据 难点:获取该表的所有列名,动态生成datag ...
- Silverlight日记:动态生成DataGrid、行列装换、动态加载控件
本文主要针对使用DataGrid动态绑定数据对象,并实现行列转换效果. 一,前台绑定 <sdk:DataGrid x:Name="dataGrid2" Style=" ...
- 使用WPF动态生成Code 39条形码
最近在看些条形码方面相关的资料,而如果只是看的话,效果似乎并不怎么好,所以决定动手做点Demo,以增强对相关知识的记忆. 这里是一个我编写的使用WPF生成Code 39的例子,Code 39的编码很简 ...
- WPF Datagrid 动态生成列 并绑定数据
原文:WPF Datagrid 动态生成列 并绑定数据 说的是这里 因为列头是动态加载的 (后台for循环 一会能看到代码) 数据来源于左侧列 左侧列数据源 当然num1 属于临时的dome使用 可 ...
- 基于webpack的前端工程化开发解决方案探索(一):动态生成HTML(转)
1.什么是工程化开发 软件工程的工程化开发概念由来已久,但对于前端开发来说,我们没有像VS或者eclipse这样量身打造的IDE,因为在大多数人眼中,前端代码无需编译,因此只要一个浏览器来运行调试就行 ...
- 使用C#动态生成Word文档/Excel文档的程序测试通过后,部署到IIS服务器上,不能正常使用的问题解决方案
使用C#动态生成Word文档/Excel文档的程序功能调试.测试通过后,部署到服务器上,不能正常使用的问题解决方案: 原因: 可能asp.net程序或iis访问excel组件时权限不够(Ps:Syst ...
- 根据数据库记录动态生成C#类及其公共属性并动态执行的解决方案
原文:根据数据库记录动态生成C#类及其公共属性并动态执行的解决方案 问题: C#中,想动态产生这么一个类: public class StatisticsData { public ...
- append动态生成的元素,无法触发事件的原因及解决方案
今天笔者在实现一个简单的动态生成元素功能的时候,发现了一个问题: 使用append动态生成的元素事件绑定失效了. 查阅资料后发现: click(fn)当选中的选择器被点击时触发回调函数fn.只针对与页 ...
随机推荐
- 为 Jenkins 配置 .Net 持续集成环境
去年年底,得益于公司引入 Jenkins,让我们在持续集成方面迈出了第一步,本文不赘述如何安装 Jenkins,主要关注点在于配置 .Net 环境.另外本文是在 Windows 环境下安装的 Jenk ...
- OGG学习笔记04-OGG复制部署快速参考
OGG学习笔记04-OGG复制部署快速参考 源端:Oracle 10.2.0.5 RAC + ASM 节点1 Public IP地址:192.168.1.27 目标端:Oracle 10.2.0.5 ...
- 法国总统放大招,用“分身术”竞选总统 全息3d 网
编辑:大熊 [摘要]法国总统采用全息技术实现"分身"演讲,可谓是一次演讲,全面覆盖! 全息3d网讯:众所周知,欧美国家的总统是通过公开竞选得到的,所以能更直接.更广泛的近距离接触民 ...
- 【前端】:HTML
前言: 最近开始学前端了,这篇博客主要介绍html的一些主要标签,写完这篇博客,我会用刚学的html做一个简单的登陆界面~~ 一.HTML介绍 HTML(Hyper Text Mark-up Lang ...
- 《解决在Word中为汉子插入拼音及音标的问题》
说明:本人使用的是Word2007版本.以下示例都是基于本人电脑操作.如有疑问,欢迎留言交流. [1]为word中的一些文字添加拼音及音标. [2]开始为文字添加拼音及音标. 选中要添加拼音及音标的文 ...
- WKWebView的使用与JS交互详细解读
前言: WKWebView 这是在iOS8.0之后增加的一个比UIWebView更加完善和强大的控件!看网上关于它的博客也是有许多的了,从各个方面总结一下这个WKWebView看网上说它主要是为了和J ...
- react-router3.x hashHistory render两次的bug,及解决方案
先写一个简单App页面,其实就是简单修改了react-router的官方例子中的animations例子,修改了两个地方: 1.路由方式由browserHistory修改为hashHistory 2. ...
- 文件读写监控(inotify, systemtap)
一.inotify inotify是内核的一个特性,可以用来监控目录.文件的读写等事件,当监控目标是目录时,inotify除了会监控目录本身,还会监控目录中的文件.inotify的监控功能由 ...
- PB程序“无法启动此程序,因为计算机中丢失PBvm90.dll。尝试重新安装该程序以解决此问题”的解决方法
因为有计算机自考科目,要求使用PB程序做一个管理系统.昨天刚安装好了PB程序,今天使用的时候,当我打开一个PB程序时,出现了"无法启动此程序,因为计算机中丢失PBvm90.dll.尝试重新安 ...
- OpenGl编程指南第7版(红宝书)环境配制
环境 OS:win7 旗舰版SP1 64位 编译器: VS 2013 express 的cl 软件 glut. 在这个页面https://www.opengl.org/resources/librar ...