WPF 进阶 列表,树和网络

37-44  3.66h

ListViewoListView继承自简单的没有特色的ListBox。增加了对基于列显示的支持,并

增加了快速切换视图或显示模式的能力,而不需要重新绑定数据以及重新构建列表。
TreeView。TreeView是层次化容器,这意味可创建多层数据显示。例如,可创建在第一
级中显示类别组,并在每个类别节点中显示相关产品的TreeView控件。
DataGrid。DataGnd是WPF中功能最完备的数据显示工具。它将数据分割到包含行和列
的网格中,就像ListView控件,但DataGnd控件具有其他格式化特性(如冻结列以及设置
单行样式的能力),并且支持就地编辑数据。

 

 

LIstView控件

GridView和GridViewColumn都提供了一些有用的方法,可使用这些方法定制列表的显示
外观。为创建最简单、最直观的列表(很像Windows资源管理器中的详细信息视图),只需要为
每个GridViewColumn对象设置两个属性:Header和DisplayMemberBindingoHeader属性提供
放在列顶部的文本,而属性包含一个绑定,该绑定从每个数据项提取希
望显示的信息。

<Window x:Class="DataBinding.BasicListView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DataBinding"
    Title="BasicListView" Height="370" Width="554"
    >

  <Grid>
      <ListView Margin="5" Name="lstProducts">
        <ListView.View>
          <GridView>
            <GridView.Columns>              
              <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=ModelName}" />
              <GridViewColumn Header="Model" DisplayMemberBinding="{Binding Path=ModelNumber}" />
                        <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=UnitCost,StringFormat={}{0:C}}" />
              
            </GridView.Columns>
          </GridView>
        </ListView.View>
      </ListView>
    </Grid>
</Window>
BasicListView.xaml
namespace DataBinding
{
    /// <summary>
    /// Interaction logic for BasicListView.xaml
    /// </summary>

    public partial class BasicListView : System.Windows.Window
    {
            

        public BasicListView()
        {
            InitializeComponent();                    
            
            lstProducts.ItemsSource = App.StoreDb.GetProducts();         
        }

    }
}
BasicListView.xaml.cs

单元格模板

为在单元格中显示数据,GridViewColumn.DisplayMemberBinding属性不是唯一的选择。另一
个选择是使用CellTemplate属性,该属性使用数据模板。除了只能应用于一列之外,它与在前面学过的数据模板十分相似。如果很有耐心的话,也可为每一列都提供数据模板。

<Window x:Class="DataBinding.AdvancedListView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DataBinding"
    Title="AdvancedListView" Height="600" Width="600"
    >
  <Window.Resources>    
    <local:ImagePathConverter x:Key="ImagePathConverter"></local:ImagePathConverter>
    <local:PriceToBackgroundConverter x:Key="PriceToBackgroundConverter" HighlightBrush="PaleGoldenrod" MinPrice="100"></local:PriceToBackgroundConverter>   
    
  </Window.Resources>

  <Grid>
    <ListView Margin="5" Name="lstProducts" GridViewColumnHeader.Click="gridViewColumn_Click">      
      <!--<ListView.ItemContainerStyle>
        <Style>
          <Setter Property="ListViewItem.Background" Value="{Binding Converter={StaticResource PriceToBackgroundConverter}}" />
        </Style>
      </ListView.ItemContainerStyle>-->
      <ListView.View>        
        <GridView>          
          <GridView.Columns>
            <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=ModelName}" >              
            </GridViewColumn>
            <GridViewColumn Header="Model" DisplayMemberBinding="{Binding Path=ModelNumber}" />
            <GridViewColumn Header="Description" Width="300">
              <GridViewColumn.CellTemplate>
                <DataTemplate>
                  <TextBlock Text="{Binding Path=Description}" TextWrapping="Wrap"></TextBlock>
                </DataTemplate>
              </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Picture" >
              <GridViewColumn.CellTemplate>
                <DataTemplate>
                  <Image Source="{Binding Path=ProductImagePath, Converter={StaticResource ImagePathConverter}}"></Image>
                </DataTemplate>
              </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=UnitCost, StringFormat={}{0:C} }" >              
            </GridViewColumn>
          </GridView.Columns>
        </GridView>
      </ListView.View>
    </ListView>
  </Grid>
</Window>
View Code

 

 

 创建自定义视图

视图类

视图样式

使用ListView控件

为视图传递信息

TreeVIew控件

<TreeView>
    <TreeViewItem>
            <TreeViewItem Header="1"/>
            <TreeViewItem Header="1"/>
            <TreeViewItem Header="1"/>
    </TreeViewItem>
    <TreeViewItem>
            <TreeViewItem Header="1"/>
            <TreeViewItem  Header="1"/>
            <TreeViewItem  Header="1"/>
    </TreeViewItem>
</TreeView>    
非常基本的TreeVIew骨架

不见得非要使用TreeViewItem对象构造TreeView控件。实际上,几乎可为TreeView控件
添加任何元素,包括按钮、面板以及甲像。然而,如果希望显示非文本内容,最好使用
TreeViewItem封装器,并通过TreeViewItem.Header属性提供内容。虽然这与直接为TreeView
控件添加非TreeV1ewItem元素得到的效果相同,但这样做将更容易管理一些特定于TreeVrew控件的细节,例如选择和展开节点。如果希望显示非UIElement对象,可使用具有HeaderTemplate或HeaderTemplateSelector属性的数据模板设置其格式。

将DataSet绑定到TreeView

使用数据填充TreeView控件非常简单一一与任意ItemsControl控件一样,只需要设置ItemsSource属性。然而,这种技术只能填充TreeView控件的第一层。使用TreeView趣的方法控件更有是包含具有某种嵌套结构的层次化数据。

namespace StoreDatabase
{
    public class Product : INotifyPropertyChanged
    {
        private string modelNumber;
        public string ModelNumber
        {
            get { return modelNumber; }
            set {
                modelNumber = value;
                OnPropertyChanged(new PropertyChangedEventArgs("ModelNumber"));
            }
        }              

        private string modelName;
        public string ModelName
        {
            get { return modelName; }
            set {
                modelName = value;
                OnPropertyChanged(new PropertyChangedEventArgs("ModelName"));
            }
        }

        private decimal unitCost;
        public decimal UnitCost
        {
            get { return unitCost; }
            set { unitCost = value;
                OnPropertyChanged(new PropertyChangedEventArgs("UnitCost"));
            }
        }

        private string description;
        public string Description
        {
            get { return description; }
            set { description = value;
                OnPropertyChanged(new PropertyChangedEventArgs("Description"));
            }
        }

        private string categoryName;
        public string CategoryName
        {
            get { return categoryName; }
            set { categoryName = value; }
        }

        // For DataGridComboBoxColumn example.
        private int categoryID;
        public int CategoryID
        {
            get { return categoryID; }
            set { categoryID = value; }
        }

        private string productImagePath;
        public string ProductImagePath
        {
            get { return productImagePath; }
            set { productImagePath = value; }
        }        

        public Product(string modelNumber, string modelName,
            decimal unitCost, string description)
        {
            ModelNumber = modelNumber;
            ModelName = modelName;
            UnitCost = unitCost;
            Description = description;
        }

        public Product(string modelNumber, string modelName,
           decimal unitCost, string description, 
           string productImagePath)
            :
           this(modelNumber, modelName, unitCost, description)
        {            
            ProductImagePath = productImagePath;            
        }

        public Product(string modelNumber, string modelName,
            decimal unitCost, string description, int categoryID,
            string categoryName, string productImagePath) : 
            this(modelNumber, modelName, unitCost, description)
        {
            CategoryName = categoryName;
            ProductImagePath = productImagePath;
            CategoryID = categoryID;
        }

        public override string ToString()
        {
            return ModelName + " (" + ModelNumber + ")";
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }

        // This for testing date editing. The value isn't actually stored in the database.
        private DateTime dateAdded = DateTime.Today;
        public DateTime DateAdded
        {
            get { return dateAdded; }
            set { dateAdded = value; }
        }

    }
}
Product.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace StoreDatabase
{
    public class Category : INotifyPropertyChanged
    {
        private string categoryName;
        public string CategoryName
        {
            get { return categoryName; }
            set
            {
                categoryName = value;
                OnPropertyChanged(new PropertyChangedEventArgs("CategoryName"));
            }
        }

        // For DataGridComboBoxColumn example.
        private int categoryID;
        public int CategoryID
        {
            get { return categoryID; }
            set {
                categoryID = value;
                OnPropertyChanged(new PropertyChangedEventArgs("CategoryID"));
            }
        }

        //这个技巧一一创建通过属性提供另一个集合的集合一一是使用WPF数据绑定导航父一子关
        //系的秘密所在。例如,可将Category对象集合绑定到一个列表控件,然后将另一个列表控件绑
        //定到当前选中的category对象的products属性,从而显示相关联的Product对象·
        private ObservableCollection<Product> products;
        public ObservableCollection<Product> Products
        {
            get { return products; }
            set
            {
                products = value;
                OnPropertyChanged(new PropertyChangedEventArgs("Products"));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }

        public Category(string categoryName, ObservableCollection<Product> products)
        {
            CategoryName = categoryName;
            Products = products;
        }

        public Category(string categoryName, int categoryID)
        {
            CategoryName = categoryName;
            CategoryID = categoryID;            
        }

    }

}
Category.cs
namespace DataBinding
{
    /// <summary>
    /// Interaction logic for BoundTreeView.xaml
    /// </summary>

    public partial class BoundTreeView : System.Windows.Window
    {

        public BoundTreeView()
        {
            InitializeComponent();

            //然后将category集合绑定到树控件,使树控件显示第一层数据:
            treeCategories.ItemsSource = App.StoreDb.GetCategoriesAndProducts();
        }

    }
}
绑定
<Window x:Class="DataBinding.BoundTreeView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="BoundTreeView" Height="300" Width="300"
    xmlns:data="clr-namespace:StoreDatabase;assembly=StoreDatabase"
    >
  <Window.Resources>
        <!--雕一不寻常的细节是使用HierarchicalDataTemplate对象设置TreeView.ItemTemplate属性,
而不是使用DataTemplate对象。HierarchicalDataTemplate对象具有一个额外优点,就是能够
封装第二个模板。然后HierarchicalDataTemplate对象就可以从第一层数据中提取项的集合,
并将之提供给第二层的模板。可简单地设置ItemsSource属性,指示该属性具有子项;并设置
ItemTemplate属性,指示如何设置每个对象的格式。
-->
        <HierarchicalDataTemplate DataType="{x:Type data:Category}"
                                ItemsSource="{Binding Path=Products}">
      <TextBlock Text="{Binding Path=CategoryName}"/>
    </HierarchicalDataTemplate>
      
    <HierarchicalDataTemplate DataType="{x:Type data:Product}"      >
      <TextBlock Text="{Binding Path=ModelName}" />      
    </HierarchicalDataTemplate>
    
  </Window.Resources>
  <Grid>
        <!--在这个示例中,TreeView控件没有显式地设置它的ItemTemplate属性,而是根据绑定对象
        的数据类型使用恰当的数据模板。同样,Category模板也没有指定将用于处理Products
        集合的ItemTemplate,也是通过数据类型自动选择的。现在,这棵树可显示产品列表或包含产
        品组的目录列表。
        -->
    <TreeView Name="treeCategories" Margin="5">
    </TreeView>
  </Grid>
</Window>
BoundTreeView.xaml

将DataSet对象绑定到TreeView控件 

即时创建节点

TreeView控件经鼍用于包含人量数掘,这是因为TreeView控件的显示是能够折叠的。即使吏用户从顶部滚动到底部,也不需要显示全部信息。完全可在TreeView控件中省略不显示的信息,以便降低开销(以及填充树所需的时间)。甚至更好的是,当展开每个TreeViewItem对象时会引发Expanded事件,并且当关闭时会引发Collapsed事件。可通过处理这两个事件即时填充丢失的节点或丢弃不再需要的节点。这种技术被称为即时创建节点。 

DataGrid控件

为创建暂且应急的DataGrid控件,可使用自动列生成功能。为此,需要将AutoGenerate-
Columns属性设置为true(这是默认值):

对于自动列生成,DataGrid控件使用反射查找绑定对象中的每个公有属性,并为每个属性
创建一列。

使动生成的列,可快速创建显示所有数据的DataGnd控件,但放弃了一些控制能力。
例如,不能控制列的顺序、每列的宽度、如何格式化列中的值以及应该放在顶部的标题文本的
内容。

为显示非字符串属性,DataGnd控件调用ToString()方法。对于数字、日期以及其他简单
类型,该方法效果不错。但如果对象包含更复杂的数据对象,该方法就行不通了(对于这种情况,可能希望明确地定义列,从而获得绑定到了属性、使用值转换器或应用模板以获取正确显示内容的机会)

DataGrid控件的基本显示属性:

AlternatingRowBackground
ColumnHeaderHeight
RowHeaderWidth
ColumnWidth
RowHcight
GndLinesVisibillity
VerticalGridLinesBrash
HonzontalGridLinesbrush
HeadersVisibility
HorimntalScrollBarVisibillity
verticalScrollBarViMbillty

改变列的尺寸和重新安排列

如果选用确切的尺寸,简单地在XAML中将ColumnWidth属性设置为合适的数字,
或(在代码中)当创建DataGrid对象时作为构造函数的参数提供数字.
grid.COlumnWidth=new DataGridLength(150);
下面是使用默认的DataGridLengthSizeToHeader尺寸改变模式的示例:
grid.C01umnWidth=DataGridLength.SizeToHeader
另一个常见选项是DataGridLength.SizeToCells,该模式加宽每一列以适应当前视图中最宽
的值。当用户开始滚动数据时,DataGnd控件试图保持这个智能的尺寸改变方法。一旦进入具
有更长数据的行,DataGnd控件就会加宽合话的列以适应该行·自动改銮尺寸仅是单向的,所
以当离开大的数据时不会收缩列。
另一个专门的尺寸改变模式选择是DataGridLength.Auto,该模式的工作方式和DataGrid-
LengthSizeToCeIIs模式类似,除了加宽每列以适应最大的显示值或列题头文本一一一使用其中较大的值。
DataGrid控件还可使用按比例改变尺寸的系统,与在Grid布局面板中使用的星号式类似。通常,*表示按比例改变尺寸,并且可通过添加数字来使用选择的比例分割可用的空间

定义列

更大的方法是将AutoGenerateCoIumns属性设置为false以关闭自动列生成功能“然后可
使用希的设置和指定的顺序,用合适的列对象填充DataGridColumns集合。

目前,DataGrid控件支持几种类型的列,通过继承自DataGndCoIumn的不同类表示这些列:
DataGridTextCoIumn。这种列对于大部分数据类型是标准选择。值被转换为文本,并在
元索中显示。当编辑行时,TextBIock元索被替换为标准的文本框
DataGridCheckBoxCoIumn。这种列显示复选框。为Ble呵或可空BooIean)值自动使
用这种列类型。通常,复选框是只读的;但当编辑行时,会变成普通的复选框。
DataGridHyperlinkColumn·这种列显示可单击的链接。如果结合使用WPF中的导航容
器,如Frame或NavrgationWmdow,可允许用户导航到其他URI(通常是外部web站点)。
DataGridComboBox 最初这种列看起来与DataGridTextCoIumn类似,但在编辑模式下
这种列会变成下拉的ComboBox控件。当希望将编辑限制于允许的少部分值时,这种列
是很好的选择。
DataGridTemplateColumn。这种列是到目前为止功能最强大的选择。这种列允许为显
示列值定义数据模板,具有在列表控件中使用模板时所具有的所有灵活性和功能。例如,

可使用DataGndTemplateCoIumn显示图像数据或使用专门的WPF控件(如具有合法值的
下拉列表或用于日期值的DataP1cker控件)。

与简单的列表控件(如ListBox和ComboBox)不同。这些控件包括DisplayMemberPath属性而不是Binding属性。Bind1ng方法更灵活、一一允许使用字符串格式化和值转换器

设置列的格式和样式:

设置行的格式

对于设置行格式,LoadingRow事件是个非常弓虽大的工具。它提供了对当前行数据对象的访
问,允许丌发人员执行简单的范围检查、比较以及更复杂的操作。它还提供了行的DataGndRow对象,允许开发人员使用不同的颜色或不同的字体设置行的格式。
然而,不能只设置行中单个单元格的格式一一为达到那样的目的,需要使用DataGridTemplateColumn和自定义的值转换器。
当每一行出现在屏幕上时,就会立即为该行引发LoadingRow事件。
还有一个考虑事项:項容器再循环。为降低内存丌销,当在数据中滚动时,DataGrid控件
为显示新数据而重用相同的DataGndRow对象(这也是为什么将该事件称为LoadmgRow而不是
CreatingRow的原因如有不慎,DataGrid控件能够将数据加载到己经格式化了的DataGridRow
对象中·为了防止发生这种情况,必须明确地将每行恢复到其初始状态。

        <DataGrid x:Name="gridProducts" Margin="5" AutoGenerateColumns="False" RowHeight="100" LoadingRow="gridProducts_LoadingRow"
                  FrozenColumnCount="1">
 // Reuse brush objects for efficiency in large data displays.
        private SolidColorBrush highlightBrush = new SolidColorBrush(Colors.Orange);
        private SolidColorBrush normalBrush = new SolidColorBrush(Colors.White);

        private void gridProducts_LoadingRow(object sender, DataGridRowEventArgs e)
        {
            Product product = (Product)e.Row.DataContext;
            if (product.UnitCost > 100)
                e.Row.Background = highlightBrush;
            else
                e.Row.Background = normalBrush;

        }
View Code

 

 

 显示行的细节

DataGnd控件还支持行细节(rowdells)一一一一块可选的独立显示区域,在行的列值的下面
显示。行细节区域添加了无法仅使用列实现的两个特征:
*能够跨越DataGrid控件的整个宽度,并且不会切入到独立的列中,从而提供了更多可供
使用的空间。
*可配置行细节区域,从而只为选择的行显示该区域,当不需要时允许用户折叠额外的
细节。

<Grid>
        <DataGrid x:Name="gridProducts" Margin="5" AutoGenerateColumns="False"
        RowDetailsVisibilityMode="VisibleWhenSelected">
            <DataGrid.RowDetailsTemplate>
                <DataTemplate>
                    
                        <Border Margin="10" Padding="10" BorderBrush="SteelBlue" BorderThickness="3" CornerRadius="5">
                            <TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="10" MaxWidth="300" TextAlignment="Left"></TextBlock>
                        </Border>
                    
                </DataTemplate>
            </DataGrid.RowDetailsTemplate>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Product" Width="175" Binding="{Binding ModelName}"></DataGridTextColumn>
                <DataGridTextColumn Header="Price" Binding="{Binding UnitCost, StringFormat={}{0:C}}"></DataGridTextColumn>
                <DataGridTextColumn Header="Model Number" Binding="{Binding ModelNumber}"></DataGridTextColumn>


            </DataGrid.Columns>
        </DataGrid>
View Code

 

 

冻结列

冻结的列位于DataGnd控件的左边,甚至当向右滚动时冻结的列仍然位于左边。

选择

排序

DataGrid编辑

DaGnd控件的最方便之处在于支持编辑。当用户双击DataGrid单元格时,该单元格会切
换到编辑模式。但BataGrid控件以几种方式限制这种编辑功能:
*DataGrid」sReadOnIyo当该属性为e时,用户不能编辑任何内容。
*DataGridColumn.lsReadOnlyo当该属性为e时,用户不能编辑该列中的任意值。
*只读属性·如果数据对象具有没有属性设置器的属性,DataGrid控件将足够智能,它能

当单元格切换到编辑模式时发生的变化取决于列的类型。

DataGrid控件自动支持19章中学习过的相同的基本验证系统,该系统响应在数据绑
定系统中的问题(例如不能将提供的文本转换为畲适的数据类型)以及由属性设置器抛出的异
常。

可通过其他几种方法为DataGnd控件实现验证。一种选择是使用DG控件的编辑事件,

BeginningEdit  :当单元格正进入编辑模式时发生。可检查当前编辑的列和行,检查单元格的值,并且可以使用DataGndBegmmngEditEvens.Cancel属性取消操作
PreparingCeIIForEdit:用于模板列。这时,可为编辑控件执行所有最后的初始化操作,可使用
DataGringCellForEditEventArgs.EditmgEIement访问CellEditinTemlate中的元素
CellEditEnding:
当单元格正退出编辑模式时发生DataGridCellEditEndingEventsArgs.EditActton指示用户
是试图接受编辑(例如,通过按下Enter键或单击另一个单元格),还是取消编辑(通过按
下Escae键可检查新数据并设置Cancel属性以回滚修改

RowEditEnding:当用户在编辑完当前行之后导航到新行时发生。与CellEditEndtng事件一样,可使用该事件执行验证并取消修改。通常,将在此执行涉及几列的验证.一一例如,确保某列中的值大于另一列中的值

posted @ 2021-03-02 17:05  KnowledgePorter  阅读(693)  评论(0)    收藏  举报