我要做回自己--Aaronyang的博客(www.ayjs.net)
博客摘要:
- 全方位的讲解了转换器的使用,单值,多值转换器,条件转换器,StringFormat等方式
- 详细的实践地讲解了ItemsControl中的知识
一:ItemsSource,DisplayMemberPath,ItemStringFormat,ItemContainerStyle
二:ItemContainerStyle下修改显示模板
三:AlternationCount例讲
四:StyleSelector样式选择器
五:DataTemplateSelector模板选择器,以及数据模板深入
六:ItemsPanel
七:细节分析
- ComboBox的简单原理套用与讲解,以及自动搜索TextSearch.TextPath等
新建一个WPF项目TemplateDemo
1. binding的数据二次处理方式
1.1 自带StringFormat,只简单列举几个
写DEMO创建窗口时候,默认Grid,改成Canvas做演示最好了
<Window x:Class="TemplateDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TemplateDemo" Title="MainWindow" Height="700" Width="1200"> <Window.Resources> <ObjectDataProvider x:Key="nowTime" MethodName="GetDate" IsAsynchronous="True" ObjectType="{x:Type local:DataDemo}"/> </Window.Resources> <Canvas> <Label>时间:{}{0:时间格式}</Label> <TextBox Height="23" Canvas.Left="137" TextWrapping="Wrap" Text="{Binding Source={StaticResource nowTime},Path=MyDateTime,StringFormat={}{0:yyyy年MM月dd日}}" Canvas.Top="4" Width="120"/> </Canvas> </Window>
后台,本课最基本的样子了:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace TemplateDemo { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } public class DataDemo { public DataObject GetDate() { DataObject d = new DataObject(); d.MyDateTime=DateTime.Now; return d; } } public class DataObject { public DateTime MyDateTime { get; set; } } }
运行效果:文本框显示当前日期 2015年1月27日
1.2 金额
public class DataDemo { public DataObject GetDate() { DataObject d = new DataObject(); d.MyDateTime=DateTime.Now; d.MyMoney = 520.58M; return d; } } public class DataObject { public DateTime MyDateTime { get; set; } public decimal MyMoney { get; set; } }
<TextBox Height="23" Canvas.Left="137" TextWrapping="Wrap" Text="{Binding Source={StaticResource nowTime},Path=MyMoney,StringFormat=我的Money:{0:C}}" Canvas.Top="24" Width="163"/>
1.3其他
{0:P} 百分数,{0:E}科学计数法 {0:F?},例如{0:F0}就是不保留小数,F1就是保留1位
当例如 {0:P}位于StringFormat的开头字符需要使用{}转义,所以你看到时间是{}{0:yyyy年} 关于时间常见 yyyyMMddHHmmssaa 其中aa是AM或者PM
1.4值转换器 以前写过文章使用用法,挺详细: 点击查看
在三巴掌系列里面,前面写过了简单的转换器了,当然还有条件转换器,在转换器中加入几个属性,声明资源时候可以赋值。
补充说明:
多值转换器:继承IMultiValueConverter,本来第一个object value是一个值得,现在是object[]了,那么前台怎么传递的
我们继续现在这个例子,MyDate代表消费日期,MyMoney代表消费金额吧,展示个数据
第一种普通方式:
<Label Canvas.Top="78" Content="多值转换器:"/> <TextBlock Canvas.Top="83" Canvas.Left="87"> <TextBlock.Text> <MultiBinding StringFormat="{}{0:yyyy年MM月dd日},{1:C}"> <Binding Path="MyDateTime" Source="{StaticResource nowTime}"></Binding> <Binding Path="MyMoney" Source="{StaticResource nowTime}"></Binding> </MultiBinding> </TextBlock.Text>
效果:
假如我现在还想知道星期几消费的怎么办,或者更复杂的,关联到保密表,表示这个消费是否是加密的,怎么办?那就需要转换器了
现在我们简单做个demo,如果消费金额大于500与就不显示金额,只显示保密。
新增一个SpendHistory.cs类,实现IMultiValueConverter
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Data; namespace TemplateDemo { public class SpendHistory:IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var d1 =values[0]; string d1string = ""; if (d1 is DateTime) { d1string = ((DateTime)d1).ToString("yyyy年MM月dd日:"); } decimal d = (decimal)values[1]; if (d > 500) { return d1string + "保密"; } else { return d1string +d.ToString("0:C"); } } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } }
OK,补充说明下面的ConvertBack一般当你的绑定是TwoWay的,或者OneWayToSource就需要需实现了
在运行项目前,你需要将顶部声明的ObjectDataProvider的IsAsynchronous改成false,否则下面的绑定会报错。关于ConverterParameter的转换器参数不讲了,通过bingding指定ConverterParameter后,就可以再转换器的parameter参数中取得该值
整体代码如下:
<Window x:Class="TemplateDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:TemplateDemo" Title="MainWindow" Height="700" Width="1200"> <Window.Resources> <ObjectDataProvider x:Key="nowTime" MethodName="GetDate" IsAsynchronous="False" ObjectType="{x:Type local:DataDemo}"/> <local:SpendHistory x:Key="spendConvertor"></local:SpendHistory> </Window.Resources> <Canvas> <Label>时间:{}{0:时间格式}</Label> <TextBox Height="23" Canvas.Left="137" TextWrapping="Wrap" Text="{Binding Source={StaticResource nowTime},Path=MyDateTime,StringFormat={}{0:yyyy年MM月dd日}}" Canvas.Top="4" Width="163"/> <Label Canvas.Top="22">货币:{0:C}</Label> <TextBox Height="23" Canvas.Left="137" TextWrapping="Wrap" Text="{Binding Source={StaticResource nowTime},Path=MyMoney,StringFormat=我的Money:{0:C}}" Canvas.Top="24" Width="163"/> <Label Canvas.Top="48">1位小数:{0:F1}</Label> <TextBox Height="23" Canvas.Left="137" TextWrapping="Wrap" Text="{Binding MyMoney, Source={StaticResource nowTime}, StringFormat=我的1位小数:{0:F1}}" Canvas.Top="52" Width="163"/> <Label Canvas.Top="78" Content="多值转换器1:"/> <TextBlock Canvas.Top="83" Canvas.Left="87"> <TextBlock.Text> <MultiBinding StringFormat="{}{0:yyyy年MM月dd日},{1:C}"> <Binding Path="MyDateTime" Source="{StaticResource nowTime}"></Binding> <Binding Path="MyMoney" Source="{StaticResource nowTime}"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> <Label Canvas.Top="98" Content="多值转换器2:"/> <TextBlock Canvas.Top="103" Canvas.Left="87" x:Name="tbtest"> <TextBlock.Text> <MultiBinding Converter="{StaticResource spendConvertor}" > <Binding Path="MyDateTime" Source="{StaticResource nowTime}"></Binding> <Binding Path="MyMoney" Source="{StaticResource nowTime}"></Binding> </MultiBinding> </TextBlock.Text> </TextBlock> </Canvas> </Window>
展示效果:
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
接下来比较复杂的 ItemsControl
在WPF中有很多集合数据源的控件,重复的显示每一项,我们可以控制每一项(Item)的显示样式和模板,然后样式选择器ItemContainerStyleSelector,模板选择器ItemTemplateSelector,接着每一项重复几次AlternationCount,一个数据的来源ItemsSouce,所有项外面都有层Panel,这个Panel使用哪种,怎么控制,就操作ItemPanel,像Grid那种有的可以分组,所以每组样式怎么控制,就有了GroupStyle,当然组不止一个,为了让组显示不同的样式,所以又有了GroupStyleSelector。同理还有简单的指定DisplayMemberPath,ItemStringFormat等
关于Selector类,就拥有了 SelectedItem(选中的数据对象),SelectedIndex(选中项索引),SelectedValue(选中对象的Value属性,通过SelectedValuePath指定),ListBox的多选是Listbox类自己提供的,SelectionMode和SelectedItems。
1. ItemsSource,DisplayMemberPath,ItemStringFormat,ItemContainerStyle
其实不太想偷懒方式修改模板或者样式(blend),在vs中也可以简单使用,使用文档大纲
生成代码:
<ListBox x:Name="lbDemo1" Height="100" Canvas.Left="377.868" Canvas.Top="10" Width="376.337" ItemContainerStyle="{DynamicResource ListBoxItemStyle1}"> <ListBox.Resources> <Style x:Key="ListBoxItemStyle1" TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Grid/> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListBox.Resources> </ListBox>
默认是以资源方式,嵌入进来的,下面的定位为止,如果是window窗口,就是Window.Resources中定义了,其实这样挺冗余的,复杂效果的时候用resource方式,简单的话,就直接Listbox点出来吧
先创建一批假数据吧,方便测试
public class DataDemo { public DataObject GetDate() { DataObject d = new DataObject(); d.MyDateTime = DateTime.Now; d.MyMoney = 520.58M; List<Blog> blog = new List<Blog> { new Blog{BlogName="wpf文章1",BlogContent="wpf内容1"}, new Blog{BlogName="wpf文章2",BlogContent="wpf内容2"}, new Blog{BlogName="wpf文章3",BlogContent="wpf内容3"}, new Blog{BlogName="wpf文章4",BlogContent="wpf内容4"}, new Blog{BlogName="wpf文章5",BlogContent="wpf内容5"}, new Blog{BlogName="wpf文章6",BlogContent="wpf内容6"}, new Blog{BlogName="wpf文章7",BlogContent="wpf内容7"}, new Blog{BlogName="wpf文章8",BlogContent="wpf内容8"}, new Blog{BlogName="wpf文章9",BlogContent="wpf内容9"}, }; d.MyBlogs = blog; return d; } } public class DataObject { public DateTime MyDateTime { get; set; } public decimal MyMoney { get; set; } public List<Blog> MyBlogs { get; set; } } public class Blog { public string BlogName { get; set; } public string BlogContent { get; set; } }
前台先手动写代码吧,增加熟练度:
<ListBox x:Name="lbDemo1" Height="100" Canvas.Left="377.868" Canvas.Top="10" Width="376.337" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" DisplayMemberPath="BlogName" ItemStringFormat="Aaronyang-《{0}》"> <ListBox.ItemContainerStyle> <Style> </Style> </ListBox.ItemContainerStyle> </ListBox>
简单使用了ItemsSource,DisplayMemberPath,ItemStringFormat,知道原理,就很容易使用了。指定子项的数据,每一项显示哪个字段,每项怎么格式化
效果图:
接下来稍微改变每项的样式
<ListBox.ItemContainerStyle> <Style> <Setter Property="ListBoxItem.Background" Value="#A5E0F1"></Setter> <Style.Triggers> <Trigger Property="ListBoxItem.IsSelected" Value="True"> <Setter Property="ListBoxItem.Background" Value="#44BCDF"></Setter> <Setter Property="ListBoxItem.Foreground" Value="#fff"></Setter> </Trigger> </Style.Triggers> </Style> </ListBox.ItemContainerStyle>
效果图:
选中后,字体变白,背景其他颜色,这种效果被一一重复的影响到每一项Item,更复杂的效果当然就要看你的Style怎么写了
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
2.ItemContainerStyle下修改显示模板,所以ControlTemplate属性了
接下来,我们在每一项前面加上一个checkbox,然后文章后面加上 下载 的超链接,在listbox上方加上,下载的按钮,我们在Style中修改Template属性,注意这里的Style要加上 TargetType,不然Template属性出不来。
我们设置SelectionMode=Muliple多选
<Button x:Name="downBlog" Width="66" Height="18" Content="下载" Canvas.Left="378" Canvas.Top="22" Click="downBlog_Click"/> <ListBox x:Name="lbDemo1" Height="100" Canvas.Left="378" Canvas.Top="42" Width="376" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" DisplayMemberPath="BlogName" SelectionMode="Multiple"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="ListBoxItem.Background" Value="#A5E0F1"></Setter> <Setter Property="Margin" Value="2"></Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <CheckBox Focusable="False" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsSelected,Mode=TwoWay}"> <StackPanel Orientation="Horizontal"> <ContentPresenter Content="{Binding Path=BlogName}"/> <TextBlock Margin="15,0,0,0"><Hyperlink>下载</Hyperlink></TextBlock> </StackPanel> </CheckBox> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="ListBoxItem.IsSelected" Value="True"> <Setter Property="ListBoxItem.Background" Value="#44BCDF"></Setter> <Setter Property="ListBoxItem.Foreground" Value="#fff"></Setter> </Trigger> </Style.Triggers> </Style> </ListBox.ItemContainerStyle> </ListBox>
单击下载后,拿到选择的值
private void downBlog_Click(object sender, RoutedEventArgs e) { var a=lbDemo1.SelectedItems; if(a.Count==0){ MessageBox.Show("你没有选择任何选项"); return; } StringBuilder sb=new StringBuilder(); foreach (var item in a) { sb.AppendLine(((Blog)item).BlogName); } MessageBox.Show(sb.ToString()); }
效果图:
ContentPresenter控件,你理解为内容显示控件,在这里他就是Item中绑定的要显示的字和一个整体的位置。那么关于radioButton的版本,你应该也会写了。
注意:这里修改了模板,样式的触发器没效果了。但是你可以在模板触发器中编写
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
3.AlternationCount
我们在Listbox上增加AlternationCount属性AlternationCount=3,表示自动会给Item项增加属性 AlternationIndex=0,然后1,2 ,就是0,1,2这样循环
我们去掉模板的代码,给不同的AlternationIndex添加不同的背景颜色
<ListBox x:Name="lbDemo1" AlternationCount="3" Height="100" Canvas.Left="378" Canvas.Top="42" Width="376" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" DisplayMemberPath="BlogName" SelectionMode="Multiple"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="ListBoxItem.Background" Value="#A5E0F1"></Setter> <!--<Setter Property="Template"> <Setter.Value> <ControlTemplate> <CheckBox Focusable="False" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsSelected,Mode=TwoWay}" x:Name="ck"> <StackPanel Orientation="Horizontal"> <ContentPresenter Content="{Binding Path=BlogName}"/> <TextBlock Margin="15,0,0,0"><Hyperlink>下载</Hyperlink></TextBlock> </StackPanel> </CheckBox> <ControlTemplate.Triggers> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter>--> <Style.Triggers> <Trigger Property="ItemsControl.AlternationIndex" Value="0"> <Setter Property="ListBoxItem.Background" Value="#64CD4F"></Setter> <Setter Property="ListBoxItem.Foreground" Value="#fff"></Setter> </Trigger> <Trigger Property="ItemsControl.AlternationIndex" Value="1"> <Setter Property="ListBoxItem.Background" Value="#FF0000"></Setter> <Setter Property="ListBoxItem.Foreground" Value="#fff"></Setter> </Trigger> <Trigger Property="ItemsControl.AlternationIndex" Value="2"> <Setter Property="ListBoxItem.Background" Value="#FFC90E"></Setter> <Setter Property="ListBoxItem.Foreground" Value="#fff"></Setter> </Trigger> <Trigger Property="ListBoxItem.IsSelected" Value="True"> <Setter Property="ListBoxItem.Background" Value="#44BCDF"></Setter> <Setter Property="ListBoxItem.Foreground" Value="#fff"></Setter> </Trigger> </Style.Triggers> </Style> </ListBox.ItemContainerStyle> </ListBox>
效果图:
4.样式选择器
OK,现在我们根据 是否 博客是否置顶属性,给置顶的文章额外的样式,增加 IsTop属性
public class DataDemo { public DataObject GetDate() { DataObject d = new DataObject(); d.MyDateTime = DateTime.Now; d.MyMoney = 520.58M; List<Blog> blog = new List<Blog> { new Blog{BlogName="wpf文章1",BlogContent="wpf内容1"}, new Blog{BlogName="wpf文章2",BlogContent="wpf内容2",IsTop=true}, new Blog{BlogName="wpf文章3",BlogContent="wpf内容3"}, new Blog{BlogName="wpf文章4",BlogContent="wpf内容4",IsTop=true}, new Blog{BlogName="wpf文章5",BlogContent="wpf内容5"}, new Blog{BlogName="wpf文章6",BlogContent="wpf内容6"}, new Blog{BlogName="wpf文章7",BlogContent="wpf内容7"}, new Blog{BlogName="wpf文章8",BlogContent="wpf内容8"}, new Blog{BlogName="wpf文章9",BlogContent="wpf内容9"} }; d.MyBlogs = blog; return d; } } public class Blog { public string BlogName { get; set; } public string BlogContent { get; set; } private bool isTop = false; public bool IsTop { get { return isTop; } set { isTop = value; } } }
接下来,我们在前台的window.Resources增加2个样式,置顶的 样式描红,加粗,字体大点。其他的正常显示,有淡灰色背景
<Window.Resources> <ObjectDataProvider x:Key="nowTime" MethodName="GetDate" IsAsynchronous="False" ObjectType="{x:Type local:DataDemo}"/> <local:SpendHistory x:Key="spendConvertor"></local:SpendHistory> <Style TargetType="ListBoxItem" x:Key="normalBlog"> <Setter Property="ListBoxItem.Background" Value="#E3E3E3"></Setter> <Setter Property="ListBoxItem.Foreground" Value="#9E9D9D"></Setter> </Style> <Style TargetType="ListBoxItem" x:Key="topBlog"> <Setter Property="ListBoxItem.Background" Value="#E3E3E3"></Setter> <Setter Property="ListBoxItem.Foreground" Value="#FF0000"></Setter> <Setter Property="ListBoxItem.FontSize" Value="18"></Setter> <Setter Property="ListBoxItem.FontWeight" Value="Bold"></Setter> </Style> </Window.Resources>
新建一个类,继承样式选择器类
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Windows; using System.Windows.Controls; namespace TemplateDemo { public class BlogSelector : StyleSelector { public Style NormalStyle { get; set; } public Style TopStyle { get; set; } /// <summary> /// 根据对象的什么属性 /// </summary> public string PropertyPath { get; set; } /// <summary> /// 根据对象的属性,参考判断的值 /// </summary> public string PropertyValue { get; set; } public override Style SelectStyle(object item, DependencyObject container) { Blog blog = (Blog)item; Type type = blog.GetType(); PropertyInfo pi = type.GetProperty(PropertyPath);//获得这个blog对象的指定属性 var pi_value = pi.GetValue(blog,null); if (pi_value.ToString() == PropertyValue) { return TopStyle; } else { return NormalStyle; } } } }
修改Listbox
<ListBox x:Name="lbDemo1" Height="100" Canvas.Left="378" Canvas.Top="42" Width="376" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" DisplayMemberPath="BlogName" SelectionMode="Multiple"> <ListBox.ItemContainerStyleSelector> <local:BlogSelector NormalStyle="{StaticResource normalBlog}" TopStyle="{StaticResource topBlog}" PropertyPath="IsTop" PropertyValue="True"/> </ListBox.ItemContainerStyleSelector> </ListBox>
效果图:
5.数据模板
当我们设置ItemsSource时候,ListBoxItem.Content的属性被设置为恰当的Blog对象了。我们可以在ItemTemplate自定义组合控件,然后显示数据了
<ListBox x:Name="lbDemo1" Height="344" Canvas.Left="378" Canvas.Top="42" Width="340" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" SelectionMode="Multiple"> <ListBox.ItemTemplate> <DataTemplate> <Border BorderBrush="#008000" BorderThickness="1" Margin="2" CornerRadius="2" Background="#fff" Width="305"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="60"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0"></TextBlock> <TextBlock Grid.Row="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> </DataTemplate> </ListBox.ItemTemplate> <ListBox.ItemContainerStyleSelector> <local:BlogSelector NormalStyle="{StaticResource normalBlog}" TopStyle="{StaticResource topBlog}" PropertyPath="IsTop" PropertyValue="True"/> </ListBox.ItemContainerStyleSelector> </ListBox>
这里我去掉了 DisplayMemberPath,因为不显示单个数据了,使用DataTemplate时候也必须去掉DisplayMemberPath
效果图:
接下来,我们把数据模板移到资源中去,使用key的方式使用
<DataTemplate x:Key="blogdt"> <Border BorderBrush="#008000" BorderThickness="1" Margin="2" CornerRadius="2" Background="#fff" Width="305"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="60"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0"></TextBlock> <TextBlock Grid.Row="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> </DataTemplate>
使用:
<ListBox x:Name="lbDemo1" ItemTemplate="{StaticResource blogdt}" Height="344" Canvas.Left="378" Canvas.Top="42" Width="340" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" SelectionMode="Multiple"> <ListBox.ItemContainerStyleSelector> <local:BlogSelector NormalStyle="{StaticResource normalBlog}" TopStyle="{StaticResource topBlog}" PropertyPath="IsTop" PropertyValue="True"/> </ListBox.ItemContainerStyleSelector> </ListBox>
效果是一样的。
接下来,我们可以定义 数据类型DataType 绑定 数据模板
删掉资源中,x:key的代码,换上DataType
<DataTemplate DataType="{x:Type local:Blog}"> <Border BorderBrush="#008000" BorderThickness="1" Margin="2" CornerRadius="2" Background="#fff" Width="305"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="60"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0"></TextBlock> <TextBlock Grid.Row="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> </DataTemplate>
那么该窗口中,所有数据类型是Blog的展示,都会自动应该该数据模板
那么下面的Listbox就不需要指定ItemTemplate了,也可以看到刚刚的展示效果
提示1:
但是如果Listbox还指定了ItemTemplate,那么还是以指定的数据模板展示。
WPF的数据模板中,例如有按钮,则可以像在窗口中写代码一样,可以添加事件,事件中可以拿到数据源,你就理解它就好像是Item中的窗口的代码,而不是单独的,绑定时候时候使用转换器等。
接下来,在DataTemplate中还有触发器,叫做DataTrigger,跟其他触发器一样的,就是值是绑定的。OK,现在我们去掉ListBox的样式选择器。把置顶的文章背景变成黄色
<DataTemplate x:Key="blogdt"> <Border BorderBrush="#f00" BorderThickness="1" Margin="2" CornerRadius="2" Background="#fff" Width="305" Name="BlogItemBorder"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="60"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0"></TextBlock> <TextBlock Grid.Row="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> <DataTemplate.Triggers> <DataTrigger Binding="{Binding Path=IsTop}" Value="True"> <Setter TargetName="BlogItemBorder" Property="Background" Value="Yellow"/> </DataTrigger> </DataTemplate.Triggers> </DataTemplate>
效果图:
提示2:
数据模板绑定的对象如果实现了INotifyPropertyChanged接口,那么后台改变数据,模板中的数据也会变
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
6.模板选择器
新建一个模板选择器,BlogDataTemplateSelector,方式和样式选择器很像
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; namespace TemplateDemo { public class BlogDataTemplateSelector : DataTemplateSelector { public DataTemplate NormalDataTemplate { get; set; } public DataTemplate TopDataTemplate { get; set; } /// <summary> /// 根据对象的什么属性 /// </summary> public string PropertyPath { get; set; } /// <summary> /// 根据对象的属性,参考判断的值 /// </summary> public string PropertyValue { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { Blog blog = (Blog)item; Type type = blog.GetType(); PropertyInfo pi = type.GetProperty(PropertyPath);//获得这个blog对象的指定属性 var pi_value = pi.GetValue(blog, null); if (pi_value.ToString() == PropertyValue) { return TopDataTemplate; } else { return NormalDataTemplate; } } } }
接下来,前台2个数据模板,然后在容器中指定模板选择器
新增2个DataTemplate,一个横着显示,一个竖着显示
<DataTemplate x:Key="normalData"> <Border BorderBrush="#008000" BorderThickness="1" Margin="2" CornerRadius="2" Background="#fff" Width="305"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="60"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0"></TextBlock> <TextBlock Grid.Row="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> </DataTemplate> <DataTemplate x:Key="TopData"> <Border BorderBrush="#008000" BorderThickness="1" Margin="2" CornerRadius="2" Background="#fff" Width="305"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition Width="280"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0" Foreground="#f00"></TextBlock> <TextBlock Grid.Column="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> </DataTemplate>
ListBox使用中ItemTemplateSelector
<ListBox x:Name="lbDemo1" Height="344" Canvas.Left="378" Canvas.Top="42" Width="340" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" SelectionMode="Multiple"> <ListBox.ItemTemplateSelector> <local:BlogDataTemplateSelector NormalDataTemplate="{StaticResource normalData}" TopDataTemplate="{StaticResource TopData}" PropertyPath="IsTop" PropertyValue="True"/> </ListBox.ItemTemplateSelector> </ListBox>
效果图:isTop等于true的使用了第二个模板,false的使用了第一个模板
到目前,选中SelectedItem的时候,背景颜色都不出来,因为设置了数据模板中,border的背景颜色被设置了白色, 我们在ItemContainerStyle中设置了Trigger也没用,我们把TopData这个数据模板中的border的background属性绑定到父节点的ListBoxItem的背景颜色上,就有效果了。
<DataTemplate x:Key="TopData"> <Border BorderBrush="#008000" BorderThickness="1" CornerRadius="2" Background="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem},Mode=FindAncestor},Path=Background}" Width="316"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition Width="280"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0" Foreground="#f00"></TextBlock> <TextBlock Grid.Column="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> </DataTemplate>
然后ListBox
<ListBox x:Name="lbDemo1" Height="344" Canvas.Left="378" Canvas.Top="42" Width="340" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" SelectionMode="Multiple" HorizontalAlignment="Stretch"> <ListBox.ItemTemplateSelector> <local:BlogDataTemplateSelector NormalDataTemplate="{StaticResource normalData}" TopDataTemplate="{StaticResource TopData}" PropertyPath="IsTop" PropertyValue="True"/> </ListBox.ItemTemplateSelector> <ListBox.ItemContainerStyle> <Style> <Setter Property="Control.Padding" Value="0"/> <Style.Triggers> <Trigger Property="ListBoxItem.IsSelected" Value="True"> <Setter Property="ListBoxItem.Background" Value="Red"></Setter> </Trigger> </Style.Triggers> </Style> </ListBox.ItemContainerStyle> </ListBox>
由于模板中,Border的背景绑定了父节点的背景,我们只需要将Border最大化占满Item,就可以完全替代Item了。由于使用模板选择器,所以很明显看到ListboxItem的这个问题,怎么解决的。默认即使容器
有触发器,也不会修改背景的颜色(我们去掉normalData中的背景白色 和 Margin,请自己简单调整吧),这是由于模板包含了使用不同颜色的元素,这些元素在原来的颜色上显示,给遮住了。
这里使用了元素绑定的知识 Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem},Mode=FindAncestor},Path=Background
相对源,我们向上找节点FindAncestor,找到第一个ListBoxItem的元素,然后它的属性Background
效果图:
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
7.容器Panel,改变布局
到现在你看到的都是竖着摆放ListBoxItem的,因为Listbox容器中是StackPanel,我们更改其容器即可。我们把容器改成WrapPanel
为了更好的演示效果,我们把每个Item的宽度改小点,下面代码改了Border的宽度。我们修改下Listbox的宽度,方便查看效果
<DataTemplate x:Key="normalData"> <Border BorderBrush="#008000" BorderThickness="1" CornerRadius="2" Width="200"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="60"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0"></TextBlock> <TextBlock Grid.Row="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> </DataTemplate> <DataTemplate x:Key="TopData"> <Border BorderBrush="#008000" BorderThickness="1" CornerRadius="2" Background="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem},Mode=FindAncestor},Path=Background}" Width="200"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition Width="280"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0" Foreground="#f00"></TextBlock> <TextBlock Grid.Column="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> </DataTemplate>
Listbox代码如下:我们指定了ItemsPanel的值使用了WrapPanel
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled" x:Name="lbDemo1" Height="344" Canvas.Left="56" Canvas.Top="128" Width="1000" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" SelectionMode="Multiple"> <ListBox.ItemTemplateSelector> <local:BlogDataTemplateSelector NormalDataTemplate="{StaticResource normalData}" TopDataTemplate="{StaticResource TopData}" PropertyPath="IsTop" PropertyValue="True"/> </ListBox.ItemTemplateSelector> <ListBox.ItemContainerStyle> <Style> <Setter Property="Control.Padding" Value="0"/> <Style.Triggers> <Trigger Property="ListBoxItem.IsSelected" Value="True"> <Setter Property="ListBoxItem.Background" Value="Red"></Setter> </Trigger> </Style.Triggers> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel/> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox>
效果图:
这里我们加了ScrollViewer.HorizontalScrollBarVisibility="Disabled" 如果启用横向滚动条,则所有的listboxItem都在一行了
提示3:
自定义指定容器是好,但是是在考虑item项不多的时候建议使用。默认的ListBox使用的是 VirtualizingStackPanel而非StackPanel。Virtualizing的用法不说了,上篇文章已经讲解过了
如果项不多,但是模板过于复杂,一下子加载200-300多条,也是很影响使用,建议自己估量着用。
同理,关于 ComboBox的使用也很简单了,我们直接添加个ComboBox,指定数据源,由于window.Resources中的下面资源,所有使用Blog对象的,都是如下显示
<DataTemplate DataType="{x:Type local:Blog}"> <Border BorderBrush="#008000" BorderThickness="1" Margin="2" CornerRadius="2" Background="#fff" Width="200"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="60"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{Binding BlogName}" VerticalAlignment="Center" Background="#F1F0F0"></TextBlock> <TextBlock Grid.Row="1" Text="{Binding BlogContent}" TextTrimming="WordEllipsis" TextWrapping="Wrap" Padding="3"></TextBlock> </Grid> </Border> </DataTemplate>
所以下方ComboBox指定数据源时候,立即就应用了数据模板
<ComboBox Width="300" Canvas.Left="533" Canvas.Top="19" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" > </ComboBox>
效果图:
如果:IsEditable="True"
默认显示的是对象的ToString()我们现在添加TextSearch.TextPath="BlogName"
<ComboBox Width="300" Canvas.Left="533" Canvas.Top="19" ItemsSource="{Binding Path=MyBlogs,Source={StaticResource nowTime}}" IsEditable="True" TextSearch.TextPath="BlogName">
这样选中后,就是BlogName了,并且支持搜索
方式1:
不展开下拉框,输入wpf文章7,然后点击展开,默认滚动条滑到可看到BlogName等于wpf文章7的下拉项
方式2:
展开下拉框,输入 wpf文章5,下拉框就开始一个一个字开始匹配了,并且滚动条在移动
OK,三巴掌的数据绑定就写到这里了
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========