1.MVVM模式

MVVM即模型-视图-视图模型 ,是用于解耦 UI 代码和非 UI 代码的 设计模式。 借助 MVVM,可以在 XAML 中以声明方式定义 UI,将 UI使用数据绑定标到包含数据和命令的其他层。 数据绑定提供数据和结构的松散耦合,使 UI 和链接的数据保持同步,同时可以将用户输入路由到相应的命令。

 MVVM模式由M(Model),V(View),VM(ViewModel)三部分组成,其设计模式类似为MVC开发模式。目的是解耦UI和代码,编译编辑和修改。

通常一个View对应一个ViewModel,一个ViewModel可能包含n个model。

实际应用过程中可以对View中的不同控件绑定不同的ViewModel,变成一对多的模式。

2.View

 View即UI界面,wpf使用xaml编,xaml是在xml的基础上开发的,整体风格与xml有诸多类似之处。

2.1绑定数据

View界面中需要通过数据绑定将具体的数据绑定到ViewModel的对应属性上。

<DataGrid x:Name="_dgSubItemList" Grid.Row="0" Margin="10,0,10,0" ItemsSource="{Binding SubitemList}" SelectedItem="{Binding SelectedSubitem}" Style="{StaticResource MLMB.DataGridStyle}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="{x:Static MUI:Strings.ID}" Binding="{Binding ID, StringFormat=\{0:D\}}" MinWidth="40"/>
                <DataGridTextColumn Header="{x:Static MUI:Strings.SubitemName}" Binding="{Binding Name}" MinWidth="100"  Width="1*"/>
                <DataGridTextColumn Header="{x:Static MUI:Strings.RS}" Binding="{Binding RS}"  MinWidth="80"/>
                <DataGridTextColumn Header="{x:Static MUI:Strings.RelatedGene}" Binding="{Binding RelatedGene}" MinWidth="100"/>
            </DataGrid.Columns>
        </DataGrid>

如上XAML中将DataGrid的ItemsSource属性绑定到列表,同时将对应列的数据绑定到列表的不同属性数据上。

2.2绑定事件

<ctrls:UdButton Content="{x:Static MUI:Strings.Add}" Command="{Binding AddCommand}" CommandParameter="{Binding ElementName=_dgSubItemList}" x:Name="_btnAddSubItem"/>

按钮不设置Click事件,该有使用Command属性绑定具体的方法,进而调用,同时使用CommandParameter属性传递UI界面参数给ViewModel,以便后台可以访问View的控件进行相关操作。

如果可以一般ViewModel不建议访问View中的控件,以便完全剥离UI和后台代码,便于移植。

2.3绑定ViewModel

    <Window.DataContext>
        <vm:AddSubItemViewModel/>
    </Window.DataContext>

通过设置窗体的DataContext属性绑定具体的ViewModel,此方法不需要后台实现ViewModel之后再绑定,可以直接系统实现ViewModel。

DataContext="{DynamicResource vm:AddSubItemViewModel}"

Xaml设置DataContext属性时需要单列绑定<Window.DataContext>属性标签,如果在Window标签中绑定DataContext属性需要绑定静态的ViewModel,否则无法正常获取ViewModel数据。

        /// <summary>
        /// 构造函数
        /// </summary>
        public AddSubItemWindow()
        {
            InitializeComponent();
            this.DataContext = new AddSubItemViewModel();
        }

也可以在后台用用代码进行设置,再构造函数里用构造函数绑定ViewModel。

一般一个界面设定窗体的DataContext属性,不过实际使用可以对不同的孔家设置不同的DataContext属性。

3. ViewModel

ViewModel即View和Model的中间层,用于中转界面和具体实现代码。

3.1 实现INotifyPropertyChanged接口

ViewModel用于被绑定的数据需要使用INotifyPropertyChanged接口,才能做到UI界面和后台数据的实时变更通知,ViewModel数据更新后实时刷新界面View的数据。

    public class BaseNotifyPropertyChanged : INotifyPropertyChanged
    {

        /// <summary>
        /// 实现接口
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// 属性变更事件
        /// </summary>
        /// <param name="Path">事件源</param>
        public void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

配置[CallerMemberName]属性可以自动获取属性名称作为委托参数的名称,省去代码调用时需要一个个输入委托参数名称。

3.2 基本属性参数

        /// <summary>
        /// private button enabled state.
        /// </summary>
        private bool _isBtnEnabled = true;

        /// <summary>
        /// Button enabled state.
        /// </summary>
        public bool IsBtnEnabled
        {
            get
            {
                return _isBtnEnabled;
            }
            set
            {
                _isBtnEnabled = value;
                OnPropertyChanged();
            }
        }

先定义一个内部私有属性,然后定义一个public属性,通过get,set获取私有类。设置属性值时调用“OnPropertyChanged()”方法通知界面刷新数据。

3.3 绑定列表

        /// <summary>
        /// private subitem list.
        /// </summary>
        private ObservableCollection<SubItem> _subitemList = new ObservableCollection<SubItem>();

        /// <summary>
        /// Subitem list.
        /// </summary>
        public ObservableCollection<SubItem> SubitemList
        {
            get
            {
                return _subitemList;
            }
            set
            {
                _subitemList = value;
                OnPropertyChanged();
            }
        }

绑定列表时一般使用ObservableCollection<T>类实现,以便数据更新和刷新。

当然,如果不需要数据实时刷新时,可以直接绑定DataTable或者List<T>等数据源。

3.4 绑定事件

3.4.1 有command属性时绑定方法

绑定事件需要继承ICommand类,通过ICommand实现事件。

    /// <summary>
    /// Base command class.
    /// </summary>
    /// <remarks>通过事件委托,以便跨线程调用</remarks>
    public class BaseCommand : ICommand
    {
        /// <summary>
        /// Event handler
        /// 事件出发时先调用CanExecute(),然后调用
        /// </summary>
        public event EventHandler CanExecuteChanged;
        /// <summary>
        /// Execute action.
        /// </summary>
        public Action<object> DoExecute { get; set; }

        /// <summary>
        /// Canexecute method.
        /// </summary>
        public Func<object, bool> DoCanExecute { get; set; } = new Func<object, bool>(obj => true);

        /// <summary>
        /// Can execute.
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            //初始化时会调用一次
            return DoCanExecute?.Invoke(parameter) == true;
        }
        /// <summary>
        /// Excute.
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            //实例化委托。
            DoExecute?.Invoke(parameter);
        }

        /// <summary>
        /// 执行委托,调用CanExecute。
        /// </summary>
        public void DoCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }

ICommand接头实例化时会先调用一次CanExecuteChanged,然后出发方法后先调用一次CanExecuteChanged,然后调用Execute类。

该BaseCommand类实现两个方法,然后将事件通过委托外传,ViewMode可以为具体事件绑定不同的方法。

        /// <summary>
        /// 构造函数
        /// </summary>
        public AddSubItemViewModel()
        {
            //Relate to export data method.
            ExportDataCommand = new BaseCommand()
            {
                DoExecute = new Action<object>(ExportData)
            };
        }

        /// <summary>
        /// Export data.
        /// </summary>
        /// <param name="obj"></param>
        public void ExportData(object obj)
        {
            IsBtnEnabled = false;
            FileIO.ExportData();
        }

先实现指令类,然后再构造函数中管理具体的实现方法,再在函数中调用具体的处理函数。

3.4.2 将事件绑定到Command上

需要先添加NuGet包 behaviors

 然后再Xaml文件中添加引用:

 xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"

再对应的控件中添加具体方法,绑定Command:

<behaviors:Interaction.Triggers>
    <behaviors:EventTrigger EventName="SelectionChanged">
             <behaviors:InvokeCommandAction Command="{Binding SelectedChangedCommand}" CommandParameter="{Binding ElementName=lb_Items}"/>
    </behaviors:EventTrigger>
</behaviors:Interaction.Triggers>

4. Model

Model类是一个个具体处理的类,包括各种处理函数等实体类,通过ViewModel进行调用。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐