Introduction
This article is an attempt to visualize a WPF menu using the MVVM architecture.
Background
This article is inspired from the works of Josh Smith, mainly his implementation of the TreeView
. It opened at least my eyes and made me realize that a monster like a Treeview
in WPF can be easily overwhelmed using a ViewModel approach. If you have not gone through the article, please have a look as it is a must read for all MVVM aspirants.
Implementation on WPF Menu
What I have tried to achieve is to reuse the implementation of Treeview
that Josh had done on WPF menus. Realizing a Menu Item as a View Model makes the implementation far easier and also enables separation of concern. So what a View Model object generally encloses is the state of the model and the properties associated with the presentation layer like IsEnabled
, Icon
etc. Header
of the Menu Item is bound to the mapping property in the View Model which is mapped to let's same a Name
property in the model.
The ViewModels
Let’s have a look at the Project structure that I have maintained:
A BaseViewModel
class is maintained which has the implementation for INotifyPropertyChanged
interface which enables to have the PropertyChanged
. The PropertyChanged
event notifies the UI about the changes happening to the property in the View Model.
public class BaseViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyOnPropertyChanged(string strPropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs strPropertyName));
}
}
#endregion
}
Using the Code
It’s sixes raining at Twenty 20 and I'm a keen follower of the game whenever it happens. So my sample has an inclination towards the terminologies used in the game.
Let us see the Viewmodels that I have used to accomplish the task.
MenuItemViewModel
This one I have used as the base class for all the menu item viewmodels.
public class MenuItemViewModel : BaseViewModel
{
public MenuItemViewModel(MenuItemViewModel parentViewModel)
{
ParentViewModel = parentViewModel;
_childMenuItems = new ObservableCollection<menuitemviewmodel>();
}
private ObservableCollection<menuitemviewmodel> _childMenuItems;
public ObservableCollection<menuitemviewmodel> ChildMenuItems
{
get
{
return _childMenuItems;
}
}
private string _header;
public string Header
{
get
{
return _header;
}
set
{
_header = value; NotifyOnPropertyChanged("Header");
}
}
public MenuItemViewModel ParentViewModel { get; set; }
public virtual void LoadChildMenuItems()
{
}
public object IconSource { get; set; }
}
The menuitemviewmodel
has a virtual method LoadChildMenuItems
which will be overridden and used to load child menuitems in the application. The menuitemviewmodel
is also associated with a parent view model in case a parent exists. It has a Header
property which is bound with the Header
property of MenuItem
, i.e., the presentation property. We can also have properties like IconSource
in the menuitemviewmodel
which will have a direct mapping to the Source
Property of an Icon which can be used in a MenuItem
.
PlayerViewModel
This viewmodel has a direct mapping to the Player
business object and inherits MenuItemViewModel
class.
public class PlayerViewModel : MenuItemViewModel
{
private Player _player;
public PlayerViewModel(TeamViewModel parentViewModel, Player player)
: base(parentViewModel)
{
_player = player;
}
private string _playerName;
public string PlayerName
{
get
{
return _player.PlayerName;
}
set
{
_player.PlayerName = value;
NotifyOnPropertyChanged("PlayerName");
}
}
}
Similarly we have Twenty20TeamsViewModel
which maps to Twenty20Teams
business objects and TeamViewModel
mapping to Team
class.
public class SeparatorViewModel : MenuItemViewModel
{
public SeparatorViewModel(MenuItemViewModel parentViewModel) :
base(parentViewModel)
{
}
}
SeparatorViewModel
This viewmodel is very specific to the presentation part as it helps to show the separator object between menuitems.
How to use this viewmodel to enable separators will be discussed in the XAML part of the code which can be seen in my sample application.
A
HierarchicalDataTemplate
is defined whose
DataType
is set to
MenuItemViewModel
and
ItemSource
is set to the
ChildMenuItems
. So this will behave in a recursive way until the
ChildMenuItems
exist, that many levels of hierarchical structure of menus will be generated. So this will be a good approach if the menus are generated dynamically and we are not sure about the level upto which the hierarchy can happen. I'm loading the Viewmodels on startup of the application something like this as follows:
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();
}
}
Here, Twenty20TeamsViewModel
is passed as a parameter to the constructor of the sample window. This viewmodel is set as the DataContext
of the window.
public MenuSampleWindow(MenuItemViewModel menuItemViewModel)
{
InitializeComponent();
this.DataContext = menuItemViewModel;
}
The Output
Output shows you the hierarchy of menu items being loaded and also the Separator
items.
Points of Interest
Funny thing to be noticed here is that the Separator is a focussable item even if we have set the Focussable
property to false
. Still thinking what it is that I'm missing here to make it non focussable.
Conclusion
Thinking in terms of ViewModels doesn't come by itself. It has to be cultivated within. Special thanks to Josh for the Treeview
article which made a paradigm shift to my thought process. Hopefully my article will help someone to think in terms of view models.
History
- How to use an
XDocument
along with Lambda expression and Linq for the sample application is updated in my blog site. Once the implementation is complete, it'll be available for download here.
- Updates related to this article is now available in by blogspot site (http://mywpf-visu.blogspot.com/)