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

使用 ViewModel 创建 WPF 菜单 - 第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (10投票s)

2009年6月14日

CPOL

4分钟阅读

viewsIcon

81524

downloadIcon

2816

使用 ViewModel 方法实现 WPF 菜单。

引言

本文旨在尝试使用 MVVM 架构可视化一个 WPF 菜单。

背景

本文的灵感来自 Josh Smith 的作品,主要是他对 TreeView 的实现。它至少让我眼前一亮,并让我意识到像 WPF 中的 Treeview 这样的庞然大物,使用 ViewModel 方法可以轻松应对。如果您还没有阅读这篇文章,请看一下,因为对于所有 MVVM 有志者来说,这是必读的。

WPF 菜单的实现

我试图实现的是重用 Josh 在 WPF 菜单上对 Treeview 的实现。将菜单项实现为 View Model 使实现更容易,也实现了关注点分离。因此,一个 View Model 对象通常包含模型的状态和与表示层相关的属性,例如 IsEnabled, Icon 等。菜单项的 Header 绑定到 View Model 中的映射属性,该属性映射到模型中的一个属性,比如 Name 属性。

ViewModels

让我们看一下我维护的项目结构

MenuSolution.jpg

维护了一个 BaseViewModel 类,该类实现了 INotifyPropertyChanged 接口,从而可以拥有 PropertyChanged 事件。PropertyChanged 事件通知 UI View Model 中属性的更改。

public class BaseViewModel : INotifyPropertyChanged
{
   #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary> <summary>
    /// Notifies on property changed.
    /// </summary> </summary>
    /// Name of the STR property.
    public void NotifyOnPropertyChanged(string strPropertyName)
    {
      if (PropertyChanged != null) 
      { 
         PropertyChanged(this, new PropertyChangedEventArgs strPropertyName));
      }
    }

    #endregion
}

Using the Code

在 Twenty 20 比赛中,六次击球不断出现,我是一个狂热的比赛追随者。因此,我的示例对比赛中使用的术语有所偏向。

让我们看看我用来完成任务的 Viewmodel。

MenuItemViewModel

我将其用作所有菜单项 viewmodel 的基类。

public class MenuItemViewModel : BaseViewModel
{
    /// <summary>
    /// Initializes a new instance of the <see cref=""MenuItemViewModel"">   
    ///  class.
    /// </see></summary>
    /// The parent view model.
    public MenuItemViewModel(MenuItemViewModel parentViewModel)
    {
        ParentViewModel = parentViewModel;
        _childMenuItems = new ObservableCollection<menuitemviewmodel>();
    }

    private ObservableCollection<menuitemviewmodel> _childMenuItems;
    /// <summary>
    /// Gets the child menu items.
    /// </summary>
    /// <value>The child menu items.</value>
    public ObservableCollection<menuitemviewmodel> ChildMenuItems
    {
        get
        {
            return _childMenuItems;
        }
    }

    private string _header;
    /// <summary>
    /// Gets or sets the header.
    /// </summary>
    /// <value>The header.</value>
    public string Header
    {
        get
        {
            return _header;
        }
        set
        {
            _header = value; NotifyOnPropertyChanged("Header");
        }
    }

    /// <summary>
    /// Gets or sets the parent view model.
    /// </summary>
    /// <value>The parent view model.</value>
    public MenuItemViewModel ParentViewModel { get; set; }

    public virtual void LoadChildMenuItems()
    {
            
    }

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

menuitemviewmodel 有一个虚拟方法 LoadChildMenuItems,该方法将被重写并用于在应用程序中加载子菜单项。menuitemviewmodel 还与父 view model 关联,以防父项存在。它有一个 Header 属性,该属性与 MenuItemHeader 属性(即表示属性)绑定。我们还可以在 menuitemviewmodel 中拥有 IconSource 等属性,这些属性将直接映射到图标的 Source 属性,该属性可以在 MenuItem 中使用。

PlayerViewModel

这个 viewmodel 直接映射到 Player 业务对象,并继承了 MenuItemViewModel 类。

public class PlayerViewModel : MenuItemViewModel
{
        private Player _player;
        /// <summary>
        /// Initializes a new instance of the <see cref=""MenuItemViewModel""> class.
        /// </see></summary>
        /// The parent view model.
        public PlayerViewModel(TeamViewModel parentViewModel, Player player)
            : base(parentViewModel)
        {
            _player = player;
        }

        private string _playerName;
        /// <summary>
        /// Gets or sets the name of the player.
        /// </summary>
        /// <value>The name of the player.</value>
        public string PlayerName
        {
            get
            {
                return _player.PlayerName;
            }
            set
            {
                _player.PlayerName = value;      
                NotifyOnPropertyChanged("PlayerName");
            }
        }
}

类似地,我们有 Twenty20TeamsViewModel 映射到 Twenty20Teams 业务对象和 TeamViewModel 映射到 Team 类。

public class SeparatorViewModel : MenuItemViewModel
{
      public SeparatorViewModel(MenuItemViewModel parentViewModel) : 
              base(parentViewModel)
      {

      }
}

SeparatorViewModel

此 viewmodel 专门用于表示部分,因为它有助于显示菜单项之间的分隔符对象。

如何在代码的 XAML 部分使用此 viewmodel 来启用分隔符将在我的示例应用程序中讨论。

定义了一个 HierarchicalDataTemplate,其 DataType 设置为 MenuItemViewModelItemSource 设置为 ChildMenuItems。因此,这将以递归方式运行,直到 ChildMenuItems 存在,将生成许多级别的菜单分层结构。如果动态生成菜单,并且我们不确定层次结构可以达到的级别,这将是一个好方法。我正在应用程序启动时加载 Viewmodel,如下所示

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        Twenty20Teams twenty20Teams = new Twenty20Teams();
        twenty20Teams.Teams = new List<team>();

        Team teamA = new Team() { TeamName = "Team A"};
        teamA.Players = new List<player>();

        Player player1 = new Player(teamA) { PlayerName = "Player A" };
        teamA.Players.Add(player1);
            
        /*…………………………………………………………………………………………………………………………………………..
    …………………………………………………………………………………………………………………………………………..*/
        Player player6 = new Player(teamB) { PlayerName = "Player F" };
        teamB.Players.Add(player6);

        twenty20Teams.Teams.Add(teamA);
        twenty20Teams.Teams.Add(teamB);

        Twenty20TeamsViewModel viewModel = new 
                       Twenty20TeamsViewModel(twenty20Teams);
        viewModel.Header = "Twenty 20 Teams";
        MenuSampleWindow sampleWindow = new MenuSampleWindow(viewModel);
        sampleWindow.Show();
    }
}

在这里,Twenty20TeamsViewModel 作为参数传递给示例窗口的构造函数。此 viewmodel 被设置为窗口的 DataContext

public MenuSampleWindow(MenuItemViewModel menuItemViewModel)
{
    InitializeComponent();
    this.DataContext = menuItemViewModel;
}

输出

输出显示了正在加载的菜单项的层次结构以及 Separator 项。

MenuItemViewModelSample

关注点

这里要注意的有趣的事情是,分隔符是一个可聚焦的项,即使我们已将 Focussable 属性设置为 false。仍在思考我在这里缺少什么才能使其不可聚焦。

结论 

从 ViewModels 方面思考不会自动发生。它必须在内部培养。特别感谢 Josh 撰写的 Treeview 文章,它使我的思维方式发生了范式转变。希望我的文章能帮助某人从 view model 方面思考。

历史  

  1. 如何在示例应用程序中使用 XDocument 以及 Lambda 表达式和 Linq,已在我的博客站点上更新。实现完成后,将在此处提供下载。
  2. 与本文相关的更新现在可以在我的 blogspot 站点上找到 (http://mywpf-visu.blogspot.com/)
© . All rights reserved.