首页 > 代码库 > [Aaronyang] 写给自己的WPF4.5 笔记7[三巴掌-ItemsControl数据绑定详解与binding二次处理 3/3]

[Aaronyang] 写给自己的WPF4.5 笔记7[三巴掌-ItemsControl数据绑定详解与binding二次处理 3/3]

我要做回自己--Aaronyang的博客(www.ayjs.net)

博客摘要:

  1. 全方位的讲解了转换器的使用,单值,多值转换器,条件转换器,StringFormat等方式
  2. 详细的实践地讲解了ItemsControl中的知识

    一:ItemsSource,DisplayMemberPath,ItemStringFormat,ItemContainerStyle

    二:ItemContainerStyle下修改显示模板

    三:AlternationCount例讲

    四:StyleSelector样式选择器

    五:DataTemplateSelector模板选择器,以及数据模板深入

    六:ItemsPanel

    七:细节分析

  3. 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 = http://www.mamicode.com/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 = http://www.mamicode.com/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 =========== 安徽 六安 杨洋 ==========   未经允许不许转载 =========

 

[Aaronyang] 写给自己的WPF4.5 笔记7[三巴掌-ItemsControl数据绑定详解与binding二次处理 3/3]