代码改变世界

MVVM体验记之DataGrid绑定

2010-07-26 20:53 囧月 阅读(...) 评论(...) 编辑 收藏

    知道MVVM这个词已经很久,但是一直没有去认识一下它,恰好这几天正在做点Silverlight的东西玩,顺便就也见识一下MVVM。

    准备工作:

1、Silverlight 4包括SDK 及 Silverlight 4 Toolkit April 2010

2、创建实体类(将在后面的DEMO中使用)People.cs:

    public class People
    {
        public string Name { get; set; }

        public int Age { get; set; }
    }

3、创建实体类相应View视图PeopleView.xaml:

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="姓名:" />
        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" />
        <TextBlock Grid.Row="1" Grid.Column="0" Text="年龄:" />
        <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Age}" />
    </Grid>

4、创建ViewModel(PropertyChangedBase来自这里),使它实现INotifyPropertyChanged接口:

    public class PeopleViewModel : PropertyChangedBase
    {
        public PeopleViewModel()
        {
            Peoples = new ObservableCollection<People>();
            for (int i = 10; i < 30; i++)
            {
                Peoples.Add(new People { Name = "test" + i.ToString(), Age = i });
            }
        }

        private People selectedPeople;

        public People SelectedPeople
        {
            get { return selectedPeople; }
            set
            {
                selectedPeople = value;
                this.NotifyPropertyChanged(p => p.SelectedPeople);
            }
        }

        public ObservableCollection<People> Peoples { get; set; }

    }

 

接下来开始DEMO

一、绑定DataGrid当前选中的行SelectedItem

创建DEMO1页面,Xaml代码为:

<UserControl x:Class="LWME.MVVMExperience.Views.Demo1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" 
    xmlns:v="clr-namespace:LWME.MVVMExperience.Views"
    xmlns:vm="clr-namespace:LWME.MVVMExperience.ViewModel"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <vm:PeopleViewModel x:Key="vm1" />
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource vm1}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="400" />
        </Grid.ColumnDefinitions>
        <sdk:DataGrid Grid.Column="0" ItemsSource="{Binding Peoples}" SelectedItem="{Binding SelectedPeople, Mode=TwoWay}" />
        <StackPanel Grid.Column="1" HorizontalAlignment="Center">
            <TextBlock Text="当前选中:" />
            <v:PeopleView DataContext="{Binding SelectedPeople}" />
        </StackPanel>
    </Grid>
</UserControl>

运行它,可以看到选择DataGrid某一行的时候,右边相应的详细信息也跟着变了,但这里并没有写任何代码,是不是很神奇呢?

 

二、绑定选择的多行SelectedItems

    上面由于SelectedItem同时有get、set访问器,所以支持直接绑定。但是SelectedItems是不支持set访问器的,所以无法直接绑定。有人提到使用Command来解决这一问题,现在便用一下他的方法吧:

    首先,实现ICommand接口:

    public class RelayCommand<T> : ICommand
    {
        /// <summary>
        /// Occurs when changes occur that affect whether the command should execute.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        Predicate<T> canExecute;
        Action<T> executeAction;
        bool canExecuteCache;

        /// <summary>
        /// Initializes a new instance of the <see cref="RelayCommand"/> class.
        /// </summary>
        /// <param name="executeAction">The execute action.</param>
        public RelayCommand(Action<T> executeAction) : this(executeAction, null) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="RelayCommand"/> class.
        /// </summary>
        /// <param name="executeAction">The execute action.</param>
        /// <param name="canExecute">The can execute.</param>
        public RelayCommand(Action<T> executeAction,
                               Predicate<T> canExecute)
        {
            this.executeAction = executeAction;
            this.canExecute = canExecute;
        }

        #region ICommand Members
        /// <summary>
        /// Defines the method that determines whether the command 
        /// can execute in its current state.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. 
        /// If the command does not require data to be passed,
        /// this object can be set to null.
        /// </param>
        /// <returns>
        /// true if this command can be executed; otherwise, false.
        /// </returns>
        public bool CanExecute(object parameter)
        {
            if (canExecute == null)
                return true;

            bool tempCanExecute = canExecute((T)parameter);

            if (canExecuteCache != tempCanExecute)
            {
                canExecuteCache = tempCanExecute;
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, EventArgs.Empty);
                }
            }

            return canExecuteCache;
        }

        /// <summary>
        /// Defines the method to be called when the command is invoked.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. 
        /// If the command does not require data to be passed, 
        /// this object can be set to null.
        /// </param>
        public void Execute(object parameter)
        {
            executeAction((T)parameter);
        }
        #endregion
    }

  接下来,修改PeopleViewModel,定义一个Command属性以绑定到DataGrid的SelectionChange事件,同时添加其他需要用于多选的属性:

    public class PeopleViewModel : PropertyChangedBase
    {
        public PeopleViewModel()
        {
            Peoples = new ObservableCollection<People>();
            for (int i = 10; i < 30; i++)
            {
                Peoples.Add(new People { Name = "test" + i.ToString(), Age = i });
            }

            SelectionChangedCommand = new RelayCommand<IList>(SelectionChanged);
        }

        private People selectedPeople;

        public People SelectedPeople
        {
            get { return selectedPeople; }
            set
            {
                selectedPeople = value;
                this.NotifyPropertyChanged(p => p.SelectedPeople);
            }
        }

        public ObservableCollection<People> Peoples { get; set; }


        #region 多选

        private IEnumerable<People> selectedPeoples;
        public IEnumerable<People> SelectedPeoples
        {
            get { return selectedPeoples; }
            set
            {
                selectedPeoples = value;
                this.NotifyPropertyChanged(p => p.SelectedPeoples);
            }
        }

        private int numberOfItemsSelected;
        public int NumberOfItemsSelected
        {
            get { return numberOfItemsSelected; }
            set
            {
                numberOfItemsSelected = value;
                this.NotifyPropertyChanged(p => p.NumberOfItemsSelected);
            }
        }

        public RelayCommand<IList> SelectionChangedCommand { get; set; }

        private void SelectionChanged(IList items)
        {
            if (items != null)
            {
                SelectedPeoples = items.Cast<People>();
                NumberOfItemsSelected = items.Count;
            }
            else
            {
                SelectedPeoples = null;
                NumberOfItemsSelected = 0;
            }
        }
        #endregion
    }

    再定义一个Converter用于显示多个选择的People到textblock上:

    public class SelectedPeoplesConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            IEnumerable<People> SelectedPeoples = value as IEnumerable<People>;
            if (SelectedPeoples != null)
            {
                return string.Join(
                    "|",
                    SelectedPeoples.Select(p => string.Format("姓名:{0},年龄:{1}   ", p.Name, p.Age))
                    );
            }

            return string.Empty;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    再添加我们的Demo2.xaml,这里Command的绑定用到了System.Windows.Interactivity.dll:

<UserControl x:Class="LWME.MVVMExperience.Views.Demo2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" 
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:v="clr-namespace:LWME.MVVMExperience.Views"
    xmlns:vm="clr-namespace:LWME.MVVMExperience.ViewModel"
    xmlns:cv="clr-namespace:LWME.MVVMExperience.Common"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <vm:PeopleViewModel x:Key="vm1" />
        <cv:SelectedPeoplesConverter x:Key="pc1" />
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource vm1}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="400" />
        </Grid.ColumnDefinitions>
        <sdk:DataGrid x:Name="peoplesDataGrid" Grid.Column="0" ItemsSource="{Binding Peoples}" SelectedItem="{Binding SelectedPeople, Mode=TwoWay}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged">
                    <i:InvokeCommandAction
                        Command="{Binding SelectionChangedCommand, Mode=OneWay}"
                        CommandParameter="{Binding SelectedItems, ElementName=peoplesDataGrid}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </sdk:DataGrid>
        <StackPanel Grid.Column="1" HorizontalAlignment="Center">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text=" 总共选中了:" />
                <TextBlock Text="{Binding NumberOfItemsSelected}" />
                <TextBlock Text="条记录,分别为:" />
            </StackPanel>
            <TextBlock Text="{Binding SelectedPeoples, Converter={StaticResource pc1}}" TextWrapping="Wrap" />
            <TextBlock Text=" 当前选中:" />
            <v:PeopleView DataContext="{Binding SelectedPeople}" />
        </StackPanel>
    </Grid>
</UserControl>

 

运行一下,选中多个列的时候,右边同时显示出了多个被选中的People数据以及选中的数目。

 

总结一下,良好的分离使MVVM看起来蛮优雅的,而且熟悉了以后,有利于简化代码以及提高开发效率,over。

 

DEMO在这里。。。