需求描述
需要使用一个类似表格的控件,数据格式如下:
数据名称 | 数据标记 | 数据值选择 |
DataA | true | Kea(可选的值:Kea,Lau,Nuh) |
DataB | true | Lau(可选的值:Kea,Lau,Nuh) |
DataC | true | invalid(可选的值:invalid,valid) |
DataD | false | invalid(可选的值:invalid,valid) |
DataE | true | 0(可选的值:0,1,2,3,4,5,6) |
DataF | true | 100(可选的值:0,20,40,60,80,100,120,140,160,180,200) |
DataG | true | 3(可选的值:0,1,2,3,4,5,6) |
需求分析
-
界面实现分析:
可以看出,我们的数据排列为:名称、标识和值。名称可以通过
TextBlock
之类的标签控件实现,标识可以使用CheckBox
或者ToggleButton
实现;最后的值典型的需要使用下拉列表框(ComboBox
、ListBox
等)。能够实现以上布局的有:①表格(
DataGrid
);②列表(ListBox
,ListView
,ItemControl
);③自定义控件布局(数据有限和确定的情况下可以考虑)。本次对前两种进行讨论。DataGrid
具备行、列的形式,因此很容易想到。每列我们分别使用DataGridTextColumn
、DataGridCheckBoxColumn
和DataGridComboBoxColumn
即可完成。列表典型的列结构,不伦我们使用哪种列表,都需要自定义一个数据模板,用以实现我们
-
数据结构分析:
我们数据其实非常有规律,可以将其结构定义如下:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Controls;
namespace Melphi.UiCore
{
/// <summary>
/// ListViewsView.xaml 的交互逻辑
/// </summary>
public partial class ListViewsView : UserControl
{
public ListViewsView()
{
InitializeComponent();
}
private ObservableCollection<MelphiDataItem> melphiDataSource = new ObservableCollection<MelphiDataItem>();
/// <summary>
/// 数据源
/// </summary>
public ObservableCollection<MelphiDataItem> MelphiDataSource
{
get
{
return melphiDataSource;
}
set
{
melphiDataSource = value;
}
}
}
/// <summary>
/// 数据项
/// </summary>
public class MelphiDataItem
{
/// <summary>
/// 数据名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 数据标识
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 设定值
/// </summary>
public string SelectedValue { get; set; }
/// <summary>
/// 设定值选定项集合
/// </summary>
public List<string> SelectionSource { get; set; }
}
}
稍做说明:每个数据项就是一个MelphiDataItem
的实例,使用变化自动通知界面的集合ObservableCollection
来存储所有的数据。
设计实现
-
初始化数据
一般情况下,我们的数据获取来源一般由文件、服务器或者数据库获得,为了方便,这里就直接在程序里面定义了。
public class MelphiDataService
{
/// <summary>
/// 模拟获取数据源
/// </summary>
/// <returns></returns>
public static ObservableCollection<MelphiDataItem> GetDataSource()
{
ObservableCollection<MelphiDataItem> melphiDatas = new ObservableCollection<MelphiDataItem>();
melphiDatas.Add(new MelphiDataItem()
{
Name = "DataA",
IsEnabled = true,
SelectedValue = "Kea",
SelectionSource = new List<string>() { "Kea", "Lau", "Nuh" }
});
melphiDatas.Add(new MelphiDataItem()
{
Name = "DataB",
IsEnabled = true,
SelectedValue = "Lau",
SelectionSource = new List<string>() { "Kea", "Lau", "Nuh" }
});
melphiDatas.Add(new MelphiDataItem()
{
Name = "DataC",
IsEnabled = true,
SelectedValue = "invalid",
SelectionSource = new List<string>() { "invalid", "valid" }
});
melphiDatas.Add(new MelphiDataItem()
{
Name = "DataD",
IsEnabled = true,
SelectedValue = "invalid",
SelectionSource = new List<string>() { "invalid", "valid" }
});
melphiDatas.Add(new MelphiDataItem()
{
Name = "DataE",
IsEnabled = false,
SelectedValue = "0",
SelectionSource = new List<string>() { "0", "1", "2", "3", "4", "5", "6" }
});
var listsource = new List<string>();
for (int i = 0; i <= 200; i+=20)
{
listsource.Add(i.ToString());
}
melphiDatas.Add(new MelphiDataItem()
{
Name = "DataF",
IsEnabled = true,
SelectedValue = "100",
SelectionSource = listsource
});
melphiDatas.Add(new MelphiDataItem()
{
Name = "DataG",
IsEnabled = true,
SelectedValue = "3",
SelectionSource = new List<string>() { "0", "1", "2", "3", "4", "5", "6" }
});
return melphiDatas;
}
}
在程序视图构造时获取数据,并将数据复制给我们界面绑定的数据源。
public partial class ListViewsView : UserControl
{
public ListViewsView()
{
InitializeComponent();
// 获取数据源
MelphiDataSource = MelphiDataService.GetDataSource();
}
private ObservableCollection<MelphiDataItem> melphiDataSource = new ObservableCollection<MelphiDataItem>();
/// <summary>
/// 数据源
/// </summary>
public ObservableCollection<MelphiDataItem> MelphiDataSource
{
get
{
return melphiDataSource;
}
set
{
melphiDataSource = value;
}
}
}
using System.Collections.ObjectModel;
using System.Windows.Controls;
namespace Melphi.UiCore
{
/// <summary>
/// DataGridsView.xaml 的交互逻辑
/// </summary>
public partial class DataGridsView : UserControl
{
public DataGridsView()
{
InitializeComponent();
// 获取数据源
MelphiDataSource = MelphiDataService.GetDataSource();
}
private ObservableCollection<MelphiDataItem> melphiDataSource = new ObservableCollection<MelphiDataItem>();
/// <summary>
/// 数据源
/// </summary>
public ObservableCollection<MelphiDataItem> MelphiDataSource
{
get
{
return melphiDataSource;
}
set
{
melphiDataSource = value;
}
}
}
}
-
界面设计以及数据绑定
- ListView
<UserControl x:Class="Melphi.UiCore.ListViewsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Melphi.UiCore"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<WrapPanel>
<StackPanel>
<!-- 标题 -->
<ListView HorizontalAlignment="Left"
BorderBrush="Black" BorderThickness="1" Margin="20 20 20 0" >
<ListViewItem>
<Border BorderBrush="Black" BorderThickness="1">
<Grid Width="500" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.5*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="Name"/>
<Border Grid.Column="1">
<TextBlock VerticalAlignment="Center" Text="IsEnabled"/>
</Border>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="Value"/>
</Grid>
</Border>
</ListViewItem>
</ListView>
<!-- 内容 -->
<ListView HorizontalAlignment="Left"
BorderBrush="Black" BorderThickness="1" Margin="20 0 20 20"
ItemsSource="{Binding Path=MelphiDataSource,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}">
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<Grid Width="500" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.5*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding Name}"/>
<Border Grid.Column="1">
<!-- 使用 ToggleButton 会让程序更好看,或者你重新定义控件模板来实现自己想要的效果 -->
<!--<ToggleButton IsChecked="{Binding IsEnabled}" HorizontalAlignment="Stretch"/>-->
<CheckBox IsChecked="{Binding IsEnabled}" HorizontalAlignment="Stretch"/>
</Border>
<ComboBox Grid.Column="2" SelectedItem="{Binding SelectedValue}" ItemsSource="{Binding SelectionSource}" TextBlock.TextAlignment="Center" HorizontalAlignment="Stretch" >
</ComboBox>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</WrapPanel>
</Grid>
</UserControl>
-
DataGrid
<UserControl x:Class="Melphi.UiCore.DataGridsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Melphi.UiCore"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<WrapPanel>
<DataGrid HorizontalAlignment="Left" Margin="10"
ItemsSource="{Binding Path=MelphiDataSource,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
RowDetailsVisibilityMode="VisibleWhenSelected"
AutoGenerateColumns="False">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border BorderThickness="0" Padding="5">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Tips: " VerticalAlignment="Center"/>
<TextBlock Text="{Binding Description}" VerticalAlignment="Center"
FontSize="15" FontWeight="Bold"/>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" />
<!-- 使用 DataGrid 提供的列 -->
<DataGridCheckBoxColumn Header="IsActive" Binding="{Binding Path=IsEnabled}"/>
<!-- 使用自定义的列模板 -->
<DataGridTemplateColumn Header="Temp">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton IsChecked="{Binding Path=IsEnabled,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- 使用 DataGrid 提供的列,修改其样式满足我们的显示需要 -->
<!-- 注意:直接对其进行数据绑定是没有效果,这是关键点 -->
<DataGridComboBoxColumn Header="Value">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=SelectionSource}" />
<Setter Property="SelectedValue" Value="{Binding Path=SelectedValue}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=SelectionSource}" />
<Setter Property="SelectedValue" Value="{Binding Path=SelectedValue}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</WrapPanel>
</Grid>
</UserControl>
注意:
1.使用DataGridComboBoxColumn
作为数据列时,需要修改DataGridComboBoxColumn
的EditingElementStyle
和ElementStyle
,将我们数据绑定到这两个样式上的ItemsSource
和SelectedValue
上。如果直接绑定到DataGridComboBoxColumn
的ItemsSource
和SelectedValue
上,我们的数据绑定是无效的。
2.DataGrid
控件提供的列中,有一个非常方便的列控件——DataGridTemplateColumn
。这个模板列控件可以按照我们的界面需求进行自定义界面样式和数据绑定。值得留意的是:DataGrid
提供的默认列,如DataGridTextColumn
、DataGridCheckBoxColumn
、DataGridComboBoxColumn
以及DataGridHyperlinkColumn
在做数据绑定时,默认情况下数据绑定是双向更新的(数据模型<-->界面);但是DataGridTemplateColumn
的默认绑定是单向的(数据模型-->界面),如果需要双向时,需要对绑定的数据 指定UpdateSourceTrigger
,如下所示,而列表类的控件就没有这个细节。
<!-- 使用自定义的列模板 -->
<DataGridTemplateColumn Header="IsTemplate">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton IsChecked="{Binding Path=IsEnabled,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
-
运行调试
总结
虽然完成之后觉得很简单没什么,但是我在最开始拿到需求到真正实现的过程中走了不少的弯路,从早上上班到下午3点才算解决。
现在回头,觉得自己之所以走弯路,主要的问题出现在对控件的不熟悉,其中DataGrid
的DataGridComboBoxColumn
数据绑定不成功耗费最多。其次是对自己定义的数据结构不信任,导致对数据结构的多次更换、重新实验。
还是列表控件拯救了我,相比DataGrid
控件,自己对列表控件认识要更深一些(也许是他更简单一些吧),让我最终坚信我的数据结构没问题,"简单轻松"得完成了需求。
但是DataGrid
控件确实是一个非常好的控件。我们可以看到他具备一些普通的列模板,虽然不能完全满足我们的需求。但是他提供了DataGridTemplateColumn
列模板,我们可以根据我们的界面和数据需求进行自制模板。是不是很nice。不仅如此,该控件还提供了很多额外的功能,一般的编辑功能就不说了,还有选中提示功能如RowDetailsTemplate
中定义了我们点击某一行时,的提示模板......总之,非常强,早学早轻松。
最后再说一点,在程序开发时,我们需要分析,尤其是对数据结构的分析,慢慢地你会得到极大提升。