65.9K
CodeProject 正在变化。 阅读更多。
Home

介绍 Apex

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (21投票s)

2011年7月30日

CPOL

10分钟阅读

viewsIcon

60242

downloadIcon

1500

介绍 Apex - 一个轻量级的 MVVM 和实用库,支持 WPF、Silverlight 和 WP7。

Apex.png

引言

如果你要处理任何类型的 WPF、Silverlight 或 WP7 应用程序,那么几乎肯定会使用 MVVM。MVVM(模型-视图-视图模型)是一种设计模式,它鼓励你将表示层与数据分离。表示层在视图中完成,数据访问和操作在模型中完成,两者由视图模型绑定在一起。

本文假设你理解 MVVM 的基本原理——如果你不理解,我建议阅读 Jeremy Likness 的精彩文章《模型-视图-视图模型 (MVVM) 详解》。

如果你要使用 MVVM,市面上有一些很棒的库——然而,如果你喜欢从零开始,无论是作为学习练习,还是为了构建自己的自定义代码库,那么你将需要了解如何构建一个 MVVM 框架。在本文中,我将向你展示如何从头开始构建一个基本的 MVVM 框架。我还会演示如何创建一个通用的单一代码库,并从中创建支持 WPF、Silverlight **和** WP7 的库。

这个库叫做 **Apex**。我还有一堆已准备好发布的文章在使用 Apex——所以在我介绍它之前,我无法发布更具趣味性的内容!

现有框架

如果你正在寻找一个开箱即用的功能齐全的框架,那么我首先推荐 Cinch。Cinch 由 Sacha Barber 编写,在我看来是市面上最好的 MVVM 框架。v2 的主页 在此。另一个不错的选择是 Prism

自己动手

准备好从零开始自己动手了吗?我们开始吧。一个基本的 MVVM 框架需要什么?

  • **一个视图模型基类**:实现 INotifyPropertyChanged 并允许我们快速构建视图模型的类。
  • **一个命令基类**:实现 ICommand 并允许我们快速构建命令,从而让视图能够调用视图模型的功能的类。
  • **通用性**:一个允许我们在 WPF、Silverlight 和 WP7 中使用一致模式的框架。

让我们开始吧——首先,我们来看一个用作视图模型并实现 INotifyPropertyChanged 的类。

ViewModel

这是一个实现 INotifyPropertyChanged 的视图模型。

/// <summary>
/// ViewModel1 - a class that implements
/// INotifyPropertyChanged in the most basic way.
/// </summary>
public class ViewModel1 : INotifyPropertyChanged
{
  #region INotifyPropertyChanged Members

  /// <summary>
  /// The property changed event.
  /// </summary>
  public event PropertyChangedEventHandler PropertyChanged;
  
  #endregion
}

正如我们所见,这里唯一值得关注的是我们提供了 `PropertyChanged` 事件——这是我们可以触发的事件,用于指示特定属性已更改。当我们触发此事件时,WPF 框架将知道更新绑定到指定属性的任何内容的视觉效果。

接下来,我们应该将一个调用该事件的函数放在一起——如果它已被注册。

/// <summary>
/// Raises the property changed event.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
public virtual void NotifyPropertyChanged(string propertyName)
{
    //  Store the event handler - in case it changes between
    //  the line to check it and the line to fire it.
    PropertyChangedEventHandler propertyChanged = PropertyChanged;

    //  If the event has been subscribed to, fire it.
    if (propertyChanged != null)
        propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

现在我们有了一个可以调用来触发属性更改事件的函数。以此为起点,我们可以充实视图模型,使其真正做些事情——显示名字和姓氏。

/// <summary>
/// Gets or sets the first name.
/// </summary>
/// <value>The first name.</value>
public string FirstName
{
    get { return firstName; }
    set
    {
        if (firstName != value)
        {
            firstName = value;
            NotifyPropertyChanged("FirstName");
        }
    }
}

/// <summary>
/// Gets or sets the second name.
/// </summary>
/// <value>The second name.</value>
public string SecondName
{
    get { return secondName; }
    set
    {
        if (secondName != value)
        {
            secondName = value;
            NotifyPropertyChanged("SecondName");
        }
    }
}

这样,我们就拥有了一个代表名字和姓氏的视图模型。有什么问题?

  1. 我们必须为每个视图模型实现 `INotifyPropertyChanged`。
  2. 每个属性都必须进行检查,以确定它是否已更改并调用 `NotifyPropertyChanged` 函数。

我们将如何解决这个问题?

  1. 创建一个名为“`ViewModel`”的新类,它将成为所有未来视图模型的基础。这对于 MVVM 框架来说相当标准。
  2. 创建一个名为“`NotifyingProperty`”的新类,它将表示一个调用 `NotifyPropertyChanged` 的视图模型属性。这有点不同,所以我们将在这里详细介绍。

如果你一边阅读一边编码,现在是时候开始实际项目了。我将以 Apex 解决方案为基础进行描述——如果你使用此代码作为你自己的项目基础,可以在这里开始自定义内容。

创建一个名为 Apex 的新解决方案。添加一个名为 Apex 的 WPF 类库项目。在 Apex 中添加一个名为 _MVVM_ 的文件夹——这里我们将放置所有与 MVVM 相关的类。现在我们可以以上面的类为基础,将其作为我们基本视图模型类的起点。视图模型类如下所示。创建一个新文件,_ViewModel.cs_。

using System.ComponentModel;

namespace Apex.MVVM
{
    /// <summary>
    /// Standard viewmodel class base, simply allows
    /// property change notifications to be sent.
    /// </summary>
    public class ViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// The property changed event.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the property changed event.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        public virtual void NotifyPropertyChanged(string propertyName)
        {
            //  Store the event handler - in case it changes between
            //  the line to check it and the line to fire it.
            PropertyChangedEventHandler propertyChanged = PropertyChanged;

            //  If the event has been subscribed to, fire it.
            if (propertyChanged != null)
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

搞定。我们有了一个可以使用的视图模型基类。那么 NotifyingProperties 呢?作为启发,让我们看看依赖属性是如何定义的。

public class SomeDependencyObject : DependencyObject
{
    private static readonly DependencyProperty ageProperty =
        DependencyProperty.Register("Age", 
        typeof(int), typeof(SomeDependencyObject));
    
    public int Age
    {
        get { return (int)GetValue(ageProperty);}
        set { SetValue(ageProperty, value);}
    }
}

虽然有点啰嗦,但它的好处是熟悉。在任何自定义控件工作中,我们都会使用依赖属性,所以我们使用 WPF 和 Silverlight 越多,就会对上面的内容越熟悉。我们能否为视图模型的属性做类似的事情?

是的,我们可以——我们可以从 `NotifyingProperty` 类开始。它需要什么?属性的名称(字符串),属性值本身(对象),以及属性的类型。有了这些信息,我们就可以创建 `NotifyingProperty` 类。

using System;
namespace Apex.MVVM
{
    /// <summary>
    /// The NotifyingProperty class - represents a property of a viewmodel that
    /// can be wired into the notification system.
    /// </summary>
    public class NotifyingProperty
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="NotifyingProperty"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="type">The type.</param>
        /// <param name="value">The value.</param>
        public NotifyingProperty(string name, Type type, object value)
        {
            Name = name;
            Type = type;
            Value = value;
        }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the type.
        /// </summary>
        /// <value>The type.</value>
        public Type Type { get; set; }

        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        /// <value>The value.</value>
        public object Value { get; set; }
    }
}

这似乎是一个很好的起点——现在关于 `GetValue` 和 `SetValue` 函数,它们在 `DependencyObject` 中。如果我们向视图模型基类添加两个函数,我们可以在视图模型类中使用完全相同的语法。将以下两个函数添加到 `ViewModel` 类中。

/// <summary>
/// Gets the value of a notifying property.
/// </summary>
/// <param name="notifyingProperty">The notifying property.</param>
/// <returns>The value of the notifying property.</returns>
protected object GetValue(NotifyingProperty notifyingProperty)
{
    return notifyingProperty.Value;
}

/// <summary>
/// Sets the value of the notifying property.
/// </summary>
/// <param name="notifyingProperty">The notifying property.</param>
/// <param name="value">The value to set.</param>
/// <param name="forceUpdate">If set to <c>true</c> we'll force an update
/// of the binding by calling NotifyPropertyChanged.</param>
protected void SetValue(NotifyingProperty notifyingProperty, 
               object value, bool forceUpdate = false)
{
    //  We'll only set the value and notify that it has changed if the
    //  value is different - or if we are forcing an update.
    if (notifyingProperty.Value != value || forceUpdate)
    {
        //  Set the value.
        notifyingProperty.Value = value;
        //  Notify that the property has changed.
        NotifyPropertyChanged(notifyingProperty.Name);
    }
}

`GetValue` 很简单——它只是返回属性的值。`SetValue` 也很简单——它会检查值是否已更改,然后如果更改了,它会设置该值并触发我们之前添加的 `NotifyPropertyChanged` 函数。我们还添加了一个“`forceUpdate”参数,它允许我们在值未更改的情况下强制更新。在某些罕见的情况下我们可能需要它,但通常情况下我们不需要,所以我们将值默认设置为 `false`。

我们做到了!视图模型已完成,`NotifyingProperty` 也已完成!让我们来看看它的实际效果。

MVVMSample

在 Apex 解决方案中创建一个示例解决方案文件夹,并向其中添加一个名为 MVVMSample 的新 WPF 应用程序。我们将使用这个示例作为视图模型的演示。

添加对 Apex 库的引用,并在 MVVMSample 中创建一个名为 `MainViewModel` 的新类,如下所示。

using Apex.MVVM;
namespace MVVMSample
{
    /// <summary>
    /// An example view model that uses our new ViewModel base.
    /// </summary>
    public class MainViewModel : ViewModel
    {
        /// <summary>
        /// The first name property.
        /// </summary>
        private NotifyingProperty firstNameProperty = 
            new NotifyingProperty("FirstName", typeof(string), string.Empty);

        /// <summary>
        /// The second name property.
        /// </summary>
        private NotifyingProperty secondNameProperty =
            new NotifyingProperty("SecondName", typeof(string), string.Empty);

        /// <summary>
        /// Gets or sets the first name.
        /// </summary>
        /// <value>The first name.</value>
        public string FirstName
        {
            get { return (string)GetValue(firstNameProperty); }
            set { SetValue(firstNameProperty, value); }
        }

        /// <summary>
        /// Gets or sets the name of the second.
        /// </summary>
        /// <value>The name of the second.</value>
        public string SecondName
        {
            get { return (string)GetValue(secondNameProperty); }
            set { SetValue(secondNameProperty, value); }
        }
    }
}

到目前为止一切顺利——我们有一个实现了基类并支持通知属性(语法与 DependencyProperties 类似)的视图模型。让我们将其连接到 MainWindow。这是主窗口代码,新增的部分以粗体显示。

<Window x:Class="MVVMSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MVVMSample"
        Title="MainWindow" Height="200" Width="300">
    
    <!-- The datacontext for the window is an instance of our viewmodel. -->
    <Window.DataContext>
        <local:MainViewModel x:Name="viewModel" />
    </Window.DataContext>
    
    <Grid>
        <StackPanel>
            <TextBlock Text="First Name" />
            <TextBox Margin="4" Text="{Binding FirstName}" />
            <TextBlock Text="Second Name" />
            <TextBox Margin="4" Text="{Binding SecondName}" />
        </StackPanel>
    </Grid>
</Window>

XAML 代码紧凑整洁——事情确实朝着正确的方向发展。下一步将是处理命令。

视图模型命令

实现命令函数在此站点上的许多文章中都已完成。我想要实现的应该能够做到这一点:

  1. 允许将 Command 对象添加到视图模型,以便视图可以绑定到它。
  2. 允许 Command 对象以某种方式与视图模型的函数相关联,以便我们允许视图调用视图模型的功能。
  3. 允许 Command 对象向任意对象发送事件,以便我们可以让视图响应命令。这一点很重要。例如,假设我们有一个命令可以将地址簿中的当前联系人保存到数据库。我们希望确保当命令执行时,视图会将焦点移到“名字”文本框,即焦点逻辑(或任何与 UI 相关的逻辑)发生在视图中,而业务逻辑发生在视图模型中。

我们开始吧。向 Apex MVVM 文件夹添加一个名为“`ViewModelCommand”的新类,它实现 `ICommand`。

public class ViewModelCommand : ICommand
{
    #region ICommand Members

    public bool CanExecute(object parameter)
    {
        throw new NotImplementedException();
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        throw new NotImplementedException();
    }

    #endregion
}

这里我们看到了我们需要的最基本的东西。我们来充实一下。删除 `ICommand` 成员区域中的代码,我们将用新的代码替换它。我们添加的内容如下,逐条说明。

首先,我们需要跟踪命令是否可以执行。

/// <summary>
/// Boolean member indicating whether the command can execute.
/// </summary>
private bool canExecute = false;

查看上面的初始实现——我们需要一个 `CanExecuteChanged` 事件和一个 `CanExecute` 函数——我们现在就可以添加它们。

/// <summary> 
/// Occurs when can execute is changed.
/// </summary>
public event EventHandler CanExecuteChanged; 

/// <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>
bool ICommand.CanExecute(object parameter)
{
  return canExecute;
}

让我们添加一个属性,它允许我们设置命令是否可以执行并自动触发事件。

/// <summary>
/// Gets or sets a value indicating whether this instance can execute.
/// </summary>
/// <value>
/// <c>true</c> if this instance can execute; otherwise, <c>false</c>.
/// </value>
public bool CanExecute
{
  get { return canExecute; }
  set
  {
    if (canExecute != value)
    {
      canExecute = value;
      EventHandler canExecuteChanged = CanExecuteChanged;
      if (canExecuteChanged != null)
        canExecuteChanged(this, EventArgs.Empty);
    }
  }
}

当命令被调用时,`Execute` 将被调用。我们将添加一个名为“`DoExecute”的函数,它将实际执行命令。这意味着代码可以手动调用命令。`DoExecute` 将首先触发一个“`Executing”事件,该事件允许取消命令。这将非常有用——例如,假设我们有一个保存到数据库的命令,但我们想先弹出一个警告框。视图可以订阅“`Executing”事件,显示消息框,并在用户取消操作时设置已取消标志。这会将 UI 逻辑保留在视图中。我们还将添加一个“`Executed”事件,该事件将在命令完成时调用。这些事件中的每一个都将有一个手动编写的委托和一个手动编写的“EventArgs”类——我们需要这个来使其兼容 Silverlight 和 WP7。

最后,Command 将允许指定一个操作——一个普通的函数或一个接受对象作为参数的函数——这就是命令执行时将要调用的。我将插入完整的类——稍后在示例中会变得更清楚。

完整的 _ViewModelCommand.cs_ 文件

using System.Windows.Input;
using System;
namespace Apex.MVVM
{
    /// <summary>
    /// The ViewModelCommand class - an ICommand that can fire a function.
    /// </summary>
    public class ViewModelCommand : ICommand
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelCommand"/> class.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="canExecute">if set to <c>true</c> [can execute].</param>
        public ViewModelCommand(Action action, bool canExecute)
        {
            //  Set the action.
            this.action = action;
            this.canExecute = canExecute;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelCommand"/> class.
        /// </summary>
        /// <param name="parameterizedAction">The parameterized action.</param>
        /// <param name="canExecute">if set to <c>true</c> [can execute].</param>
        public ViewModelCommand(Action<object> parameterizedAction, bool canExecute)
        {
            //  Set the action.
            this.parameterizedAction = parameterizedAction;
            this.canExecute = canExecute;
        }
        /// <summary>
        /// Executes the command.
        /// </summary>
        /// <param name="param">The param.</param>
        public virtual void DoExecute(object param)
        {
            //  Get copies of the two event handlers we'll be using.
            //  We get them here to protect against the event timing anti-pattern.
            CancelCommandEventHandler executing = Executing;
            CommandEventHandler executed = Executed;

            //  Do we have an 'executing' event?
            if (executing != null)
            {
                //  Call the event.
                CancelCommandEventArgs args = 
                    new CancelCommandEventArgs() { Parameter = param };
                executing(this, args);
                //  If the event has been cancelled, bail now.
                if (args.Cancel)
                    return;
            }
            //  Call the action or the parameterized action, whichever has been set.
            if (action != null)
                action();
            else if (parameterizedAction != null)
                parameterizedAction(param);
            //  Call the executed event.
            if (executed != null)
                executed(this, new CommandEventArgs() { Parameter = param });
        }

        /// <summary>
        /// The action (or parameterized action) that
        /// will be called when the command is invoked.
        /// </summary>
        protected Action action = null;
        protected Action<object> parameterizedAction = null;
        /// <summary>
        /// Bool indicating whether the command can execute.
        /// </summary>
        private bool canExecute = false;
        /// <summary>
        /// Gets or sets a value indicating whether this instance can execute.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance can execute; otherwise, <c>false</c>.
        /// </value>
        public bool CanExecute
        {
            get { return canExecute; }
            set
            {
                if (canExecute != value)
                {
                    canExecute = value;
                    EventHandler canExecuteChanged = CanExecuteChanged;
                    if (canExecuteChanged != null)
                        canExecuteChanged(this, EventArgs.Empty);
                }
            }
        }
        #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>
        bool ICommand.CanExecute(object parameter)
        {
            return canExecute;
        }
        /// <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>
        void ICommand.Execute(object parameter)
        {
            this.DoExecute(parameter);
        }
        #endregion

        /// <summary>
        /// Occurs when can execute is changed.
        /// </summary>
        public event EventHandler CanExecuteChanged;
        /// <summary>
        /// Occurs when the command is about to execute.
        /// </summary>
        public event CancelCommandEventHandler Executing;
        /// <summary>
        /// Occurs when the command executed.
        /// </summary>
        public event CommandEventHandler Executed;
    }
    /// <summary>
    /// The CommandEventHandler delegate.
    /// </summary>
    public delegate void CommandEventHandler(object sender, CommandEventArgs args);
    /// <summary>
    /// The CancelCommandEvent delegate.
    /// </summary>
    public delegate void CancelCommandEventHandler(object sender, 
                    CancelCommandEventArgs args);
    /// <summary>
    /// CommandEventArgs - simply holds the command parameter.
    /// </summary>
    public class CommandEventArgs : EventArgs
    {
        /// <summary>
        /// Gets or sets the parameter.
        /// </summary>
        /// <value>The parameter.</value>
        public object Parameter { get; set; }
    }
    /// <summary>
    /// CancelCommandEventArgs - just like above but allows the event to 
    /// be cancelled.
    /// </summary>
    public class CancelCommandEventArgs : CommandEventArgs
    {
        /// <summary>
        /// Gets or sets a value indicating whether this
        /// <see cref="CancelCommandEventArgs"/> command should be cancelled.
        /// </summary>
        /// <value><c>true</c> if cancel;
        /// otherwise, <c>false</c>.</value>
        public bool Cancel { get; set; }
    }
}

这里的内容相当多,但都是我们已经描述过的。让我们在 MVVMSample 中添加一个命令,该命令可以根据名字和姓氏属性构建完整姓名。对 MVVMSample 的视图模型的更改如下所示。

using Apex.MVVM;
namespace MVVMSample
{
    /// <summary>
    /// An example view model that uses our new ViewModel base.
    /// </summary>
    public class MainViewModel : ViewModel
    {
        public MainViewModel()
        {
            //  Create the build name command.
            buildNameCommand = new ViewModelCommand(BuildName, true);
        }
        private void BuildName()
        {
            //  Set the full name.
            FullName = FirstName + " " + SecondName;
        }
        /// <summary>
        /// The first name property.
        /// </summary>
        private NotifyingProperty firstNameProperty = 
            new NotifyingProperty("FirstName", typeof(string), string.Empty);
        /// <summary>
        /// The second name property.
        /// </summary>
        private NotifyingProperty secondNameProperty =
            new NotifyingProperty("SecondName", typeof(string), string.Empty);
        /// <summary>
        /// The full name property.
        /// </summary>
        private NotifyingProperty fullNameProperty =
            new NotifyingProperty("FullName", typeof(string), string.Empty);
        /// <summary>
        /// Gets or sets the first name.
        /// </summary>
        /// <value>The first name.</value>
        public string FirstName
        {
            get { return (string)GetValue(firstNameProperty); }
            set { SetValue(firstNameProperty, value); }
        }
        /// <summary>
        /// Gets or sets the second name.
        /// </summary>
        /// <value>The second name.</value>
        public string SecondName
        {
            get { return (string)GetValue(secondNameProperty); }
            set { SetValue(secondNameProperty, value); }
        }
        /// <summary>
        /// Gets or sets the full name.
        /// </summary>
        /// <value>The full name.</value>
        public string FullName
        {
            get { return (string)GetValue(fullNameProperty); }
            set { SetValue(fullNameProperty, value); }
        }
        /// <summary>
        /// The build name command.
        /// </summary>
        private ViewModelCommand buildNameCommand;
        /// <summary>
        /// Gets the build name command.
        /// </summary>
        /// <value>The build name command.</value>
        public ViewModelCommand BuildNameCommand
        {
            get { return buildNameCommand; }
        }
    }
}

我们添加了一个可以绑定到的 Command,并将其与“`BuildName”函数关联起来。现在我们可以更新视图来使用它。对 _MainWindow.xaml_ 的更改如下所示,以粗体显示。

<Window x:Class="MVVMSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MVVMSample"
        Title="MainWindow" Height="200" Width="300">
    
    <!-- The datacontext for the window is an instance of our viewmodel. -->
    <Window.DataContext>
        <local:MainViewModel x:Name="viewModel" />
    </Window.DataContext>
    
    <Grid>
        <StackPanel>
            <TextBlock Text="First Name" />
            <TextBox Margin="4" Text="{Binding FirstName}" />
            <TextBlock Text="Second Name" />
            <TextBox Margin="4" Text="{Binding SecondName}" />
            <Button Margin="4" Width="80" 
               Content="Build Name" Command="{Binding BuildNameCommand}" />
            <TextBox Margin="4" Text="{Binding FullName}" />
        </StackPanel>
    </Grid>
</Window>

运行代码。它奏效了,我们得到了我们想要的一切。让我们展示一下“`CanExecute”的功能。修改名字和姓氏属性如下。

/// <summary>
/// Gets or sets the first name.
/// </summary>
/// <value>The first name.</value>
public string FirstName
{
    get { return (string)GetValue(firstNameProperty); }
    set 
    { 
        SetValue(firstNameProperty, value);
        BuildNameCommand.CanExecute = string.IsNullOrEmpty(FirstName) == 
            false && string.IsNullOrEmpty(SecondName) == false ;
    }
}
/// <summary>
/// Gets or sets the second name.
/// </summary>
/// <value>The second name.</value>
public string SecondName
{
    get { return (string)GetValue(secondNameProperty); }
    set
    {
        SetValue(secondNameProperty, value);
        BuildNameCommand.CanExecute = string.IsNullOrEmpty(FirstName) == 
          false && string.IsNullOrEmpty(SecondName) == false ;
    }
}

同时调整构造函数,以便默认禁用命令。

buildNameCommand = new ViewModelCommand(BuildName, false);

最后,调整名字和姓氏框,使它们在文本更改时绑定,而不是在框失去焦点时绑定。

<TextBox Margin="4" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" />
... 
<TextBlock Text="Second Name" />
<TextBox Margin="4" Text="{Binding SecondName, UpdateSourceTrigger=PropertyChanged}" />

我们做到了。一个扎实的视图模型基类和一个扎实的命令基类,用于关联功能。

ScreenshotWPF.png

通用性

这很棒的一点是它很容易与 Silverlight 和 WP7 集成。添加一个名为 Apex.Silverlight 的 Silverlight 类库。添加一个 MVVM 文件夹。右键单击并选择“添加文件”。选择 Apex 中 MVVM 文件夹的文件,然后选择“添加为链接”。

现在创建一个名为 Apex.WP7 的 WP7 类库。像以前一样添加 MVVM 文件链接。为了使文章长度适中,我不会包含示例应用程序——你猜怎么着——它们工作方式相同,并使用相同的 Apex 代码库。

ScreenshotSilverlight.png

ScreenshotWP7.png

在许多情况下,并非所有内容都可以通用——某些控件只能在 Silverlight 或 WPF 上运行。但是,框架代码、视图模型、拖放等应该在每个平台上都是通用的。

后续步骤

我拥有大量基于 Apex 并扩展 Apex 的有用代码——设计时代码、拖放代码、自定义控件、验证等。我将在接下来的几周内添加每个项目——即将推出一些非常出色的内容。

© . All rights reserved.