使用.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&amp;fm=253&amp;app=138&amp;size=w931&amp;n=0&amp;f=JPEG&amp;fmt=auto?sec=1717261200&amp;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),包括ListBoxComboBox以及TabControl。这些控件都继承自Selector类,

并且都具有跟踪当前选择项(SelectedItem)或其位置(SelectedIndex)的属性。

另一个分支,以不同方式选择列表项。该分支包括用于MenuToolbar以及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),此外,还定义了ItemTemplateItemContainerStyleSelectedBackground属性,他们分别用于数据模板、元素样式和选择时背景颜色的设置。

关于ComponentResourceKey,它的作用是定义和引用从外部程序集加载的资源的键。 这使资源查找能够在程序集中指定目标类型,而不是在程序集中或类上指定显式资源字典。ComponentResourceKey封装了两部分信息:拥有样式的类的类型和标识资源描述性的ResourceId字符串。

 

2、为视图定义样式

创建自定义控件后,Visual Studio会生成一个Themes文件夹,并在文件夹下生成一个Generic.xaml文件,WPF使用Generic.xaml文件关联到某个类的默认样式。

我们在Generic.xaml里为ListViewListViewItem定义样式

 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类型,然后设置TargetTypeListView/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支持以下数据源。

 

使用示例:

这里我直接在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 属性从 false 更改为 true 时发生。

Collapsed

当 IsExpanded 属性从 true 更改为 false 时发生。

Selected

当 IsSelected 的 TreeViewItem 属性从 false 更改为 true 时发生。

Unselected 当 IsSelected 的 TreeViewItem 属性从 true 更改为 false 时发生。

 

 

手动展开树节点

通过TreeViewItemIsExpanded属性,可以手动控制节点展开(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的性能

https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/how-to-improve-the-performance-of-a-treeview?view=netframeworkdesktop-4.8

 

示例代码

github

 

参考资料:

https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.controls.primitives.textboxbase?view=windowsdesktop-8.0

https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.controls.listview?view=windowsdesktop-8.0

https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/how-to-create-a-custom-view-mode-for-a-listview?view=netframeworkdesktop-4.8

posted @ 2024-05-27 15:53  zhaotianff  阅读(703)  评论(0编辑  收藏  举报