wpf 模板选择器DataTemplateSelector及动态绑定使用教程

其实也说不上算是教程了,只是把自己学习的代码拿出来分享一下,同时方便以后遇到类似问题的时候翻一下。
MSDN里如是说:
通常,如果有多个 DataTemplate 可用于同一类型的对象,并且您希望根据每个数据对象的属性提供自己的逻辑来选择要应用的 DataTemplate,则应创建 DataTemplateSelector。请注意,如果具有不同类型的对象,则可以对 DataTemplate 设置 DataType 属性。如果您执行了此操作,则无需创建 DataTemplateSelector。此外,如果对象类型相同但属性不同,也可以考虑使用 DataTrigger 或数据转换器。
通俗讲,就是根据不同的数据选择不同的模板。接下来,我用一个例子来讲述DataTemplateSelector和动态绑定的使用方法。
先看例子的UI:

图一

图二
一个Listbox三个button,listbox显示一个集合的数据,第一个button “change”点击后可以切换与listbox绑定的集合,第二个button给PersonCollection添加person实例,第三个button给AnimalCollection添加animal实例。
默认情况下listbox与PersonCollection绑定,程序启动后,点击Add person按钮会向PersonCollection添加一个person实例,listbox会显示该集合下数据,如果person类的gender属性为Female,则只显示name属性,并把边框改成蓝色。点击change后,listbox与AnimalCollection绑定,显示AnimalCollection数据,同样,Add animal添加animal实例,如果第二个属性不是4,则只显示animal的type属性,并修改边框为蓝色。
下面讲实现
第一步:建两个类,一个person,一个animal,这就是每个listbox item将来要显示的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//person类
     public  class person  : INotifyPropertyChanged
     {
         public  event PropertyChangedEventHandler PropertyChanged ;
         private  string _name ;
         public  string name
         {
            get
             {
                 return _name ;
             }
            set
             {
                 if  (value  != _name )
                 {
                    _name  = value ;
                    prochanged ( "name" ) ;
                 }
             }
         }
         private  string _gender ;
         public  string gender
         {
            get
             {
                 return _gender ;
             }
            set
             {
                 if  (value  != _gender )
                 {
                    _gender  = value ;
                    prochanged ( "gender" ) ;
                 }
             }
         }
         private  void prochanged ( string info )
         {
             if  (PropertyChanged  !=  null )
             {
                PropertyChanged ( thisnew PropertyChangedEventArgs (info ) ) ;
             }
         }
     }

注意到这里,实现了INotifyPropertyChanged接口,需要引用命名空间System.ComponentModel.因为我希望这些属性改变的时候,listboxitem能自动更新,关于INotifyPropertyChanged接口,请看我另一篇日志wpf数据绑定binding与INotifyPropertyChanged
另一个类,animal与person基本类似,只是把person类里的属性:name和gender改成了type和legs。这里我就不贴代码了。
第二步:创建类DataContainer,包含两个集合,存储animal和person实例。代码:

1
2
3
4
5
6
7
8
9
10
     public  class DataContainer
     {
         public  ObservableCollection <person > PersonCollection  { get ;set ;  }
         public  ObservableCollection <animal > AnimalCollection  { get ; set ;  }
         public DataContainer ( )
         {
            PersonCollection  =  new ObservableCollection <person > ( ) ;
            AnimalCollection  =  new ObservableCollection <animal > ( ) ;
         }
     }

我用了ObservableCollection,因为ObservableCollection已经实现了INotifyPropertyChanged接口,所以如果集合的项增删的时候UI会自动更新。(using System.Collections.ObjectModel;//ObservableCollection)
注意:假设最上面第一段代码person类里面,没有实现INotifyPropertyChanged接口,而这里仍然使用ObservableCollection来存储person列表的话,PersonCollection 里的项增加或删除时,UI的listbox也会相应的增删,但是当你想修改PersonCollection里某一个person实例的属性,比如gender由male变成female时,UI并不会更新,因为PersonCollection 并没变。所以,当你希望改变UI listbox item里某个person的属性时,person类就需要实现INotifyPropertyChanged接口。
第三步:定义数据模板。
当然首先在窗口里把需要的listbox和button放好。我的XAML代码如下:

1
2
3
4
5
6
7
     <Grid>
         <ListBox Margin="12,12,12,60" Name="listBox1"   HorizontalContentAlignment ="Stretch" ItemTemplateSelector="{StaticResource mytemplate_selector}">
         </ListBox>
         <Button Height="23" HorizontalAlignment="Left" Margin="12,0,0,12" Name="Change" VerticalAlignment="Bottom" Width="75" Click="Change_Click">Change </Button>
         <Button Height="23" Margin="120,0,109,12" Name="addperson" VerticalAlignment="Bottom" Click="addperson_Click">Add person </Button>
         <Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" Name="addanimal" VerticalAlignment="Bottom" Width="75" Click="addanimal_Click">Add animal </Button>
     </Grid>

吃饭去了,回来再写……
继续,上面的XAML里的3个button没什么好说的,listbox里,最重要的一句是:ItemTemplateSelector=”{StaticResource mytemplate_selector}”,从字面意思理解,就是把一个叫做mytemplate_selector的静态资源,做为listbox的模板选择器。所以,接下来,定义我们需要资源,包括一个模板选择器和四个数据模板。模板选择器就是这里的mytemplate_selector,四个模板,person和animal各两个,通过模板选择器选择使用哪一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
     <Window.Resources>
         <local:MyDataTemplateSelector x:Key="mytemplate_selector" />
         <DataTemplate x:Key ="person_template">
             <Border  Name="myborder" BorderBrush="Blue" Padding="5" Margin="2" BorderThickness="1">
             <StackPanel>
                 <TextBlock  Text="{Binding Path=name}"></TextBlock >
                 <TextBlock  Text="{Binding Path=gender}"></TextBlock >
             </StackPanel>
              </Border>
             <DataTemplate.Triggers>
                 <DataTrigger  Value="Male" Binding="{Binding Path=gender}">           
                     <Setter TargetName="myborder" Property="BorderBrush" Value="Red" />
                 </DataTrigger>
             </DataTemplate.Triggers>
         </DataTemplate>
         <DataTemplate x:Key="male_template">
             <DataTemplate.Resources>
                 <Style TargetType="TextBlock">
                     <Setter Property="FontSize" Value="20" />
                 </Style>
             </DataTemplate.Resources>
             <Border  Name="myborder_male" BorderBrush="Blue" Padding="5" Margin="2" BorderThickness="1">
                 <DockPanel>
                     <TextBlock Text="{Binding Path=name}"/>
                 </DockPanel>
             </Border>
         </DataTemplate>
         <DataTemplate x:Key ="animal_template">
             <Border  Name="myborder" BorderBrush="Blue" Padding="5" Margin="2" BorderThickness="1">
                 <StackPanel>
                     <TextBlock  Text="{Binding Path=type}"></TextBlock >
                     <TextBlock  Text="{Binding Path=legs}"></TextBlock >
                 </StackPanel>
             </Border>
             <DataTemplate.Triggers>
                 <DataTrigger  Value="4" Binding="{Binding Path=legs}">
                     <Setter TargetName="myborder" Property="BorderBrush" Value="Red" />
                 </DataTrigger>
             </DataTemplate.Triggers>
         </DataTemplate>
         <DataTemplate x:Key="fourlegs_template">
             <DataTemplate.Resources>
                 <Style TargetType="TextBlock">
                     <Setter Property="FontSize" Value="20" />
                 </Style>
             </DataTemplate.Resources>
             <Border  Name="myborder_male" BorderBrush="Blue" Padding="5" Margin="2" BorderThickness="1">
                 <DockPanel>
                     <TextBlock Text="{Binding Path=type}"/>
                 </DockPanel>
             </Border>
         </DataTemplate>
     </Window.Resources>

分析前面的XAML,在里面,我们共定义了五个资源,我简单分析一下前三个,后面两个跟第二个、第三个差不多,就不说多了。
第一个资源,定义了一个模板选择器,使用CS文件里的MyDataTemplateSelector类来选择模板。第二个资源,person_template, StackPanel里定义了普通的模板,显示person的name和gender,DataTrigger里定义了一个触发器,当gender为Male的时候,将item的border变成红色。这就是前面UI里不同的边框色的实现关键点。第三个资源male_template,定义了一个Male专用的模板,字体20号,只显示name。person_template模板里,边框颜色的变换通过DataTrigger就能实现,因为要修改的属性是在模板里存在的。但是当你想修改一些模板里不存的属性或者对于gender = male时使用另外完全不同的模板,那就要用到模板选择器了。下面分析前面提到的模板选择器,看着比较多,其实很简单,忍一忍认真点儿看完,你就能有更多收获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//必须继承自DataTemplateSelector
  public  class MyDataTemplateSelector :DataTemplateSelector
     {
         //覆盖SelectTemplate函数
         public  override DataTemplate SelectTemplate ( object item, DependencyObject container )
         {
            Window win  = Application . Current . MainWindow ;
             //当前正在显示的是person类数据时:
             if  (item  !=  null  && item  is person )
             {               
                person p  = item  as person ;
                 //如果gender是male,将person_template做为现在的模板
                 if  (p . gender == "Male" )
                 {
                     return win . FindResource ( "person_template" )  as DataTemplate ;
                 }
                 //否则就使用male_template
                 else
                 {
                     return win . FindResource ( "male_template" )  as DataTemplate ;
                 }
             }
             else  if (item  !=  null  && item  is animal )
             {
                animal a  = item  as animal ;
                 if  (a . legs == 4 )
                 {
                     return win . FindResource ( "animal_template" )  as DataTemplate ;
                 }
                 else
                 {
                     return win . FindResource ( "fourlegs_template" )  as DataTemplate ;
                 }
             }
             return  null ;
         }
     }

程序初初始化的时候:

1
2
3
4
5
6
7
8
9
10
11
         private  bool isPersonDisplaying ;
         private DataContainer dc ;
         public Window1 ( )
         {
            InitializeComponent ( ) ;
             //表示当前正在显示的数据。默认显示person
            isPersonDisplaying  =  true ;
            dc  =  new DataContainer ( ) ;
             //指定数据源
            listBox1 . ItemsSource  = dc . PersonCollection ;
         }

三个按钮的函数,change:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private  void Change_Click ( object sender, RoutedEventArgs e )
         {
            changeBindingPath ( ) ;
         }
         private  void changeBindingPath ( )
         {
             if  (isPersonDisplaying )
             {
                listBox1 . ItemsSource  = dc . AnimalCollection ;
                isPersonDisplaying  =  false ;
             }
             else
             {
                listBox1 . ItemsSource  = dc . PersonCollection ;
                isPersonDisplaying  =  true ;
             }
         }

这里实现不同数据源的切换,当数据源变换的时候,WPF会调用前面我们定义的模板选择器,选择正确的模板。
两个增加数据的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  private  void addperson_Click ( object sender, RoutedEventArgs e )
         {
            person p  =  new person ( ) ;
            p . name  =  "PersonName"  +  System . DateTime . Now . Second . ToString ( ) ;
             string gender  =  ( System . DateTime . Now . Second %2 ) == 1 ? "Male" : "Female" ;
            p . gender  = gender ;
            dc . PersonCollection . Add (p ) ;
         }

         private  void addanimal_Click ( object sender, RoutedEventArgs e )
         {
            animal a  =  new animal ( ) ;
            a . type  =  "AnimalType"  +  System . DateTime . Now . Second . ToString ( ) ;
            a . legs  =  ( System . DateTime . Now . Second  %  2 )  ==  1  ?  2  :  4 ;
            dc . AnimalCollection . Add (a ) ;
         }

表达能力一般,水平一般,大家见谅。源代码我打了个包,点这里下载源码,编译环境:vs2008。

 

其实也说不上算是教程了,只是把自己学习的代码拿出来分享一下,同时方便以后遇到类似问题的时候翻一下。
MSDN里如是说:
通常,如果有多个 DataTemplate 可用于同一类型的对象,并且您希望根据每个数据对象的属性提供自己的逻辑来选择要应用的 DataTemplate,则应创建 DataTemplateSelector。请注意,如果具有不同类型的对象,则可以对 DataTemplate 设置 DataType 属性。如果您执行了此操作,则无需创建 DataTemplateSelector。此外,如果对象类型相同但属性不同,也可以考虑使用 DataTrigger 或数据转换器。
通俗讲,就是根据不同的数据选择不同的模板。接下来,我用一个例子来讲述DataTemplateSelector和动态绑定的使用方法。
先看例子的UI:

图一

图二
一个Listbox三个button,listbox显示一个集合的数据,第一个button “change”点击后可以切换与listbox绑定的集合,第二个button给PersonCollection添加person实例,第三个button给AnimalCollection添加animal实例。
默认情况下listbox与PersonCollection绑定,程序启动后,点击Add person按钮会向PersonCollection添加一个person实例,listbox会显示该集合下数据,如果person类的gender属性为Female,则只显示name属性,并把边框改成蓝色。点击change后,listbox与AnimalCollection绑定,显示AnimalCollection数据,同样,Add animal添加animal实例,如果第二个属性不是4,则只显示animal的type属性,并修改边框为蓝色。
下面讲实现
第一步:建两个类,一个person,一个animal,这就是每个listbox item将来要显示的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//person类
     public  class person  : INotifyPropertyChanged
     {
         public  event PropertyChangedEventHandler PropertyChanged ;
         private  string _name ;
         public  string name
         {
            get
             {
                 return _name ;
             }
            set
             {
                 if  (value  != _name )
                 {
                    _name  = value ;
                    prochanged ( "name" ) ;
                 }
             }
         }
         private  string _gender ;
         public  string gender
         {
            get
             {
                 return _gender ;
             }
            set
             {
                 if  (value  != _gender )
                 {
                    _gender  = value ;
                    prochanged ( "gender" ) ;
                 }
             }
         }
         private  void prochanged ( string info )
         {
             if  (PropertyChanged  !=  null )
             {
                PropertyChanged ( thisnew PropertyChangedEventArgs (info ) ) ;
             }
         }
     }

注意到这里,实现了INotifyPropertyChanged接口,需要引用命名空间System.ComponentModel.因为我希望这些属性改变的时候,listboxitem能自动更新,关于INotifyPropertyChanged接口,请看我另一篇日志wpf数据绑定binding与INotifyPropertyChanged
另一个类,animal与person基本类似,只是把person类里的属性:name和gender改成了type和legs。这里我就不贴代码了。
第二步:创建类DataContainer,包含两个集合,存储animal和person实例。代码:

1
2
3
4
5
6
7
8
9
10
     public  class DataContainer
     {
         public  ObservableCollection <person > PersonCollection  { get ;set ;  }
         public  ObservableCollection <animal > AnimalCollection  { get ; set ;  }
         public DataContainer ( )
         {
            PersonCollection  =  new ObservableCollection <person > ( ) ;
            AnimalCollection  =  new ObservableCollection <animal > ( ) ;
         }
     }

我用了ObservableCollection,因为ObservableCollection已经实现了INotifyPropertyChanged接口,所以如果集合的项增删的时候UI会自动更新。(using System.Collections.ObjectModel;//ObservableCollection)
注意:假设最上面第一段代码person类里面,没有实现INotifyPropertyChanged接口,而这里仍然使用ObservableCollection来存储person列表的话,PersonCollection 里的项增加或删除时,UI的listbox也会相应的增删,但是当你想修改PersonCollection里某一个person实例的属性,比如gender由male变成female时,UI并不会更新,因为PersonCollection 并没变。所以,当你希望改变UI listbox item里某个person的属性时,person类就需要实现INotifyPropertyChanged接口。
第三步:定义数据模板。
当然首先在窗口里把需要的listbox和button放好。我的XAML代码如下:

1
2
3
4
5
6
7
     <Grid>
         <ListBox Margin="12,12,12,60" Name="listBox1"   HorizontalContentAlignment ="Stretch" ItemTemplateSelector="{StaticResource mytemplate_selector}">
         </ListBox>
         <Button Height="23" HorizontalAlignment="Left" Margin="12,0,0,12" Name="Change" VerticalAlignment="Bottom" Width="75" Click="Change_Click">Change </Button>
         <Button Height="23" Margin="120,0,109,12" Name="addperson" VerticalAlignment="Bottom" Click="addperson_Click">Add person </Button>
         <Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" Name="addanimal" VerticalAlignment="Bottom" Width="75" Click="addanimal_Click">Add animal </Button>
     </Grid>

吃饭去了,回来再写……
继续,上面的XAML里的3个button没什么好说的,listbox里,最重要的一句是:ItemTemplateSelector=”{StaticResource mytemplate_selector}”,从字面意思理解,就是把一个叫做mytemplate_selector的静态资源,做为listbox的模板选择器。所以,接下来,定义我们需要资源,包括一个模板选择器和四个数据模板。模板选择器就是这里的mytemplate_selector,四个模板,person和animal各两个,通过模板选择器选择使用哪一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
     <Window.Resources>
         <local:MyDataTemplateSelector x:Key="mytemplate_selector" />
         <DataTemplate x:Key ="person_template">
             <Border  Name="myborder" BorderBrush="Blue" Padding="5" Margin="2" BorderThickness="1">
             <StackPanel>
                 <TextBlock  Text="{Binding Path=name}"></TextBlock >
                 <TextBlock  Text="{Binding Path=gender}"></TextBlock >
             </StackPanel>
              </Border>
             <DataTemplate.Triggers>
                 <DataTrigger  Value="Male" Binding="{Binding Path=gender}">           
                     <Setter TargetName="myborder" Property="BorderBrush" Value="Red" />
                 </DataTrigger>
             </DataTemplate.Triggers>
         </DataTemplate>
         <DataTemplate x:Key="male_template">
             <DataTemplate.Resources>
                 <Style TargetType="TextBlock">
                     <Setter Property="FontSize" Value="20" />
                 </Style>
             </DataTemplate.Resources>
             <Border  Name="myborder_male" BorderBrush="Blue" Padding="5" Margin="2" BorderThickness="1">
                 <DockPanel>
                     <TextBlock Text="{Binding Path=name}"/>
                 </DockPanel>
             </Border>
         </DataTemplate>
         <DataTemplate x:Key ="animal_template">
             <Border  Name="myborder" BorderBrush="Blue" Padding="5" Margin="2" BorderThickness="1">
                 <StackPanel>
                     <TextBlock  Text="{Binding Path=type}"></TextBlock >
                     <TextBlock  Text="{Binding Path=legs}"></TextBlock >
                 </StackPanel>
             </Border>
             <DataTemplate.Triggers>
                 <DataTrigger  Value="4" Binding="{Binding Path=legs}">
                     <Setter TargetName="myborder" Property="BorderBrush" Value="Red" />
                 </DataTrigger>
             </DataTemplate.Triggers>
         </DataTemplate>
         <DataTemplate x:Key="fourlegs_template">
             <DataTemplate.Resources>
                 <Style TargetType="TextBlock">
                     <Setter Property="FontSize" Value="20" />
                 </Style>
             </DataTemplate.Resources>
             <Border  Name="myborder_male" BorderBrush="Blue" Padding="5" Margin="2" BorderThickness="1">
                 <DockPanel>
                     <TextBlock Text="{Binding Path=type}"/>
                 </DockPanel>
             </Border>
         </DataTemplate>
     </Window.Resources>

分析前面的XAML,在里面,我们共定义了五个资源,我简单分析一下前三个,后面两个跟第二个、第三个差不多,就不说多了。
第一个资源,定义了一个模板选择器,使用CS文件里的MyDataTemplateSelector类来选择模板。第二个资源,person_template, StackPanel里定义了普通的模板,显示person的name和gender,DataTrigger里定义了一个触发器,当gender为Male的时候,将item的border变成红色。这就是前面UI里不同的边框色的实现关键点。第三个资源male_template,定义了一个Male专用的模板,字体20号,只显示name。person_template模板里,边框颜色的变换通过DataTrigger就能实现,因为要修改的属性是在模板里存在的。但是当你想修改一些模板里不存的属性或者对于gender = male时使用另外完全不同的模板,那就要用到模板选择器了。下面分析前面提到的模板选择器,看着比较多,其实很简单,忍一忍认真点儿看完,你就能有更多收获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//必须继承自DataTemplateSelector
  public  class MyDataTemplateSelector :DataTemplateSelector
     {
         //覆盖SelectTemplate函数
         public  override DataTemplate SelectTemplate ( object item, DependencyObject container )
         {
            Window win  = Application . Current . MainWindow ;
             //当前正在显示的是person类数据时:
             if  (item  !=  null  && item  is person )
             {               
                person p  = item  as person ;
                 //如果gender是male,将person_template做为现在的模板
                 if  (p . gender == "Male" )
                 {
                     return win . FindResource ( "person_template" )  as DataTemplate ;
                 }
                 //否则就使用male_template
                 else
                 {
                     return win . FindResource ( "male_template" )  as DataTemplate ;
                 }
             }
             else  if (item  !=  null  && item  is animal )
             {
                animal a  = item  as animal ;
                 if  (a . legs == 4 )
                 {
                     return win . FindResource ( "animal_template" )  as DataTemplate ;
                 }
                 else
                 {
                     return win . FindResource ( "fourlegs_template" )  as DataTemplate ;
                 }
             }
             return  null ;
         }
     }

程序初初始化的时候:

1
2
3
4
5
6
7
8
9
10
11
         private  bool isPersonDisplaying ;
         private DataContainer dc ;
         public Window1 ( )
         {
            InitializeComponent ( ) ;
             //表示当前正在显示的数据。默认显示person
            isPersonDisplaying  =  true ;
            dc  =  new DataContainer ( ) ;
             //指定数据源
            listBox1 . ItemsSource  = dc . PersonCollection ;
         }

三个按钮的函数,change:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private  void Change_Click ( object sender, RoutedEventArgs e )
         {
            changeBindingPath ( ) ;
         }
         private  void changeBindingPath ( )
         {
             if  (isPersonDisplaying )
             {
                listBox1 . ItemsSource  = dc . AnimalCollection ;
                isPersonDisplaying  =  false ;
             }
             else
             {
                listBox1 . ItemsSource  = dc . PersonCollection ;
                isPersonDisplaying  =  true ;
             }
         }

这里实现不同数据源的切换,当数据源变换的时候,WPF会调用前面我们定义的模板选择器,选择正确的模板。
两个增加数据的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  private  void addperson_Click ( object sender, RoutedEventArgs e )
         {
            person p  =  new person ( ) ;
            p . name  =  "PersonName"  +  System . DateTime . Now . Second . ToString ( ) ;
             string gender  =  ( System . DateTime . Now . Second %2 ) == 1 ? "Male" : "Female" ;
            p . gender  = gender ;
            dc . PersonCollection . Add (p ) ;
         }

         private  void addanimal_Click ( object sender, RoutedEventArgs e )
         {
            animal a  =  new animal ( ) ;
            a . type  =  "AnimalType"  +  System . DateTime . Now . Second . ToString ( ) ;
            a . legs  =  ( System . DateTime . Now . Second  %  2 )  ==  1  ?  2  :  4 ;
            dc . AnimalCollection . Add (a ) ;
         }
 
 
表达能力一般,水平一般,大家见谅。源代码我打了个包,点这里下载源码,编译环境:vs2008。

转载于:https://www.cnblogs.com/sjqq/p/7827111.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值