WPF之各种数据

在Silverlight和WPF中数据绑定都是使用Binding表达式来进行数据的绑定,当然这种方法的优点不仅仅是使用简单,和其强大的功能也存在直接的联系。
先看个Sample吧:

<Grid>
        <Grid.Resources>
            <local:User x:Key="currentUser"></local:User>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="35"></RowDefinition>
            <RowDefinition Height="35"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Margin="5">Name:</TextBlock>
        <TextBox Grid.Column="1" Margin="5" Text="{Binding Name,Source={StaticResource currentUser}}"></TextBox>
        <TextBlock Grid.Column="0" Margin="5" Grid.Row="1">Age:</TextBlock>
        <TextBox Grid.Column="1" Margin="5" Grid.Row="1" Text="{Binding Age,Source={StaticResource currentUser}}"></TextBox>
    </Grid>

此段代码中在在Grid中定义了一个资源,为一个实体类的对象(User为自定义实体),在TextBox中使用Binding的方式进行绑定数据,Source指定数据源为静态资源中的定义的对象(通过key来指定)。

上边代码仅仅指定了Binding表达式,并没有指定何时进行数据源的更新,所以上述的TextBox可以进行以下修改:

  <TextBox Grid.Column="1" Margin="5" Text="{Binding Name,Source={StaticResource currentUser}, UpdateSourceTrigger=PropertyChanged}"></TextBox>

在Binding中增加了UpdateSourceTrigger的属性设置,设置的值的为当值改变时候通知数据源。

Mode:

上述例子中并没有使用到Mode属性,那是因为TextBox的Text属性默认为TwoWay(吼吼,和Silverlight有不同哦),所以是不需要的,但是还是要进行解释。
Mode一共有好几个值可选,但是呢常用的有以下几个:

OneWay:表示仅从数据源更新到控件,比如当数据源有了修改,则界面会立即更新;
TwoWay:表示既从数据源更新到控件,也从数据源更新到服务端,比如当界面中的控件(Combobox控件,此处真的不能举例TextBox)发生了值的改变,那么对应的DataContext中控件的绑定的属性也会发生改变;
OneWatToSource:此属性在Silverlight中是没有的哦,作用是从控件更新到数据源对象。

WPF中的数据源绑定方式:

1.Source,一般来说值ItemsSource或者拥有此类似的属性,一般会绑定自己的对象。

2.ElementName,一般用于元素之间的绑定,比如将TextBlock的值绑定为某一个Silder的值。

3.RelativeSource 用于指定数据源的相对位置,例如我们都知道在DataGrid的模板中的Button无法触发Command,那么怎么办呢?哈哈,有了RelativeSource一切都变的那么简单。

<DataGrid>
            <DataGrid.Columns>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button Command="{Binding Path=EditCommand, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Window}}}"></Button>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

是不是很简单呢,FindAncestor表示将从目标对象沿着元素树向上查找,AncestorType表示需要查找的对象类型。即将从目标对象为起点,沿着元素树往上查找,直至找到第一个类型为Window的对象作为数据源。 RelativeResourceMode,除了FindAncestor之外还有三个值: Self,数据源即为当前的元素本身,用于元素的几个属性之间的绑定

<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
  <Style.Triggers>
    <Trigger Property="Validation.HasError" Value="true">
      <Setter Property="ToolTip"
        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)[0].ErrorContent}"/>
    </Trigger>
  </Style.Triggers>
</Style>

PreviousData:多用在列表中,允许您绑定所显示数据项列表中以前的数据项(不是包含数据项的控件)。
TemplateParent:表示拥有该模板的父类,引用应用了模板(其中有数据绑定元素)的元素。 这类似于设置 TemplateBindingExtension,并仅当 Binding 在模板中时适用。

INotifyPropertyChanged接口:

在工作中有时候会发现明明Binding正确,也指定了正确的Mode,数据源还是无法通知到界面,那么恭喜你很有可能是你的实体类没有实现INotifyPropertyChanged接口。INotifyPropertyChanged接口用我自己的解释就是用于通知界面属性改变了,是不是很通俗的解释,它的功能也的确是这样的,只不过你需要自己写实现的代码哦:

 private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged("Name");
                }
            }
        }
     
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

 实现INotifyPropertyChanged的目的是要进行对PropertyChanged 事件进行调用,当然前提是要自己写一个OnPropertyChanged这样的方法,参数就是属性的名字,在方法中判断当有属性改变事件不为空(即有属性值发生了改变),则对属性进行通知,这样Binding了此属性的控件就会得到更新。

UpdateSourceTrigger:

从字面意思就可以理解,是更新数据源的触发器,即何时从目标对象(往往指的是绑定属性的控件)更新到数据源(UpdateSourceTrigger也是一个枚举)。
1.LostFocus:目标对象失去焦点(控件失去焦点);
2.PropertyChanged:表示目标对象值发生改变(控件的值进行了改变);
3.Explicit:显式的通知,一般来说为在后置代码中手动调用UpdateSource方法;
4.Default:Binding的UpdateSourceTrigger属性大部分都为PropertyChanged。而Text属性考虑到了效率的问题,则是LostFocus。

IValueConverter:

值转换 IValueConverter 值转换比较实用的,比如数据库中保存的男女为1和0,而希望显示在界面上的为男和女,另外也希望在编辑界面上选择男女之后可以转换为1和0便于保存到数据库。 有了IValueConverter此需求变的非常简单,值转换类实现IValueConverter接口,需要实现Convert和ConvertBack方法,通过Convert方法转换为要显示的数据,通过ConvertBack将值更新到数据源去。
Sample:

 public class SexConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null)
            {
                if ((int)value == 0)
                {
                    return "";
                }
                else if ((int)value == 1)
                {
                    return "";
                }
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null)
            {
                if (value == "")
                {
                    return 0;
                }
                else if (value == "")
                {
                    return 1;
                }
            }
            return 0;
        }
    }

此例子为转换性别的,假设数据库中存放的为数字,而希望在界面上显示的为字符串,这时候我们就可以通过IValueConverter的转换。此接口有两个方法Convert和ConvertBack,前者为将元素(控件)绑定的值(即方法中的第一个参数Value)转换为你想要的任何格式(或者是任何逻辑的值)的值;后者为将元素中的值转换为数据源对应格式的值。例子中为在界面上显示为字符串"男女",而数据库为数字“0,1”,所以就需要在Convert方法中转换为字符串,而在ConvterBack方法中转成数据库一致的数字。

使用场景:一般来说IValueConverter使用在希望将Binding的值进行处理,不管是进行格式的格式化或者是进行逻辑的判断得到具体的值,甚至说可以根据值来返回一个颜色影响到文本的状态。

  既然说到了ValueConverter我就多说几句,除了上述的IValueConverter之外还有一个很厉害的类型转换,它就是TypeConverter.

自定义数据验证

Binding的ValidationRules属性是数据验证规则的集合,要实现自定义的数据验证,则要继承自一个抽象类ValidationRule,其中必须实现Validate方法,如下的一个验证是否为数字的自定义验证类:

  class NumberRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            int number;
            if (int.TryParse(value.ToString(), out number) == false)
            {
                return new ValidationResult(false, "输入了非法的数字!");
            }
            return ValidationResult.ValidResult;
        }
    }

 使用方法如下: 其中Binding.ValidationRules为验证规则集合,可以放多个要验证的(继承自ValidationRul)类,另外要设置ValidatesOnDataErrors为true,这样才可以进行显示错误,最重要的一点是ToolTip,此处我们绑定了ToolTip为错误的文本。在这里使用的时RelativeSource相对资源的方式,Self表示TextBox的本身,Validation.Errors为附加属性,是ValidationError的集合,当数据出现非法的时候,就会创建ValidationError对象到集合中。由于每次验证时候都会清空集合,所以使用的时第一个ValidationError对象。ErrorContent是ValidationError的属性,此属性就是我们自定义ValidationRule中Validate方法中返回的ValidationResult的第二个参数值。

 <TextBox Grid.Row="0" Height="30" Width="200" Background="LightBlue"  ToolTip="{Binding RelativeSource={RelativeSource  Self},Path=(Validation.Errors)[0].ErrorContent}">
            <TextBox.Text>
                <Binding Path="Age" ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged">
                    <Binding.ValidationRules>
                        <local:NumberRule></local:NumberRule>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>

 当输入非数字的字符时候,则会使边框显示为红色,同时这里使用ToolTip来显示错误信息,显示效果如下:

 

ErrorTemplate(错误消息模版): 默认的错误消息是以红色边框来显示的,上例子中我们还使用了ToolTip来显示错误文本。

<ControlTemplate x:Key="validationTemplate">
  <DockPanel>
    <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
    <AdornedElementPlaceholder/>
  </DockPanel>
</ControlTemplate>

上述代码为一个自定义验证模板的代码,此时出现错误则会在控件的左侧显示红色叹号,如下效果:

代码中比较疑惑的地方就是,AdornedElementPlaceholder标签(表示用于 ControlTemplate 的元素指定了装饰的控件位置放置相对于其他元素在 ControlTemplate。),此处相对的控件就是TextBox控件。

开始使用验证模板:

<TextBox Name="StartDateEntryForm" Grid.Row="3" Grid.Column="1" 
    Validation.ErrorTemplate="{StaticResource validationTemplate}" 
    Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5">
    <TextBox.Text>
        <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 
            Converter="{StaticResource dateConverter}" >
            <Binding.ValidationRules>
                <src:FutureDateRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

通过指定Validation.ErrorTemplate为静态的资源key即可。

数据的验证过程:(摘抄自WPF葵花宝典)

数据验证和值转换不同,数据验证只发生在从目标到数据源转换的过程中,即数据验证只能在模式为TowWay或者OneWayToSource的两种绑定。ValidationRule的ValidationStep属性用来标识ValidationRule的Validate函数的调用顺序,它有4种不同的枚举值,即RawProposedValue,ConvertedProposedValue,UpdateValue,CommitedValue.

(1)如果有自定义的ValidationRule,WPF则会首先调用值为RawProposedValue(ValidationStep)的Validate函数。如果验证不合法,该过程结束;否则继续。

(2)如果有值转换类,WPF会调用值转换类的ConvertBack函数。如果转换不成功,该过程结束;否则继续。

(3)WPF会继续检查值为ConvertedProposed的自定义ValidationRule,调用其Validate函数。如果验证不合法,该过程结束;否则继续。

(4)WPF设置数据源的属性值。

(5)WPF继续检查值为UpdateValue的自定义ValidationRule,调用其Validate函数。如果验证不合法,该过程结束;否则继续。

(6)WPF检查值为CommitedValue的自定义ValidationRule,调用其Validate函数,整个验证过程结束。

 

数据模板:

数据模板和控件模板类似,但是前者是用来定义数据的可视化外观,后者是用来定义整个控件的可视化外观。

 

<ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderThickness="1" BorderBrush="Black">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition></RowDefinition>
                                <RowDefinition></RowDefinition>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="40"></ColumnDefinition>
                                <ColumnDefinition></ColumnDefinition>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Row="0" Grid.Column="0" Text="姓名:"></TextBlock>
                            <TextBlock Grid.Row="1" Grid.Column="0" Text="年龄:"></TextBlock>
                            <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Name}"></TextBlock>
                            <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Age}"></TextBlock>
                        </Grid>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>

 

上述代码就是一个简单的DataTemplate例子,使ListBox的ItemTemplate模板作为一个两行两列的Grid,放置了四个TextBlock用于显示信息。

当然,DataTemplate也可以控件模板一样做成资源,供多个地方使用:

 <DataTemplate x:Key="listboxItemDataTemplate">
                <Border BorderThickness="1" BorderBrush="Black">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition></RowDefinition>
                            <RowDefinition></RowDefinition>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="40"></ColumnDefinition>
                            <ColumnDefinition></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Row="0" Grid.Column="0" Text="姓名:"></TextBlock>
                        <TextBlock Grid.Row="1" Grid.Column="0" Text="年龄:"></TextBlock>
                        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Name}"></TextBlock>
                        <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Age}"></TextBlock>
                    </Grid>
                </Border>
            </DataTemplate>

这就是一个资源形式的DataTemplate,在ListBox中设置属性ListTemplate="{StaticResourcelistboxItemDataTemplate}"即可。

更甚至,可以设置同一类元素的DataTemplae,此时就需要设置DataTemplate的DataType即可:

      <DataTemplate DataType="{x:Type local:Person}">
                <Border BorderThickness="1" BorderBrush="Black">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition></RowDefinition>
                            <RowDefinition></RowDefinition>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="40"></ColumnDefinition>
                            <ColumnDefinition></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Row="0" Grid.Column="0" Text="姓名:"></TextBlock>
                        <TextBlock Grid.Row="1" Grid.Column="0" Text="年龄:"></TextBlock>
                        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Name}"></TextBlock>
                        <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Age}"></TextBlock>
                    </Grid>
                </Border>
            </DataTemplate>

和之前代码的唯一区别就是将key替换了DataType,DataType类似于样式的TargetType属性,可以自动应用到所有类型为Person的对象,当然也无需在ListBox中指定ListItemTemplate。

集合视图:

集合视图可以进行过滤,排序和分组。集合视图实现自ICollectionView接口,可以在不改变数据集的情况下过滤,分组和排序数据。数据集合数据视图是一对多的关系,并且当目标直接绑定数据源时,WPF也会为数据源创建一个默认的数据视图。

Demo:

<Window.Resources>
        <local:People x:Key="personSource"></local:People>
        <CollectionViewSource Source="{Binding Source={StaticResource personSource}}" x:Key="listingDataView"></CollectionViewSource>
        <DataTemplate DataType="{x:Type local:User}">
            <Border BorderThickness="1" BorderBrush="Black" MinWidth="300">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition></RowDefinition>
                        <RowDefinition></RowDefinition>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="40"></ColumnDefinition>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" Text="姓名:"></TextBlock>
                    <TextBlock Grid.Row="1" Grid.Column="0" Text="年龄:"></TextBlock>
                    <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Name}"></TextBlock>
                    <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Age}"></TextBlock>
                </Grid>
            </Border>
        </DataTemplate>
        <DataTemplate x:Key="groupingHeaderTemplate">
            <TextBlock  Text="{Binding Path=Name}" Foreground="Navy" FontWeight="Bold" FontSize="12"></TextBlock>
        </DataTemplate>
    </Window.Resources>
 public class User : INotifyPropertyChanged
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged("Name");
                }
            }
        }
     
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private int _age;

        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }

    }

    public class People:ObservableCollection<User>
    {
        public People()
            : base()
        {
            Add(new User { Name = "Listen", Age = 21 });
            Add(new User { Name = "Fly", Age = 26 });
            Add(new User { Name = "Colors", Age = 28 });
            Add(new User { Name = "Blue", Age = 40});
            Add(new User { Name = "Colors.Blue", Age =77 });
        }
    }

在资源中有一个People集合的一个对象,同时还有一个CollectionViewSource 集合视图对象,另外还有两个DataTemplate,一个就是之前说到的针对一个类型的模板,另一个是新添加的针对ListBox的GroupStyle的HeaderTemplate的模板。

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <CheckBox Margin="5" x:Name="chkGroup" Checked="chkGroup_Checked_1" Unchecked="chkGroup_Unchecked_1">按年龄分组</CheckBox>
            <CheckBox Margin="5" x:Name="chkFilter" Checked="chkFilter_Checked_1" Unchecked="chkFilter_Unchecked_1">过滤</CheckBox>
            <CheckBox Margin="5" x:Name="chkSort" Checked="chkSort_Checked_1" Unchecked="chkSort_Unchecked_1">排序</CheckBox>
        </StackPanel>
        <ListBox x:Name="listBox" ItemsSource="{Binding Source={StaticResource listingDataView}}" Grid.Row="2">
            <ListBox.GroupStyle>
                <GroupStyle HeaderTemplate="{StaticResource groupingHeaderTemplate}"></GroupStyle>
            </ListBox.GroupStyle>
        </ListBox>
    </Grid>

ListBox部分代码如上,其中有三个复选框用于对数据进行分组,过滤和排序,同时也在ListBox的GroupStyle中指定了HeaderTemplate,表示分组标题的模板。

 CollectionViewSource listingDataView;
        public Window2()
        {
            InitializeComponent();
            listingDataView = (CollectionViewSource)this.Resources["listingDataView"];
        }

        private void chkGroup_Checked_1(object sender, RoutedEventArgs e)
        {
            PropertyGroupDescription groupDescription = new PropertyGroupDescription();
            groupDescription.PropertyName = "Age";
            listingDataView.GroupDescriptions.Add(groupDescription);
        }

        private void chkGroup_Unchecked_1(object sender, RoutedEventArgs e)
        {
            listingDataView.GroupDescriptions.Clear();
        }

        private void chkFilter_Checked_1(object sender, RoutedEventArgs e)
        {
            listingDataView.Filter += listingDataView_Filter;
        }

        void listingDataView_Filter(object sender, FilterEventArgs e)
        {
            User user = e.Item as User;
            if (user!=null)
            {
                if (user.Age > 30)
                {
                    e.Accepted = true;
                }
                else
                {
                    e.Accepted = false;
                }
            }
        }
        private void chkFilter_Unchecked_1(object sender, RoutedEventArgs e)
        {
            listingDataView.Filter -= listingDataView_Filter;
        }

        private void chkSort_Checked_1(object sender, RoutedEventArgs e)
        {
            listingDataView.SortDescriptions.Add(new System.ComponentModel.SortDescription("Age",System.ComponentModel.ListSortDirection.Descending));
        }

        private void chkSort_Unchecked_1(object sender, RoutedEventArgs e)
        {
            listingDataView.SortDescriptions.Clear();
        }

在后置代码中先是定义了一个CollectionViewSource的对象,用于接收Resource中的对象,同时也便于之后的操作。在三个复选框的Checked和UnChecked事件中对集合进行操作。(其中过滤是对Age大于30的数据进行过滤,对Name进行分组,对Age进行倒序)

效果图如下:

 

以上四幅图依次为默认状态,按年龄分组,过滤,和排序效果。

好了,这次的数据部分就说到这里,欢迎大家进行讨论和指正。

 

posted @ 2013-05-30 22:39  wangyafei_it  阅读(1552)  评论(0编辑  收藏  举报