Introduction
This article explains the basic concepts of WPF MVVM design pattern like DataContext, Databinding, CommandBinding etc. The example I will be explaining here will be well structured to expalin what is Model, View and ViewModel.
This article does not cover the in-depth of MVVM concept but would surely give you a kick start/refresher and you will be able to create simple application in WPF on your own. I assume that you have a basic knowledge on C# and .NET.
Background
I will pull out some key some concepts and make it very straight forward to understand the MVVM and how it can be implemented.
What is Loosely Coupled System Design?
Coupling is the dependency of one class on another. In tightly coupled systems, changes in one class force the changes in another class thus making the system difficult to maintain and decreasing the reusability of component. In order to achieve stable and low maintenance cost application, loose coupling is desired.
What is MVVM?
MVVM is a central concept in WPF, Silverlight, and Windows 8 development.
The Model-View separation idea has been there in industry around 25 years.
A well designed application is one that is easy to develop, test and maintain but for creating such type of application some sort of separation and encapsulation methodology is needed. A common approach is to separate the UI with the business logic so that they are loosely coupled. It gives you flexibility to change your UI without change in business code logic
Model View ViewModel is an application design pattern that allows a developer to completely separate their business logic from UI dependencies. This also makes an application more clean and easy to test.
Model
Model represents the entity classes used in application. It represents the actual data we will be dealing with like Contact (contact name, address, email, phone no. etc.). The Model holds the information along with business and validation logic. It is not responsible for any UI related changes like formatting a text or to make a button look nice. Business logic is encapsulated in other classes that acts on model. This may not always be true. Model may contain validations
View
The View represents the UI (Window, Page, and UserControl), and the ViewModel exposes commands and bindable data that View binds to.
ViewModel
The ViewModel has no reference to Views- it holds only a reference to the Model. User interactions with the View triggers Commands in ViewModel and updates in ViewModel are propagated to View using Binding. It helps to maintain the state of View, manipulate the Model as a result of action on View.
Using the code
Implementing a simple MVVM application.
Let’s start by creating a solution with below structure
I prefer using this structure as it helps to understand more easily.
In the View folder we will be placing the Views i.e. UI related xaml files.
In the Model folder we will be placing the entity classes.
In the ViewModel we will place the ViewModel classes for the Views. A View has one ViewModel corresponding to it but a ViewModel can have multiple Views using it.
Helper folder will contain the Command classes and other useful classes that can be used as helper.
The end product of this article will be a simple application that display all the directories and file in a selected folder into a tree view where you can navigate and open the file/folder. It will look as below
Click open to directly open the file or folder.
Model
Model will have below classes:
GenericRecord class
This class will act as base class and will implement INotifyPropertyChanged interface and can also contain virtual members that can be overridden in derived classes. We will inherit this class on our model classes.
Drive Class
- Drive class has below properties:
- DriveName
- ObservableCollection Of Folders
- ObservableCollection Of Files
Folder Class
- Folder class has below properties
- FolderName
- FolderPath
- ObservableCollection Of Folders
- ObservableCollection Of Files
File Class
- File class has below properties
GenericRecord.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace EasyNavigate.Model
{
class GenericRecord : INotifyPropertyChanged
{
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Drive.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace EasyNavigate.Model
{
class Drive : GenericRecord
{
string _DriveName;
public string DriveName
{
get { return _DriveName; }
set { _DriveName = value; RaisePropertyChanged("DriveName"); }
}
private ObservableCollection<Folder> _Folders;
public ObservableCollection<Folder> Folders
{
get { return _Folders; }
set { _Folders = value; RaisePropertyChanged("Folders"); }
}
private ObservableCollection<Model.File> _Files;
public ObservableCollection<Model.File> Files
{
get { return _Files; }
set { _Files = value; RaisePropertyChanged("Files"); }
}
}
}
Folder.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace EasyNavigate.Model
{
class Folder : GenericRecord
{
string _FolderName;
public string FolderName
{
get { return _FolderName; }
set { _FolderName = value; RaisePropertyChanged("FolderName"); }
}
string _FolderPath;
public string FolderPath
{
get { return _FolderPath; }
set { _FolderPath = value; RaisePropertyChanged("FolderPath"); }
}
private ObservableCollection<Folder> _Folders;
public ObservableCollection<Folder> Folders
{
get { return _Folders; }
set { _Folders = value; RaisePropertyChanged("Folders"); }
}
private ObservableCollection<Model.File> _Files;
public ObservableCollection<Model.File> Files
{
get { return _Files; }
set { _Files = value; RaisePropertyChanged("Files"); }
}
}
}
File.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace EasyNavigate.Model
{
class File : GenericRecord
{
string _FileName;
public string FileName
{
get { return _FileName; }
set { _FileName = value; RaisePropertyChanged("FileName"); }
}
string _FilePath;
public string FilePath
{
get { return _FilePath; }
set { _FilePath = value; RaisePropertyChanged("FilePath"); }
}
}
}
Helper
Relay command class implements ICommand interface for command handling. This classic wpf concept will allow you to bind the command for actions like button click. You will not be writing glue code for button click event instead you will bind the action that will happen on click of it, to some command that should execute.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace EasyNavigate.Helpers
{
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
}
View
The DataContext of window is set to ViewModelMainWindow class through UI itself as below
<Window.DataContext>
<vm:ViewModelMainWindow/>
</Window.DataContext>
You can set the same using code behind inside MainWindow constructor as below:
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModelMainWindow();
grpBoxOpenFolderOrFile.Visibility = Visibility.Collapsed;
}
MainWindow.xaml
The UI displays 4 section:
- Available logical drives dropdown in left
- Once you select the drive it will display all available folders in next section on the right
- The third section will display all the files and folders in selected folder, in a treeview format
- The fourth section on bottom will display a textbox and Open button
Code Behind i.e MainWindow.xaml.cs
Code behind will handle the ComboBox selection changed events
- cmb_Drives_SelectionChanged
- cmbFolder_SelectionChanged
Open button is binded to OpenSelectedItemCommand
treeViewMenu selected Item Changed event
- trvMenu_SelectedItemChanged
These event handler will call the appropriate logic written in code behind file using the data context.
Below is the xaml file for the UI, The drives combo box is binded to CollecDrive collection, the folders combo box is binded to CollecFolder collection defined in ViewModelMainWindow class.
The folder details section is a treeview whose items are binded to Items collection . This will be populated based on selected drive and selected folder.
<Window x:Class="EasyNavigate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:EasyNavigate.ViewModel"
Title="Easy Navigate v1.0" Height="550" Width="650">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<GroupBox Margin="12,10,12,12" Header="Drive" HorizontalAlignment="Left" VerticalAlignment="Top" Height="50" Width="251" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock FontWeight="Bold" Text="Available Logical Drive(s):"/>
<ComboBox x:Name="cmb_Drives" ItemsSource="{Binding CollecDrive}" SelectedItem="{Binding SelectedDrive}" DisplayMemberPath="DriveName" Margin="5,0,0,5" VerticalAlignment="Top" Height="22" Width="45" SelectionChanged="cmb_Drives_SelectionChanged" />
</StackPanel>
</GroupBox>
<GroupBox Margin="12,10,12,12" Header="Folder" HorizontalAlignment="Right" VerticalAlignment="Top" Height="50" Width="251" Grid.Row="0" Grid.Column="1" Visibility="{Binding IsFolderGrpBxVisible}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock FontWeight="Bold" Text="Select a Folder:"/>
<ComboBox x:Name="cmbFolder" ItemsSource="{Binding CollecFolder}" SelectedItem="{Binding SelectedFolder}" DisplayMemberPath="FolderName" Margin="5,0,0,5" VerticalAlignment="Top" Height="22" Width="126" SelectionChanged="cmbFolder_SelectionChanged"/>
</StackPanel>
</GroupBox>
<GroupBox x:Name="grpBoxFolderViewDetails" Margin="12,10,12,12" Header="Folder Details" HorizontalAlignment="Left" Height="300" Width="600" VerticalAlignment="Top" Grid.Row="1" Grid.ColumnSpan="2" Visibility="{Binding IsFolderDetailsGrpBxVisible}" >
<TreeView Name="trvMenu" SelectedItemChanged="trvMenu_SelectedItemChanged">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:MenuItem}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</GroupBox>
<GroupBox x:Name="grpBoxOpenFolderOrFile" Margin="12,10,12,12" Header="Open File/Folder" HorizontalAlignment="Left" VerticalAlignment="Top" Height="80" Width="380" Grid.Row="2" Grid.ColumnSpan="2" Visibility="{Binding IsFolderDetailsGrpBxVisible}" >
<StackPanel Orientation="Horizontal">
<TextBox x:Name="txtSelectedItemPath" Text="{Binding SelectedItemPath}" Margin="5,5,5,5" Height="30" Width="300"/>
<Button x:Name="btnOpen" Content="Open" Command="{Binding OpenSelectedItemCommand}" Margin="5,5,5,5" Width="50" Height="25"/>
</StackPanel>
</GroupBox>
</Grid>
</Window>
MainWindow.xaml.cs
cmb_Drives_SelectionChanged event in this class will handle what happens on selecting a drive from dropdown. As soon as you select a drive from the dropdown, this event will call HandleCmbDriveSelectionChanged method in view model which will return all the folders and files in the selected drive and will populate CollecFolder collection which notify to the UI using RaisePropertyChanged.
cmbFolder_SelectionChanged event will handle what happens on selecting a folder from the dropdown.
As soons as you select a folder from the dropdown, this event will call HandleCmbFolderSelectionChanged method defined in view model. This method will return a the selected folder which contain collection of sub-folders iteratively and all the files within selected folder.
Based on the selected folder, we will build the menu item for the treeview using BuildMenuItem method and add that menu item to the tree view.
Selecting the items on the tree view will call trvMenu_SelectedItemChanged through which we will get the path of selected item in our system using getPathFromParentItem method in the view model and bind it to text property of textbox.
Note, UI related action are handled in code behind but from there it goes to view model for all the logic.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using EasyNavigate.ViewModel;
using System.Collections.ObjectModel;
using EasyNavigate.Model;
using System.Diagnostics;
namespace EasyNavigate
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModelMainWindow();
grpBoxOpenFolderOrFile.Visibility = Visibility.Collapsed;
}
private void cmb_Drives_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var vm=this.DataContext as ViewModelMainWindow;
vm.HandleCmbDriveSelectionChanged();
grpBoxOpenFolderOrFile.Visibility = Visibility.Collapsed;
}
private void cmbFolder_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
trvMenu.Items.Clear();
var vm = this.DataContext as ViewModelMainWindow;
Folder folderContent=vm.HandleCmbFolderSelectionChanged();
if (folderContent != null)
{
EasyNavigate.ViewModel.MenuItem root = vm.BuildMenuItem(folderContent,null);
trvMenu.Items.Add(root);
grpBoxFolderViewDetails.Visibility = Visibility.Visible;
}
else
{
grpBoxFolderViewDetails.Visibility = Visibility.Collapsed;
}
grpBoxOpenFolderOrFile.Visibility = Visibility.Collapsed;
}
private void trvMenu_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var vm = this.DataContext as ViewModelMainWindow;
vm.SelectedItem = (sender as TreeView).SelectedItem as EasyNavigate.ViewModel.MenuItem;
if (vm.SelectedItem != null)
{
vm.SelectedItemPath = vm.SelectedDrive.DriveName + ":\\" + vm.getPathFromParentItem(vm.SelectedItem);
grpBoxOpenFolderOrFile.Visibility = Visibility.Visible;
}
}
}
}
ViewModel
ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace EasyNavigate.ViewModel
{
class ViewModelBase : INotifyPropertyChanged
{
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
ViewModelMain.cs
This class contains the properties that we are going to expose and bind to the View.
This class contains the logic that will be executed when user performs some action on the UI. The code here is self explanatory. Request you to please go through to it once.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using EasyNavigate.Model;
using EasyNavigate.Helpers;
using System.Diagnostics;
namespace EasyNavigate.ViewModel
{
class ViewModelMainWindow: ViewModelBase
{
public RelayCommand OpenSelectedItemCommand { get; set; }
ObservableCollection<Drive> _CollecDrive;
public ObservableCollection<Drive> CollecDrive
{
get { return _CollecDrive; }
set { _CollecDrive = value; RaisePropertyChanged("CollecDrive"); }
}
ObservableCollection<Folder> _CollecFolder;
public ObservableCollection<Folder> CollecFolder
{
get { return _CollecFolder; }
set { _CollecFolder = value; RaisePropertyChanged("CollecFolder"); }
}
Visibility _IsFolderGrpBxVisible;
public Visibility IsFolderGrpBxVisible
{
get { return _IsFolderGrpBxVisible; }
set { _IsFolderGrpBxVisible = value; RaisePropertyChanged("IsFolderGrpBxVisible"); }
}
private Visibility _IsFolderDetailsGrpBxVisible;
public Visibility IsFolderDetailsGrpBxVisible
{
get { return _IsFolderDetailsGrpBxVisible; }
set { _IsFolderDetailsGrpBxVisible = value; RaisePropertyChanged("IsFolderDetailsGrpBxVisible"); }
}
private Drive _SelectedDrive;
public Drive SelectedDrive
{
get { return _SelectedDrive; }
set { _SelectedDrive = value; RaisePropertyChanged("SelectedDrive"); }
}
private Folder _SelectedFolder;
public Folder SelectedFolder
{
get { return _SelectedFolder; }
set { _SelectedFolder = value; RaisePropertyChanged("SelectedFolder"); }
}
public ObservableCollection<Drive> getAllDrivesInSystem()
{
string[] arrDrives = System.IO.Directory.GetLogicalDrives();
ObservableCollection<Drive> drives = new ObservableCollection<Drive>();
if (arrDrives != null && arrDrives.Length > 0)
{
foreach (string s in arrDrives)
{
string temp = null;
if (s != null && s.Contains(":") && !s.Contains("C"))
{
int indexOfColon = s.IndexOf(":");
temp = s.Remove(indexOfColon);
Drive newDrive = new Drive();
newDrive.DriveName = temp;
ObservableCollection<Folder> foldersInDrive= getAllFoldersInDriveOrFolder(newDrive,null);
newDrive.Folders=foldersInDrive;
ObservableCollection<EasyNavigate.Model.File> filesInDrive= getAllFilesInDriveOrFolder(newDrive,null);
newDrive.Files = filesInDrive;
drives.Add(newDrive);
}
}
}
return drives;
}
private ObservableCollection<Model.File> getAllFilesInDriveOrFolder(Drive drive,Folder folder)
{
ObservableCollection<Model.File> files = new ObservableCollection<Model.File>();
if (folder == null)
{
System.IO.DriveInfo di = new System.IO.DriveInfo(drive.DriveName + ":\\");
System.IO.DirectoryInfo dirInfo = di != null ? di.RootDirectory : null;
DirectoryInfo[] dirInfoFolders = dirInfo != null ? dirInfo.GetDirectories() : null;
FileInfo[] fileInfo = dirInfo != null ? dirInfo.GetFiles() : null;
if (fileInfo != null && fileInfo.Length > 0)
{
foreach (FileInfo item in fileInfo)
{
FileAttributes attributes = item.Attributes;
string attrNames = Convert.ToString(attributes);
if (attrNames != null && !attrNames.Contains("Hidden"))
{
Model.File file = new Model.File();
file.FileName = item.Name;
files.Add(file);
}
}
}
}
else
{
string[] filesInFolder = Directory.GetFiles(folder.FolderPath);
if (filesInFolder != null && filesInFolder.Length > 0)
{
foreach (string item in filesInFolder)
{
int folderPathLength = folder.FolderPath.Length;
string fileInFolder = null;
fileInFolder = item.Remove(0, folderPathLength + 1);
Model.File file = new Model.File();
file.FileName = fileInFolder;
file.FilePath = item;
files.Add(file);
}
}
}
return files;
}
public ObservableCollection<Folder> getAllFoldersInDriveOrFolder(Drive drive ,Folder folder)
{
ObservableCollection<Folder> folders = new ObservableCollection<Folder>();
if (folder == null)
{
System.IO.DriveInfo di = new System.IO.DriveInfo(drive.DriveName + ":\\");
System.IO.DirectoryInfo dirInfo = di != null ? di.RootDirectory : null;
DirectoryInfo[] dirInfoFolders = dirInfo != null ? dirInfo.GetDirectories() : null;
FileInfo[] fileInfo = dirInfo != null ? dirInfo.GetFiles() : null;
if (dirInfoFolders != null && dirInfoFolders.Length > 0)
{
foreach (DirectoryInfo item in dirInfoFolders)
{
FileAttributes attributes = item.Attributes;
string attrNames = Convert.ToString(attributes);
if (attrNames != null && !attrNames.Contains("Hidden"))
{
Folder newFolder = new Folder();
newFolder.FolderName = item.Name;
newFolder.FolderPath=drive.DriveName+":\\"+ newFolder.FolderName;
ObservableCollection<Folder> subFolders = getAllFoldersInDriveOrFolder(drive, newFolder);
newFolder.Folders = subFolders;
ObservableCollection<Model.File> files = getAllFilesInDriveOrFolder(drive, newFolder);
newFolder.Files = files;
folders.Add(newFolder);
}
}
}
}
else
{
string [] subFolders = Directory.GetDirectories(folder.FolderPath);
Folder subfolder= new Folder();
if(subFolders!=null && subFolders.Length>0)
{
foreach (string item in subFolders)
{
int folderPathLength = folder.FolderPath.Length;
string fldr=null;
fldr = item.Remove(0, folderPathLength+1);
subfolder= new Folder();
subfolder.FolderName = fldr;
subfolder.FolderPath = item;
subfolder.Folders=getAllFoldersInDriveOrFolder(drive,subfolder);
subfolder.Files = getAllFilesInDriveOrFolder(drive,subfolder);
folders.Add(subfolder);
}
}
}
return (folders != null && folders.Count > 0) ? folders : null;
}
public ViewModelMainWindow()
{
IsFolderDetailsGrpBxVisible = Visibility.Collapsed;
CollecDrive = getAllDrivesInSystem();
OpenSelectedItemCommand = new RelayCommand(OpenSelectedItem);
}
public void HandleCmbDriveSelectionChanged()
{
CollecFolder = getAllFoldersInDriveOrFolder(SelectedDrive,null);
}
public Folder HandleCmbFolderSelectionChanged()
{
if (SelectedFolder != null)
{
IsFolderDetailsGrpBxVisible = Visibility.Visible;
}
else
{
IsFolderDetailsGrpBxVisible = Visibility.Collapsed;
}
return SelectedFolder;
}
private MenuItem _SelectedItem;
public MenuItem SelectedItem
{
get { return _SelectedItem; }
set { _SelectedItem = value; }
}
public ViewModel.MenuItem BuildMenuItem(Folder folderContent, MenuItem parentMenuItem)
{
EasyNavigate.ViewModel.MenuItem root = new EasyNavigate.ViewModel.MenuItem() { Title = folderContent.FolderName, parentItem = parentMenuItem };
ObservableCollection<Folder> foldersInFolder = folderContent.Folders;
ObservableCollection<EasyNavigate.Model.File> filesInFolder = folderContent.Files;
if (foldersInFolder != null && foldersInFolder.Count > 0 && ((filesInFolder == null) || filesInFolder.Count == 0))
{
for (int i = 0; i < foldersInFolder.Count; i++)
{
EasyNavigate.ViewModel.MenuItem item = BuildMenuItem(foldersInFolder[i], root);
root.Items.Add(item);
}
}
if ((foldersInFolder == null || foldersInFolder.Count == 0) && filesInFolder != null && filesInFolder.Count > 0)
{
for (int i = 0; i < filesInFolder.Count; i++)
{
MenuItem item = BuildMenuItem(filesInFolder[i], root);
root.Items.Add(item);
}
}
if (foldersInFolder != null && foldersInFolder.Count > 0 && filesInFolder != null && filesInFolder.Count > 0)
{
bool itemsAdded = false;
for (int i = 0; (i < foldersInFolder.Count + filesInFolder.Count && !itemsAdded); i++)
{
if (i >= foldersInFolder.Count)
{
for (int j = 0; j < filesInFolder.Count; j++)
{
ViewModel.MenuItem item = BuildMenuItem(filesInFolder[j], root);
root.Items.Add(item);
itemsAdded = true;
}
}
else
{
EasyNavigate.ViewModel.MenuItem item = BuildMenuItem(foldersInFolder[i],root);
root.Items.Add(item);
}
}
}
return root;
}
public ViewModel.MenuItem BuildMenuItem(EasyNavigate.Model.File file, MenuItem parentMenuItem)
{
EasyNavigate.ViewModel.MenuItem root = new EasyNavigate.ViewModel.MenuItem() { Title = file.FileName, parentItem = parentMenuItem };
return root;
}
public string getPathFromParentItem(ViewModel.MenuItem menuItem)
{
string path = "";
if (menuItem != null && menuItem.parentItem == null)
{
path = menuItem.Title;
return path;
}
else
{
path = getPathFromParentItem(menuItem.parentItem) +"\\"+ menuItem.Title;
}
return path;
}
private string _SelectedItemPath;
public string SelectedItemPath
{
get { return _SelectedItemPath; }
set { _SelectedItemPath = value; RaisePropertyChanged("SelectedItemPath"); }
}
void OpenSelectedItem(object parameter)
{
if (SelectedItemPath != null)
{
Process.Start("explorer.exe", SelectedItemPath);
}
}
}
}
MenuItem.cs
This class is user-defined class for our own purpose for building the menu item control.
Each menu item will have pointer to it's parent menu item using parentItem property. For the root most menu item it will be null.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
namespace EasyNavigate.ViewModel
{
public class MenuItem
{
public MenuItem()
{
this.Items = new ObservableCollection<MenuItem>();
}
public string Title { get; set; }
public ObservableCollection<MenuItem> Items { get; set; }
public MenuItem parentItem { get; set; }
}
}
Points of Interest
I built this sample application from a learning point of view. While implementing this application I learned about xaml syntax, TreeView , MenuItem, Command binding, data binding etc.
Please feel free for any queries with this article.
Please comment if you find any error/bug/or anything that can improve it. Suggestion are always welcome.
Be happy and Keep learning.
History
Added a base class in Model.
Attached complete sample project.
Added References.
References
https://blogs.msdn.microsoft.com/jerrynixon/2012/10/12/xaml-binding-basics-101/
http://social.technet.microsoft.com/wiki/contents/articles/13536.easy-mvvm-examples-in-extreme-detail.aspx
https://blogs.msdn.microsoft.com/dancre/2006/10/11/datamodel-view-viewmodel-pattern-series/