使用.Net Core开发WPF App系列教程( 八、WPF中的常用控件(三))
控件分类
在第一篇文章.Net Core和WPF介绍中的WPF的功能和特性部分根据功能性介绍了WPF的控件 名称。
在接下来的文章中,将会详细的介绍各个控件的概念及使用。
主要包括:
内容控件:Label、Button、CheckBox、ToggleButton、RadioButton、ToolTip和ScrollViewer
带有标题的内容控件:TabItem、GroupBox、Expander
导航控件:Frame、TabControl、Page
窗体控件:Window
文本控件:TextBox、PasswordBox、RichTextBox
列表控件:ListBox、ListView、DataGrid、ComboBox、TreeView和ToolBar
基于范围的控件:Slider和ProgressBar
日期控件:Calender和DatePicker
媒体控件:Image和MediaElement
轻量级控件:TextBlock
菜单:Menu、ContextMenu
浏览器控件:WebBrowser
文本控件
TextBox、PasswordBox、和RichTextBox都是文本控件。
虽然把这三个控件归到一个类别进行演示,实际他们继承的类并不相同。
TextBox和RichTextBox都继承自TextBoxBase,TextBoxBase是为文本编辑控件提供功能的抽象基类。
TextBox控件
TextBox是普通文本框控件,支持显示或编辑未设置格式的文本。相比于RichTextBox,TextBox占用的资源更少。
TextBox和RichTextBox对比
控件 | 实时拼写检查 | 上下文菜单 | 格式命令,例如 ToggleBold (Ctr+B) | FlowDocument 内容,例如图像、段落、表格等 |
---|---|---|---|---|
TextBox | 是 | 是 | 否 | 不是。 |
RichTextBox | 是 | 是 | 是 | 是 |
常用属性
属性 | 说明 |
TextWrapping | 指示文本框应该如何换行 |
Text | 文本框要显示的文本 |
TextAlignment | 文本框水平对齐方式 |
SelectedText | 文本框选中的文本 |
MaxLines | 文本框显示的最多行数 |
MinLines | 文本框显示的最小行数 |
AcceptReturn | 按下回车时,是否换行 |
VerticalScrollBarVisibility | 控制垂直滚动条的可见性 |
SelectionStart | 所选文本的开始位置 |
SelectionLength | 所选文本的长度 |
设置文本框文本
通过TextBox.Text属性,可以设置/获取文本框的显示文本
多行显示
通过TextBox.TextWraping属性,可以设置文本框的换行方式,可选以下枚举值:
1 public enum TextWrapping 2 { 3 //如果行溢出可用块宽度,则将发生换行。 但是,如果换行算法不能确定换行时机,例如超长单词限制于固定宽度容器中而不允许滚动时,行会溢出块宽度。 4 WrapWithOverflow = 0, 5 //不执行换行。 6 NoWrap = 1, 7 //如果行溢出可用块宽度,即使标准换行算法不能确定换行时机,例如超长单词限制于固定宽度容器中而不允许滚动时,也将发生换行。 8 Wrap = 2 9 }
可以通过设置TextBox.MaxLines属性来设置文本框能显示的最多行数,在未指定TextBox高度的情况下,TextBox会显示为能显示MaxLines行数的高度。
1 <TextBox Width="200" HorizontalAlignment="Center" VerticalAlignment="Center" Text="One of the TextAlignment values that specifies the horizontal alignment of the contents of the text box. The default is Left." TextWrapping="WrapWithOverflow" MaxLines="3"></TextBox>
TextBox.MinLines属性可以设置最小显示行数。
TextBox.AcceptsReturn 属性设置为 true
会导致在按下 回车键时插入新行,如果需要,会再次自动扩展 TextBox 以包含新行的空间。
TextBox.VerticalScrollBarVisibility属性将滚动条添加到 TextBox,以便在 TextBox 扩展超出包围它的框架或窗口的大小时可以滚动 TextBox 的内容。
选择文本
通过TextBox.SelectionStart属性设置所选文本的开始位置、TextBox.SelectionLength设置所选文本的长度。
TextBox.SelectedText属性可以快速检查或改变在文本框中选中的文本。
例如我们在界面上放置一个按钮,当按钮点击时,设置SelectionStart = 0,SelectionLength = 13
1 <TextBox Margin="10" Width="200" Name="tbox1" HorizontalAlignment="Center" VerticalAlignment="Center" Text="One of the TextAlignment" TextWrapping="WrapWithOverflow" VerticalScrollBarVisibility="Auto" MaxLines="3" AcceptsReturn="True"></TextBox> 2 <Button Content="选中文本" Click="Button_Click"></Button>
1 private void Button_Click(object sender, RoutedEventArgs e) 2 { 3 tbox1.Focus(); 4 tbox1.SelectionBrush = Brushes.Red; 5 tbox1.SelectionStart = 0; 6 tbox1.SelectionLength = 13; 7 }
运行效果如下:
说明:
需要先设置文本框焦点,否则设置选中无效
TextBox.AutoWordSelection属性设置为true时,可以在拖动鼠标时自动选中整个单词。
TextBox.SelectionBrush属性可以设置文本选中时的画刷。
PasswordBox
PasswordBox是密码文本框,用于输入密码。它跟TextBox不同的是,输入的文本会变成圆圈符号,PasswordBox不支持剪贴板。
常用函数
函数 | 说明 |
Clear | 清除PasswordBox.Password属性的值 |
Paste | 将剪贴板的内容替换当前选中的密码文本 |
SelectAll | 选中全部密码文本 |
常用属性
属性 | 说明 |
Password | 获取/设置当前PasswordBox的密码文本 |
PasswordChar | 获取/设置PasswordBox密码的掩码字符 |
当密码更改时,会触发PasswordChanged事件。
在PasswordBox内部,使用的是System.Security.SecureString对象来存储密码,与普通文本类似,Security也是纯文本对象,但是是以加密方式在内存中保存。用于加密字符串的密钥是随机生成的,存储在一块从来不会写入到磁盘的内存中。
说明:
PasswordBox.Password属性是CLR属性,而非WPF的依赖属性,所以Password不支持绑定。在后面介绍MVVM及绑定时,会介绍如何实现PasswordBox.Password的绑定。
RichTextBox
RichTextBox是富文本框,它支持富文本,使用WPF中的FlowDocument对象。
这里不做详细介绍,在后面介绍WPF中的文档时,再进行详细介绍。
列表控件
WPF提供了一些可用于集合显示的控件,如ListBox、Combox、TreeView等。这些列表控件都继承自ItemsControl类。
ItemsControl
ItemsControl 是一种可以包含多个项(如字符串、对象或其他元素)的 类型 Control 。 下图显示了包含以下 ListBox 不同类型的项的控件:
-
一个字符串。
-
DateTime 对象。
-
Image 对象。
ItemsControl类添加了所有基于列表的控件都使用的功能,它提供了两种填充列表项的方式
1、使用代码或XAML将列表项直接添加到Items集合中
使用代码添加
1 ListBoxItem listBoxItem = new ListBoxItem(); 2 listBoxItem.Content = "HelloWorld"; 3 4 ListBoxItem listBoxItem2 = new ListBoxItem(); 5 listBoxItem2.Content = DateTime.Now; 6 7 ListBoxItem listBoxItem3 = new ListBoxItem(); 8 listBoxItem3.Content = new Image() {Height=150,Stretch=Stretch.Uniform, Source = new BitmapImage(new Uri("https://img0.baidu.com/it/u=2560415768,2503528984&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1717261200&t=9c08bc35cfa6953d1b68c3bdcbc7e8f9")) }; 9 10 this.listbox3.Items.Add(listBoxItem); 11 this.listbox3.Items.Add(listBoxItem2); 12 this.listbox3.Items.Add(listBoxItem3);
XAML添加
1 <ListBox> 2 <ListBox.Items> 3 <ListBoxItem>HelloWorld</ListBoxItem> 4 <ListBoxItem Content="{x:Static sys:DateTime.Now}"></ListBoxItem> 5 <ListBoxItem> 6 <ListBoxItem.Content> 7 <Image Stretch="Uniform" Height="150" Source="https://img0.baidu.com/it/u=2560415768,2503528984&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1717261200&t=9c08bc35cfa6953d1b68c3bdcbc7e8f9" ></Image> 8 </ListBoxItem.Content> 9 </ListBoxItem> 10 </ListBox.Items> 11 </ListBox>
2、使用数据绑定,将集合绑定到ItemSource。
ItemSource支持绑定到任何实现了IEnumerable接口的类型
1 List<string> list = new List<string>(); 2 list.Add("1"); 3 list.Add("2"); 4 list.Add("3"); 5 6 this.listbox1.ItemsSource = list;
说明:
当设置了ItemSource后,Items集合被设置为只读状态,此时无法再通过Items集合去操作列表。如果想清空列表,可以设置ItemSource为null。
每个 ItemsControl 类型都有相应的项容器类型。 ItemsControl 的每个项容器类型都会添加到Items中
例如:
对于 ListBox,项容器是 ListBoxItem 控件。
对于 ComboBox,项容器是 ComboBoxItem 控件。
我们可以手动为ItemsControl的每一项显式创建容器类型,但这不是必需的。 如果不显式创建容器类型,则会生成一个容器类型,其中包含项集合中的数据项。
例如,如果将字符串对象的 ItemsSource 集合绑定到 的 ListBox的ItemSource,则不会显式创建 ListBoxItem 对象,但 ListBox 将为每个字符串生成一个ListBoxItem对象。 可以使用 属性访问生成的项容器 ItemContainerGenerator 。
ItemControl的显示效果可以通过样式/控件模板和数据模板来进行改变,在后面介绍到ListBox样式时,再单独进行介绍。
Selector
Selector类继承自ItemsControl类,它除了拥有ItemsControl的全部功能外,还添加了允许用户从其子元素中选择项的功能。
常用属性
属性 | 说明 |
SelectedValue | 获取或设置SelectedItem的值,它是通过SelectedValuePath来确定的 |
SelectedItem |
获取或设置当前选中项的第一项,如果选择项为空,则返回null |
SelectedIndex |
获取或设置当前选中项的第一项的索引,如果选择项为空,则返回-1 |
SelectedValuePath |
获取或设置SelectedValue从SelectedItem获取值的路径 刚开始看到这里可能不好理解, 通俗点讲,就是列表绑定的可能是一个对象集合,我们要获取选中项对象里的 某个字段,就通过SelectedValuePath指定 |
说明:
ItemsControl类之后的继承层次有两个主要分支。
一个主要分支是选择器(Selector),包括ListBox、ComboBox以及TabControl。这些控件都继承自Selector类,
并且都具有跟踪当前选择项(SelectedItem)或其位置(SelectedIndex)的属性。
另一个分支,以不同方式选择列表项。该分支包括用于Menu、Toolbar以及TreeView,所有这些控件都继承自ltemsControl,但没有继承选择器(Selector)类。
另外 Microsoft Ribbon for WPF里的一些控件也是继承自ItemsControl类,在后面会专门出一篇文章介绍Ribbon for WPF的使用。
在我们自己封装列表控件时,可以直接继承自ListBox类,这样就可以得到列表的基础功能。
因为WPF的数据模板和控件模板功能,列表控件可以显示为多样化,下图为一个圆盘状的列表控件
在设置控件的模板后,只需要将它绑定到一个列表,即可现实类似的效果。
1 this.menu.ItemsSource = itemList;
ListBox控件
前面介绍了关于ItemsControl的一些大概理论,现在详细介绍一下ListBox控件的一些使用。
选择模式
ListBox提供了一个SelectionMode属性,可以用于多选模式。SelectionMode属性设置为Multiple或 Extended,ListBox类还允许选择多项。
在Multiple模式下,可通过单击项进行选择或取消选择。
在Extended模式下,需要按下Ctrl键选择其他项,或按下Shift键选择某个选项范围。在这两种多选模式下,可用SelectedItems 集合替代SelectedItem属性来获取所有选择的项。
选择项更改事件
当列表选项项更改时,会引发SelectionChanged事件,
如果希望确定哪些项(如果存在的话)被取消选中,在事件处理函数中可使用SelectionChangedEventArgs对象的Removedltems属性。
类似地,可通过Addedltems 属性了解哪些项被添加到了选中的项中。
1 private void listbox4_SelectionChanged(object sender, SelectionChangedEventArgs e) 2 { 3 var removedItems = e.RemovedItems;//取消选中的项 4 var addedItems = e.AddedItems; //新选中的项 5 }
在单项选择模式下,无论何时选项发生变化,总有一项被选中并总有一项被取消选中。在多项选择或扩展选择模式下,情况就未必如此了。
ListBoxItem
在前面提到过,为ListBoxItem添加项时,可以指定容器项为ListBoxItem,也可以不指定。不指定时,系统会自动生成一个ListBoxItem容器项。
ListBoxltem类继承自ContentControl类,从而ListBoxltem能够包含一段嵌套的内容。
如果该内容继承自UIElememt类,它将在ListBox 控件中呈现出来。如果是其他类型的对象,ListBoxItem对象会调用ToString()方法并显示最终的文本。
例如我们有一个Student列表,想显示在ListBox中,只需要重写ToString()函数,ListBoxItem就会显示我们想要显示的内容。
1 public class Student 2 { 3 public int Id { get; set; } 4 5 public string Name { get; set; } 6 7 public override string ToString() 8 { 9 return Name; 10 } 11 }
性能问题
显示大量项可能会导致性能问题,可以参阅
https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/optimizing-performance-controls?view=netframeworkdesktop-4.8
列表项拖动
可以nuget搜索包gong-wpf-dragdrop
ListView控件
ListView继承自ListBox,增加了对于列显示的支持,并且增加了快速切换视图或显示模式的功能,而且在切换时,不需要重新绑定数据以及重新构建列表。
ListView控件在ListBox的基础上,增加了一个View(视图)属性,View属性控制数据在ListView中的样式和组织方式。借助View属性,可以创建内容丰富的列表。
在WPF中,目前只提供了视图对象GridView,GridView对于创建多列非常有用,如果GridView不能满足需求,可以创建自定义视图,下面先介绍GridView的使用。
GridView使用
GridView类继承自ViewBase类,表示具有多列的列表视图。
通过GridView.Columns集合添加GridViewColumn对象可以定义这些列,同样可以使用其他 Collection<T> 方法(如 Remove 和 Insert )修改 中的 GridView.Columns。
GridViewColumn提供了Header和DisplayMemberBinding两个属性
Header属性用于显示列头
DisplayMemberBinding属性提供了一个绑定,可以定义要在列中显示的数据。
下面的示例代码为ListView添加了三列,并绑定数据显示
使用XAML添加
1 <ListView Name="listview1"> 2 <ListView.View> 3 <GridView AllowsColumnReorder="true" 4 ColumnHeaderToolTip="Employee Information"> 5 6 <GridViewColumn DisplayMemberBinding= 7 "{Binding Path=FirstName}" 8 Header="First Name" Width="100"/> 9 10 <GridViewColumn DisplayMemberBinding= 11 "{Binding Path=LastName}" 12 Width="100"> 13 <GridViewColumnHeader>Last Name</GridViewColumnHeader> 14 </GridViewColumn> 15 <GridViewColumn DisplayMemberBinding= 16 "{Binding Path=EmployeeNumber}" 17 Header="Employee No." Width="100"/> 18 </GridView> 19 </ListView.View> 20 </ListView>
使用代码添加
1 private void CreateListView2() 2 { 3 GridView myGridView = new GridView(); 4 myGridView.AllowsColumnReorder = true; 5 myGridView.ColumnHeaderToolTip = "Employee Information"; 6 7 GridViewColumn gvc1 = new GridViewColumn(); 8 gvc1.DisplayMemberBinding = new Binding("FirstName"); 9 gvc1.Header = "FirstName"; 10 gvc1.Width = 100; 11 myGridView.Columns.Add(gvc1); 12 GridViewColumn gvc2 = new GridViewColumn(); 13 gvc2.DisplayMemberBinding = new Binding("LastName"); 14 gvc2.Header = "Last Name"; 15 gvc2.Width = 100; 16 myGridView.Columns.Add(gvc2); 17 GridViewColumn gvc3 = new GridViewColumn(); 18 gvc3.DisplayMemberBinding = new Binding("EmployeeNumber"); 19 gvc3.Header = "Employee No."; 20 gvc3.Width = 100; 21 myGridView.Columns.Add(gvc3); 22 23 this.listview2.View = myGridView; 24 }
创建数据并绑定
Employee.cs
1 public class Employee 2 { 3 public string FirstName { get; set; } 4 5 public string LastName { get; set; } 6 7 public string EmployeeNumber { get; set; } 8 }
添加测试数据及绑定
1 var list = new List<Employee>(); 2 3 Employee employee1 = new Employee() { FirstName = "FirstName1",LastName = "LastName1",EmployeeNumber = "100"}; 4 Employee employee2 = new Employee() { FirstName = "FirstName2", LastName = "LastName2", EmployeeNumber = "200" }; 5 6 list.Add(employee1); 7 list.Add(employee2); 8 9 this.listview1.ItemsSource = list;
显示效果如下:
GridView列的大小
通过设置GridViewColumn.Width属性,可以设置列的宽度。如果想设置宽度自动,可以设置Width=NaN,如下所示
XAML
1 <GridViewColumn DisplayMemberBinding= 2 "{Binding Path=FirstName}" 3 Header="First Name" Width="NaN"/>
C#
1 gvc3.Width = double.NaN;
但这只是确定了列的初始宽度 ,用户还是可以拖动列的边缘来修改列宽,如果想限制列的最大/最小宽度,需要通过模板功能进行列头重定义。在后面介绍到控件模板功能时,再进行介绍。
单元格模板
通过GridViewColumn的CellTemplate属性,可以定义单元格的数据模板。
一般情况下,我们使用了DisplayMemberBinding属性,指定了要在列中显示的数据, 就不需要指定CellTemplate了。
在使用了DisplayMemberBinding属性的情况下,CellTemplate属性指定的设置会被忽略。
但是有时候我们需要创建一些复杂的显示,就需要使用到CellTemplate功能
例如:让单元格里的文本支持换行,如下所示
XAML
1 <GridViewColumn.CellTemplate> 2 <DataTemplate> 3 <TextBlock Text="{Binding LastName}" TextWrapping="Wrap"></TextBlock> 4 </DataTemplate> 5 </GridViewColumn.CellTemplate>
说明:
还可以通过GridViewColumn的CellTemplateSelector属性,为数据项应用不同的模板,关于模板选择器这里不做详细介绍,到后面介绍到模板功能时再详细说明。
自定义列标题
通过GridViewColumn.Header属性,可以设置列头,Header支持WPF的元素,所以我们可以添加多种内容显示。
如果想用绑定的内容显示在列头,可以使用GridViewColumn.HeaderTemplate属性。
如果想设置列头的模式,可以使用GridViewColumn.HeaderContainerStyle属性,通过样式,定义列头的显示效果。
还可以通过GridViewColumn.HeaderTemplateSelector或GridView.ColumnHeaderTemplateSelector属性,为不同的标题应用不同的模板。
创建自定义视图
在创建自定视图前我们需要先了解一些概念。
1、自定义视图类需要继承自ViewBase类,它是一个抽象类,它的作用是将两个样式捆绑在一起。
2、视图通过重写两个protected属性DefaultStyleKey和ItemContainerDefaultStyleKey来进行工作,这两个属性返System.Window.ResourceKey的特殊对象。
其中:DefaultStyleKey用于控制ListView控件的样式,ItemContainerDefaultStyleKey用于控制ListViewItem控件的样式。
3、视图类创建好后,通过DataTemplate控制每个列表元素的显示效果
说明:
创建自定义视图的前提是现有的控件无法满足需求,如果在能满足的情况下,推荐通过WPF自带控件实现。
下面我们创建一个类似Windows资源管理器的视图,运行效果如下
实现步骤如下
1、创建视图类 TileView
首先在Visual Studio中,新建自定义控件,将生成的代码删除,添加如下代码
1 public class TileView : ViewBase 2 { 3 public static readonly DependencyProperty ItemContainerStyleProperty = ItemsControl.ItemContainerStyleProperty.AddOwner(typeof(TileView)); 4 5 public Style ItemContainerStyle 6 { 7 get { return (Style)GetValue(ItemContainerStyleProperty); } 8 set { SetValue(ItemContainerStyleProperty, value); } 9 } 10 11 public static readonly DependencyProperty ItemTemplateProperty = ItemsControl.ItemTemplateProperty.AddOwner(typeof(TileView)); 12 13 public DataTemplate ItemTemplate 14 { 15 get { return (DataTemplate)GetValue(ItemTemplateProperty); } 16 set { SetValue(ItemTemplateProperty, value); } 17 } 18 19 private Brush selectedBackground = Brushes.Transparent; 20 21 public Brush SelectedBackground 22 { 23 get => this.selectedBackground; 24 set => this.selectedBackground = value; 25 } 26 27 private Brush selectedBorderBrush = Brushes.Black; 28 29 public Brush SelectedBorderBrush 30 { 31 get => this.selectedBorderBrush; 32 set => this.selectedBorderBrush = value; 33 } 34 35 protected override object DefaultStyleKey 36 { 37 get 38 { 39 return new ComponentResourceKey(GetType(), "TileView"); //指定ListView样式 40 } 41 } 42 43 protected override object ItemContainerDefaultStyleKey 44 { 45 get 46 { 47 return new ComponentResourceKey(GetType(), "TileViewItem"); //指定ListViewItem样式 48 } 49 } 50 }
TileView类应用了两个样式:TileView(用于ListView)和TileViewItem(用于ListViewItem),此外,还定义了ItemTemplate、ItemContainerStyle和SelectedBackground属性,他们分别用于数据模板、元素样式和选择时背景颜色的设置。
关于ComponentResourceKey,它的作用是定义和引用从外部程序集加载的资源的键。 这使资源查找能够在程序集中指定目标类型,而不是在程序集中或类上指定显式资源字典。ComponentResourceKey封装了两部分信息:拥有样式的类的类型和标识资源描述性的ResourceId字符串。
2、为视图定义样式
创建自定义控件后,Visual Studio会生成一个Themes文件夹,并在文件夹下生成一个Generic.xaml文件,WPF使用Generic.xaml文件关联到某个类的默认样式。
我们在Generic.xaml里为ListView和ListViewItem定义样式
1 <Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:TileView}, ResourceId=TileView}" 2 TargetType="{x:Type ListView}" BasedOn="{StaticResource {x:Type ListBox}}"> 3 <Setter Property="BorderBrush" Value="Black"></Setter> 4 <Setter Property="BorderThickness" Value="0.5"></Setter> 5 <Setter Property="Grid.IsSharedSizeScope" Value="True"></Setter> 6 7 <Setter Property="ItemsPanel"> 8 <Setter.Value> 9 <ItemsPanelTemplate> 10 <WrapPanel Width="{Binding (FrameworkElement.ActualWidth), 11 RelativeSource={RelativeSource 12 AncestorType=ScrollContentPresenter}}" 13 ></WrapPanel> 14 </ItemsPanelTemplate> 15 </Setter.Value> 16 </Setter> 17 </Style> 18 19 <Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:TileView},ResourceId=TileViewItem}" 20 TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}"> 21 <Setter Property="Background" Value="Transparent"></Setter> 22 <Setter Property="Padding" Value="3"/> 23 <Setter Property="Margin" Value="2"/> 24 <Setter Property="HorizontalContentAlignment" Value="Center"></Setter> 25 <Setter Property="ContentTemplate" Value="{Binding Path=View.ItemTemplate, 26 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"></Setter> 27 28 <Setter Property="Template"> 29 <Setter.Value> 30 <ControlTemplate TargetType="{x:Type ListViewItem}"> 31 <Border 32 Name="Border" BorderThickness="1" CornerRadius="3" Background="{TemplateBinding Background}"> 33 <ContentPresenter /> 34 </Border> 35 <ControlTemplate.Triggers> 36 <Trigger Property="IsSelected" Value="True"> 37 <Setter TargetName="Border" Property="BorderBrush" 38 Value="{Binding Path=View.SelectedBorderBrush, 39 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"></Setter> 40 <Setter TargetName="Border" Property="Background" 41 Value="{Binding Path=View.SelectedBackground, 42 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"></Setter> 44 </Trigger> 45 <Trigger Property="IsMouseOver" Value="True"> 46 <Setter Property="Background" Value="Orange" TargetName="Border"/> 47 </Trigger> 48 </ControlTemplate.Triggers> 49 </ControlTemplate> 50 </Setter.Value> 51 </Setter> 52 53 </Style>
在设置Key时,使用了ComponentResourceKey类型,然后设置TargetType为ListView/ListViewItem,通过BaseOn属性指示继承ListBox/ListBoxItem元素使用的基本样式。
在样式上:
1、将Grid.IsSharedSizeScope附加属性设置为true,当使用Grid为布局容器时,可以使不同的列表项使用共享的列或行。
2、将ItemsPanel属性从StackPanel面板改为WrapPanel面板,从而实现平铺行为。
3、为了确保最大灵活性,TileView样式需要检索TileView对象(ListView.View属性),然后从TileView.ItemTemplate属性中提取模板数据。就像下面这样
1 ItemHeight="{Binding (ListView.View).ItemHeight, 2 RelativeSource={RelativeSource AncestorType=ListView}}"
3、为ListView定义不同的视图
这里我们定义了三个视图,默认的GridView视图、图片显示视图和图片加详情视图。
1 <GridView x:Key="GridView"> 2 <GridView.Columns> 3 <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=ModelName}"/> 4 <GridViewColumn Header="Model" DisplayMemberBinding="{Binding Path=ModelNumber}"/> 5 <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=UnitCost,StringFormat={}{0:C}}"/> 6 </GridView.Columns> 7 </GridView> 8 9 <local:TileView x:Key="ImageView" SelectedBackground="Orange"> 10 <local:TileView.ItemTemplate> 11 <DataTemplate> 12 <StackPanel Width="150" VerticalAlignment="Top"> 13 <Image Source="{Binding ProductImagePath}" Margin="6"></Image> 14 <TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding ModelName}"></TextBlock> 15 </StackPanel> 16 </DataTemplate> 17 </local:TileView.ItemTemplate> 18 </local:TileView> 19 20 <local:TileView x:Key="ImageDetailView"> 21 <local:TileView.ItemTemplate> 22 <DataTemplate> 23 <Grid> 24 <Grid.ColumnDefinitions> 25 <ColumnDefinition Width="auto"/> 26 <ColumnDefinition Width="auto" SharedSizeGroup="Col2"/> 27 </Grid.ColumnDefinitions> 28 29 <Image Margin="5" Width="100" Source="{Binding Path=ProductImagePath}"></Image> 30 <StackPanel Grid.Column="1" VerticalAlignment="Center"> 31 <TextBlock FontWeight="Bold" Text="{Binding Path=ModelName}"></TextBlock> 32 <TextBlock Text="{Binding Path=ModelNumber}"/> 33 <TextBlock Text="{Binding Path=UnitCost,StringFormat={}{0:C}}"></TextBlock> 34 </StackPanel> 35 </Grid> 36 </DataTemplate> 37 </local:TileView.ItemTemplate> 38 </local:TileView>
4、动态切换视图
我们在界面上增加一个ComboBox控件,在SelectionChanged事件处理函数中动态切换视图。
1 <ComboBox Name="combox" Width="120" SelectedIndex="0" SelectionChanged="ComboBox_SelectionChanged"> 2 <ComboBoxItem>GridView</ComboBoxItem> 3 <ComboBoxItem>ImageView</ComboBoxItem> 4 <ComboBoxItem>ImageDetailView</ComboBoxItem> 5 </ComboBox>
1 private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 2 { 3 var viewName = (this.combox.SelectedItem as ComboBoxItem)?.Content; 4 this.listview3.View = (ViewBase)this.FindResource(viewName?.ToString()); 5 }
5、为视图传递信息
在定义TitlView视图类时,我们增加了一个依赖属性SelectedBackground,并在设置样式时进行了绑定
1 <Setter TargetName="Border" Property="Background" 2 Value="{Binding Path=View.SelectedBackground, 3 RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"></Setter>
这样我们就可以动态设置这个属性。
如果我们需要传递其它信息,也可以通过这种方式。
完整的代码可以参考文末的示例代码链接。
DataGrid控件
DataGrid是用于数据显示的控件,从对象集合获取信息并在具有行和单元格的网络中显示信息。每行和单独的对象相对应 ,并且每列和对象的某个属性相对应 。
使用了样式的DataGrid控件
前面我们介绍过ItemsControl和Selector类,DataGrid类继承自MultiSelector,而MultiSelector继承自Selector类。
MultiSelector在Selector的基础上增加了SelectedItems属性以及SelectAll()函数和UnselectAll()函数。
常用属性
属性 | 说明 |
RowBackground | 用于指定绘制每行背景的画刷 |
AlternatingRowBackground |
绘制交替行的背景画刷,从而更容易区分行。 默认情况下,奇数行:白色 偶数行:淡灰色 |
AutoGenerateColumns |
是否自动创建列,默认值是true |
ColumnHeaderHeight | 标题行的高度 |
RowHeaderWidth | 具有行头的列的宽度。该列在网格的最左边,不显示任何数据,它能标识当前选择的行/正在编辑的行。 |
ColumnWidth | 获取或设置DataGrid中列和标头的标准宽度和大小调整模式。 |
RowHeight | 每行的高度 |
GridLineVisiblity | 是否显示网格线,可选Horizontal、Vertical、None或All等值 |
VerticalGridLinesBrush | 用于绘制列之间网格线的画刷 |
HorizontalGridLinesBrush | 用于绘制行之间网格线的画刷 |
HeadersVisibility | 确定显示哪种表头,可选Column、Row、All和None |
快速填充数据
DataGrid具备自动生成功能,可以直接将数据源绑定到DataGrid
假设我们定义了一个Student类,并产生一些测试数据
1 public class Student 2 { 3 public int ID { get; set; } 4 5 public string Name { get; set; } 6 7 public float Height { get; set; } 8 9 public float Weight { get; set; } 10 11 public DateTime Birthdate { get; set; } 12 13 public static List<Student> GetDemoData() 14 { 15 return new List<Student>() 16 { 17 new Student(){ID = 1,Name = "极大规模集成电路",Weight = 61,Height = 170,Birthdate = new DateTime(1990,01,01) }, 18 new Student(){ID = 2,Name = "哪里有",Weight = 62,Height = 171,Birthdate = new DateTime(1991,01,01) }, 19 new Student(){ID = 3,Name = "枯井",Weight = 63,Height = 172,Birthdate = new DateTime(1992,01,01) }, 20 new Student(){ID = 4,Name = "粗浅",Weight = 64,Height = 173,Birthdate = new DateTime(1993,01,01) }, 21 }; 22 } 23 }
然后在界面上放置一个DataGrid
1 <DataGrid x:Name="grid1" AutoGenerateColumns="True"> 2 3 </DataGrid>
再将测试数据绑定到DataGrid
1 this.grid1.ItemsSource = Student.GetDemoData();
运行结果如下
可以看到DataGrid自动生成了列标题及自动填充了数据。
绑定到数据库
DataGrid的自动生成功能在展示数据库的数据时非常有用,可以直接将DataTable.DefaultView绑定到DataGrid.ItemSource上,而且还可以在编辑数据后将数据更新回数据库。
这里为了方便展示 ,以SQLite数据库为例,假设我们创建了如下的表
增加如下数据
然后查询数据并绑定到DataGrid
1 private DataTable GetDataFromDB() 2 { 3 var sql = "Select * from patient"; 4 var constr = $"data source = {Environment.CurrentDirectory + "\\patient.db"};version=3"; 5 using(SQLiteConnection con = new SQLiteConnection(constr)) 6 { 7 con.Open(); 8 9 if (con.State != ConnectionState.Open) 10 return new DataTable(); 11 12 using (SQLiteDataAdapter sQLiteDataAdapter = new SQLiteDataAdapter(sql, con)) 13 { 14 DataTable dt = new DataTable(); 15 sQLiteDataAdapter.Fill(dt); 16 return dt; 17 } 18 } 19 }
运行效果
将数据更新回数据库
在对DataGrid的数据进行编辑后,可以将数据再保存为数据库,可以通过下面的方法
首先我们将界面上DataGrid的数据取出来
1 private DataTable GetDataTableFromDataGrid(DataGrid dataGrid) 2 { 3 return ((DataView)dataGrid.ItemsSource).Table; 4 }
在界面上将数据编辑一下
更新回数据库
1 private void UpdateToDb(DataTable dt) 2 { 3 var constr = $"data source = {Environment.CurrentDirectory + "\\patient.db"};version=3"; 4 using (SQLiteConnection con = new SQLiteConnection(constr)) 5 { 6 con.Open(); 7 if (con.State != ConnectionState.Open) 8 return; 9 10 var sql = "Select * from patient"; 11 SQLiteDataAdapter da = new SQLiteDataAdapter(); 12 da.SelectCommand = new SQLiteCommand(sql, con); 13 SQLiteCommandBuilder builder = new SQLiteCommandBuilder(da); 14 da.Update(dt); 15 } 16 17 }
保存后到数据库查看,发现数据已经更新成功了
定义列
在前面介绍ListView控件时,默认的视图是GridView,GridView在使用时,需要定义列。DataGrid也是类似的模式,在使用时,可以使用自动生成的列,也可以自己定义列。
自动生成的列相比自定义列,缺少一些控制功能:不能控制列的顺序、不能控制列宽、不能控制表头文本等。
在使用自定义列时,需要将AutoGenerateColumns属性设置为false,然后将列添加到DataGrid.Columns集合中去。
添加到DataGrid.Columns中的对象必须是DataGridColumn类型(DataGridColumn是一个抽象类,表示 DataGrid的数据列)。
DataGridColumn类有几个通用的属性
Header属性:用于获取或设置列题头,通常情况下,都是设置为普通字符串。但列题头是内容控件,可以为Header提供任何内容。
Width属性:设置获取或设置列宽,如果设置了DataGrid.ColumnWidth属性,那么值会被DataGridColumn.Width值覆盖,因为DataGrid.ColumnWidth属性是应用于整个表格的所有列,而DataGridColumn.Width只应用于当前列。
Binding属性:为当前列的数据提供绑定表达式。
DataGrid支持以下几种类型的列,这些类型都是继承自DataGridColumn类。
1、DataGridTextColumn
文本列,文本显示在TextBlock控件中,当编辑行时,TextBlock被替换为TextBox。
使用示例:
1 <DataGrid Name="grid3"> 2 <DataGrid.Columns> 3 <DataGridTextColumn Header="用户名" Binding="{Binding UserName}" Width="100"></DataGridTextColumn> 4 </DataGrid.Columns> 5 </DataGrid>
2、DataGridCheckBoxColumn
复选框列,一般当值为Boolean类型时,使用这种列。默认情况下,CheckBox是只读的,在编辑行时,会变成普通的CheckBox。
DataGridCheckBoxColumn提供了IsThreeState属性,使用方法和CheckBox一致。
使用示例
1 <DataGrid Name="grid3"> 2 <DataGrid.Columns> 3 <DataGridCheckBoxColumn Header="是否启用" Binding="{Binding IsEnable}" Width="60" IsThreeState="False"></DataGridCheckBoxColumn> 4 </DataGrid.Columns> 5 </DataGrid>
3、DataGridHyperlinkColumn
超链接列,这种列显示为可单击的链接。可以导航到指定的URI。
使用示例
1 <DataGridHyperlinkColumn Binding="{Binding Site}" Header="用户站点" Width="60"></DataGridHyperlinkColumn>
4、DataGridComboxColumn
下拉框列,默认情况下显示为文本,类似DataGridTextColumn,但在编辑模式下,会显示为Combox控件。
通过SelectedItemBinding属性可以设置选中项的绑定,同时还要设置SelectedValuePath来指定值的路径。ItemSource可以设置ComboBox的下拉项。
注意:如果通过SelectedItemBinding来设置选中项时会存在一定的问题,因为对象引用问题,建议通过TextBinding属性来设置选中文本。
DataGridComboxColumn.ItemSource可以设置下拉项显示值的绑定,如果将一个对象集合绑定到DataGridComboxColumn.ItemSource时,需要DisplayMemberBinding属性来设置显示哪个值。
DataGridComboxColumn.ItemSource支持以下数据源。
-
静态资源。 有关详细信息,请参阅 StaticResource 标记扩展。
-
x:Static 代码实体。 有关详细信息,请参阅 x:Static Markup Extension。
-
类型的内联集合 ComboBoxItem 。
使用示例:
这里我直接在XAML中定义了ItemSource
1 <DataGridComboBoxColumn SelectedItemBinding="{Binding Gender}" Header="性别" Width="60"> 2 <DataGridComboBoxColumn.ItemsSource> 3 <col:ArrayList> 4 <sys:String>Male</sys:String> 5 <sys:String>Female</sys:String> 6 </col:ArrayList> 7 </DataGridComboBoxColumn.ItemsSource> 8 </DataGridComboBoxColumn>
5、DataGridTemplateColumn
模板列,这种列允许为显示值定义数据模板,这也就意味着,WPF的控件都可以支持。
1 <DataGridTemplateColumn Header="用户头像" Width="150"> 2 <DataGridTemplateColumn.CellTemplate> 3 <DataTemplate> 4 <Image Source="{Binding Avatar}" Stretch="UniformToFill" Height="150"></Image> 5 </DataTemplate> 6 </DataGridTemplateColumn.CellTemplate> 7 </DataGridTemplateColumn>
演示:使用上面的不同列创建一个DataGrid
1 <DataGrid Name="grid3"> 2 <DataGrid.Columns> 3 <DataGridTextColumn Header="用户名" Binding="{Binding UserName}" Width="100"></DataGridTextColumn> 4 <DataGridCheckBoxColumn Header="是否启用" Binding="{Binding IsEnable}" Width="60" IsThreeState="False"></DataGridCheckBoxColumn> 5 <DataGridHyperlinkColumn Binding="{Binding Site}" Header="用户站点" Width="60"></DataGridHyperlinkColumn> 6 <DataGridComboBoxColumn SelectedItemBinding="{Binding Gender}" Header="性别" Width="60"> 7 <DataGridComboBoxColumn.ItemsSource> 8 <col:ArrayList> 9 <sys:String>Male</sys:String> 10 <sys:String>Female</sys:String> 11 </col:ArrayList> 12 </DataGridComboBoxColumn.ItemsSource> 13 </DataGridComboBoxColumn> 14 <DataGridComboBoxColumn SelectedItemBinding="{Binding Project}" Header="负责的项目" Width="100" ItemsSource="{Binding ProjectList}" DisplayMemberPath="{Binding ProjectName}"></DataGridComboBoxColumn> 15 <DataGridTemplateColumn Header="用户头像" Width="150"> 16 <DataGridTemplateColumn.CellTemplate> 17 <DataTemplate> 18 <Image Source="{Binding Avatar}" Stretch="UniformToFill" Height="150"></Image> 19 </DataTemplate> 20 </DataGridTemplateColumn.CellTemplate> 21 </DataGridTemplateColumn> 22 </DataGrid.Columns> 23 </DataGrid>
待更新
ComboBox
ComboBox是一个下拉列表控件,与ListBox控件类似,既可以显示地也可以隐式地创建ComboBoxItem对象的集合。
但是ComboBox一次只能选择一项。
如果需要在下拉框中进行输入,可以将ComboBox.IsEditable属性设置为true,
并且必须确保选项集合中存储的是普通的纯文本的ComboBoxItem对象,或是提供了有意义的ToString()表示的对象。
例如,如果将一个图像显示在ComboBox中,文本框显示的仅是这个类的命名。
当IsEditable属性设置为true时,还可以通过设置IsReadOnly属性,来控制一些状态,详细的组合情况如下:
IsReadonly为true | IsReadonly为false | |
IsEditable 为 true |
- 无法通过输入字符串来选择 中的 ComboBox 项。 - 无法输入与 中的 ComboBox项不对应的字符串。 - 可以在文本框中选择字符串的一 ComboBox 部分。 - 可以复制文本框中的 ComboBox 字符串,但不能将字符串粘贴到 ComboBox 文本框中。 |
- 可以通过输入字符串来选择 中的 ComboBox 项。 - 可以输入与 中的 ComboBox项不对应的字符串。 - 可以在文本框中选择字符串的一 ComboBox 部分。 - 可以在文本框中复制或粘贴字符串 ComboBox 。 |
IsEditable 为 false |
- 可以通过输入字符串来选择 ComboBox 项。 - 无法输入与 ComboBox项不对应的字符串。 - 无法选择 字符串的一 ComboBox部分。 - 无法在 ComboBox复制或粘贴字符串。 |
注意:只有在IsEditable为true时,IsReadonly属性才有效。
TreeView控件
TrieeView控件是WPF中的树型控件,树型控件在很多地方都有用到,最常见的就是资源管理器的树形结构显示。
TreeView也是继承自ItemsControl控件,但是它与ListBox不同的是,它的项容器控件TreeViewItem不是内容控件。每个TreeViewItem控件都是单独的ItemsControl控件,可以包含更多的TreeViewItem对象。
TreeViewItem类继承自HeaderedItemsControl类,而HeaderedItemsControl类又继承自ItemsControl类。
HeaderedItemsControl类添加Header属性,该属性用于TreeView中每个项显示的内容(通常为文本)。
跟ListBox控件类似,TreeView依旧可以通过添加项到集合或绑定的形式创建。
XAML添加
1 <TreeView> 2 <TreeViewItem Header="小湔"> 3 <TreeViewItem Header="1"/> 4 <TreeViewItem Header="80"/> 5 </TreeViewItem> 6 <TreeViewItem Header="立海大"> 7 <TreeViewItem Header="2"/> 8 <TreeViewItem Header="72"/> 9 </TreeViewItem> 10 </TreeView>
代码添加
1 TreeViewItem item1 = new TreeViewItem(); 2 item1.Header = "小湔"; 3 4 TreeViewItem item1SubItem1 = new TreeViewItem(); 5 item1SubItem1.Header = "1"; 6 TreeViewItem item1SubItem2 = new TreeViewItem(); 7 item1SubItem2.Header = "80"; 8 9 //每一个TreeViewItem都是一个ItemsControl控件 10 item1.Items.Add(item1SubItem1); 11 item1.Items.Add(item1SubItem2); 12 13 TreeViewItem item2 = new TreeViewItem(); 14 item2.Header = "立海大"; 15 16 TreeViewItem item2SubItem1 = new TreeViewItem(); 17 item2SubItem1.Header = "2"; 18 TreeViewItem item2SubItem2 = new TreeViewItem(); 19 item2SubItem2.Header = "80"; 20 21 item2.Items.Add(item2SubItem1); 22 item2.Items.Add(item2SubItem2); 23 24 this.tree2.Items.Add(item1); 25 this.tree2.Items.Add(item2);
运行效果如下
使用绑定
TreeView的绑定和ListBox的绑定稍有不同,因为TreeView要控制树的结构,所以需要使用到数据模板功能,这里暂时不做详细介绍,只需要知道数据绑定的存在即可,后面会单独介绍。
XAML
1 <TreeView Name="tree3" Grid.Column="2"> 2 <TreeView.ItemTemplate> 3 <HierarchicalDataTemplate ItemsSource="{Binding SubItems}" DataType="{x:Type local:Student}"> 4 <Label Content="{Binding Name}"/> 5 <HierarchicalDataTemplate.ItemTemplate> 6 <DataTemplate DataType="{x:Type local:SubItem}"> 7 <Label Content="{Binding Id}"></Label> 8 </DataTemplate> 9 </HierarchicalDataTemplate.ItemTemplate> 10 11 </HierarchicalDataTemplate> 12 </TreeView.ItemTemplate> 13 </TreeView>
Student.cs
1 public class Student 2 { 3 public string Name { get; set; } 4 5 public List<SubItem> SubItems { get; set; } 6 } 7 8 public class SubItem 9 { 10 public string Id { get; set; } 11 }
构建数据列表并绑定
1 var list = new List<Student>(); 2 3 Student student1 = new Student(); 4 student1.Name = "小湔"; 5 student1.SubItems = new List<SubItem>() { new SubItem() { Id = "1"} }; 6 7 Student student2 = new Student(); 8 student2.Name = "立海大"; 9 student2.SubItems = new List<SubItem>() { new SubItem() { Id = "2" } }; 10 11 list.Add(student1); 12 list.Add(student2); 13 14 this.tree3.ItemsSource = list;
说明:
如果需要绑定后动态更新集合数据,推荐使用ObservableCollection<T>类,并且实体类实现INotifyPropertyChanged接口,在属性值修改时进行通知。
运行效果如下:
常用属性
属性 |
说明 |
SelectedItem |
获取 TreeView 中的选定项。 |
SelectedValue |
获取由 SelectedValuePath 的 SelectedItem 指定的属性的值。 |
SelectedValuePath |
获取或设置用于获取 SelectedValue 中 SelectedItem 的 TreeView 的路径。 |
常用事件
事件 |
说明 |
SelectedItemChanged |
当 SelectedItem 更改时发生。 |
TreeViewItem常用属性
IsSelected |
获取或设置 TreeViewItem 控件是否处于选定状态。 |
IsExpanded |
获取或设置 TreeViewItem 中的嵌套项是处于展开状态还是折叠状态。 |
TreeViewItem常用事件
Expanede |
当 IsExpanded 属性从 |
Collapsed |
当 IsExpanded 属性从 |
Selected |
当 IsSelected 的 TreeViewItem 属性从 |
Unselected | 当 IsSelected 的 TreeViewItem 属性从 true 更改为 false 时发生。 |
手动展开树节点
通过TreeViewItem的IsExpanded属性,可以手动控制节点展开(true)/关闭(false)
实时创建节点
在日常使用时,因为TreeView控件是能够折叠的,所以可以省略不显示的信息,以便能降低开销以及填充树所需的时间。例如:要显示文件树时,只显示根目标,当点击展开时,再加载子目录。
当展开每个TreeViewItem对象时,会引发Expanded事件,此时我们可以填充节点。
当关闭每个TreeViewItem对象时,会引发Collapsed事件,此时我们可以丢弃节点或保留节点。
下面的示例代码展示了在树节点展开时,加载根目录
1 private void tree_Expanded(object sender, RoutedEventArgs e) 2 { 3 var diskPath = (e.OriginalSource as TreeViewItem).Header as DiskPath; 4 5 if (diskPath == null) 6 return; 7 8 foreach (var subDiskPath in diskPath.Children) 9 { 10 if (subDiskPath.Children == null) 11 { 12 subDiskPath.Children = new System.Collections.ObjectModel.ObservableCollection<DiskPath>(); 13 Kernel32.EnumerateSubDirectory(subDiskPath.Path, subDiskPath.Children, isEnumOnce: true); 14 } 15 } 16 }
显示性能
显示大量项可能会导致性能问题,可以参考以下链接提高TreeView的性能
示例代码
参考资料: