Quantcast
Viewing all articles
Browse latest Browse all 7205

Simple MVVM application with DXBars - Tutorial

This tutorial demonstrates how to implement a simple and flexible data management system by using the MVVM pattern for WPF platform.

Task
Implement a flexible data management system by using the MVVM pattern for WPF platform.

Input Data
A list of Person objects providing custom information (first name, last name, photo and email).

Additional Requirements
The management system should provide an ability to modify information on persons and add/delete persons. The system should prevent accidental modification of persons' data via visual controls.

Step 1. Creating Data Model.
First, we create a simple data model:

1. The Person class with a set of relevant fields:

[C#]
publicclassPerson{publicstringFirstName=string.Empty;publicstringLastName=string.Empty;publicUriPhoto=null;publicstringEmail=string.Empty;}

2. The Persons entity that is a collection of Person objects:

[C#]
publicclassPersons:ObservableCollection<Person>{}

3. Utility classes that help creating Person objects:

[C#]
publicstaticclassPersonCreator{publicstaticreadonlyPerson[]Person;staticPersonCreator(){Person=newPerson[4];Person[0]=newPerson(){//... };//... }}publicstaticclassPersonsCreator{publicstaticreadonlyPersonsPersons;staticPersonsCreator(){Persons=newPersons();foreach(PersonpersoninPersonCreator.Person)Persons.Add(person);}}

Step 2. Creating Form for Person Entity.
In this step we create a view model for the Person entity by using the MVVM pattern - the PersonViewModel class. This class implements the interaction between the data model and UI.

1. Create a base view-model class implementing the INotifyPropertyChanged interface

[C#]
publicclassViewModelBase:INotifyPropertyChanged{publiceventPropertyChangedEventHandlerPropertyChanged;protectedvirtualvoidOnPropertyChanged(stringpropertyName){if(PropertyChanged!=null)PropertyChanged(this,newPropertyChangedEventArgs(propertyName));}}

2. Create the PersonViewModel class by inheriting from ViewModelBase. The class declares properties which are in-sync with Person object's properties.

[C#]
publicclassPersonViewModel:ViewModelBase{publicStringFirstName{get{returnPerson.FirstName;}set{if(Person.FirstName==value)return;Person.FirstName=value;OnPropertyChanged("FirstName");}}publicPersonPerson{get;privateset;}publicPersonViewModel(Personperson){Person=person;}//... }

The PersonViewModel class also contains a non-model related property (IsReadOnly). This property prevents objects from being modified by an end-user.

[C#]
publicclassPersonViewModel:ViewModelBase{//...publicboolIsReadOnly{get{returnisReadOnly;}set{if(isReadOnly==value)return;isReadOnly=value;OnPropertyChanged("IsReadOnly");}}boolisReadOnly=false;//...}

3. Create a view form containing controls used to display and edit data.

[XML]
<UserControl...><!--...--><Grid><Grid.RowDefinitions><RowDefinitionHeight="*"/><RowDefinitionHeight="Auto"/></Grid.RowDefinitions><dxe:ImageEditSource="{Binding Photo}"IsReadOnly="True"/><StackPanelGrid.Row="1"Orientation="Vertical"HorizontalAlignment="Center"><WrapPanelHorizontalAlignment="Center"Margin="3"><TextBlockText="Fisrt Name:"VerticalAlignment="Center"/><dxe:TextEditText="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}"IsReadOnly="{Binding IsReadOnly}"Margin="5,0,0,0"/></WrapPanel><WrapPanelHorizontalAlignment="Center"Margin="3"><TextBlockText="Last Name:"VerticalAlignment="Center"/><dxe:TextEditText="{Binding LastName, UpdateSourceTrigger=PropertyChanged}"IsReadOnly="{Binding IsReadOnly}"Margin="5,0,0,0"/></WrapPanel><WrapPanelHorizontalAlignment="Center"Margin="3"><TextBlockText="Email:"VerticalAlignment="Center"/><dxe:TextEditText="{Binding Email, UpdateSourceTrigger=LostFocus}"IsReadOnly="{Binding IsReadOnly}"Margin="5,0,0,0"/></WrapPanel></StackPanel></Grid><!--...--></UserControl>

4. A helper PersonViewModelCreator class creates a PersonViewModel and populates it with data:

[C#]
publicstaticclassPersonViewModelCreator{publicstaticreadonlyPersonViewModel[]PersonViewModel;publicstaticPersonViewModelDesignPersonViewModel{get{returnPersonViewModel[0];}}staticPersonViewModelCreator(){intlength=PersonCreator.Person.Length;PersonViewModel=newPersonViewModel[length];for(inti= 0;i<length;i++)PersonViewModel[i]=newPersonViewModel(PersonCreator.Person[i]){IsReadOnly=true};}}

Initialize the UserControl.DataContext property with the view-model provided by PersonViewModelCreator :

[XML]
<UserControlx:Class="DXBarsAndMVVM.Views.PersonView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"xmlns:dDx="http://schemas.devexpress.com/winfx/2008/xaml/core"xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars"xmlns:hlp="clr-namespace:DXBarsAndMVVM.Helpers"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"mc:Ignorable="d dDx"dDx:ThemeManager.ThemeName="Office2007Silver"d:DesignHeight="300"d:DesignWidth="300"d:DataContext="{x:Static hlp:PersonViewModelCreator.DesignPersonViewModel}"><!--...--></UserControl>

5. Add an additional visual control element (BarCheckItem) to the view form that will be bound to the PersonViewModel.IsReadOnly property

[XML]
<UserControl...><dxb:BarManager><dxb:BarManager.Items><dxb:BarCheckItemName="cbIsReadOnly"Content="Locked"IsChecked="{Binding IsReadOnly}"/></dxb:BarManager.Items><dxb:BarManager.Bars><dxb:Bar><dxb:Bar.ItemLinks><dxb:BarCheckItemLinkBarItemName="cbIsReadOnly"/></dxb:Bar.ItemLinks></dxb:Bar></dxb:BarManager.Bars><Grid><!--editor controls--></Grid></dxb:BarManager></UserControl>

6. To test the project, add the following code to the main window

[XML]
<Windowx:Class="DXBarsAndMVVM.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:vw="clr-namespace:DXBarsAndMVVM.Views"xmlns:hlp="clr-namespace:DXBarsAndMVVM.Helpers"Title="MainWindow"Height="350"Width="525"DataContext="{x:Static hlp:PersonViewModelCreator.DesignPersonViewModel}"><Grid><vw:PersonView/></Grid></Window>

Step 3. Creating Form for Persons Entity.

1. Create a PersonsViewModel object that is a collection of PersonViewModel objects. The PersonsViewModel class is inherited from the LockableCollection<PersonViewMode> class defined in the DevExpress.Xpf.Core library. This class allows you to temporarily lock change notifications by using the BeginUpdate and EndUpdate methods. The BeginUpdate method suppresses the CollectionChanged event until the EndUpdate method is called. Once the EndUpdate method is called, the CollectionChanged event is raised.

[C#]
publicclassPersonsViewModel:LockableCollection<PersonViewModel>{publicPersonsPersons{get;privateset;}publicPersonsViewModel(Personspersons){Persons=persons;}}

2. When the Persons model collection is changed, it should be synchronized with the PersonsViewModel.

[C#]
publicclassPersonsViewModel:LockableCollection<PersonViewModel>{publicPersonsPersons{get;privateset;}publicPersonsViewModel(Personspersons){Persons=persons;Persons.CollectionChanged+=newNotifyCollectionChangedEventHandler(OnPersonsCollectionChanged);SyncCollection();}protectedvirtualvoidOnPersonsCollectionChanged(objectsender,NotifyCollectionChangedEventArgse){SyncCollection();}protectedvoidSyncCollection(){BeginUpdate();Clear();foreach(PersonpersoninPersons)Add(newPersonViewModel(person));EndUpdate();}}

3. To allow a user to select a model, new Selected and SelectedIndex properties are introduced. In addition, an indexer property is added that allows you to get PersonViewModel objects by Person objects.

[C#]
publicclassPersonsViewModel:LockableCollection<PersonViewModel>{//...publicPersonViewModelSelected{get{returnthis[SelectedIndex];}protectedset{SelectedIndex=IndexOf(value);}}publicintSelectedIndex{get{returnselectedIndex;}set{value=IsValidSelectedIndex(value)?value:-1;if(selectedIndex==value)return;selectedIndex=value;OnSelectedIndexChanged();}}publicPersonViewModelthis[Personperson]{get{if(person==null)returnnull;foreach(PersonViewModelviewModelinthis)if(viewModel.Person==person)returnviewModel;returnnull;}}protectedvirtualvoidOnSelectedIndexChanged(){OnPropertyChanged(newPropertyChangedEventArgs("Selected"));OnPropertyChanged(newPropertyChangedEventArgs("SelectedIndex"));}boolIsValidSelectedIndex(intselectedIndex){returnselectedIndex>= 0 &&selectedIndex<Count;}intselectedIndex=-1;//...}

4. Now we create a view containing a ListBox control.

[XML]
<ListBoxx:Name="list"ItemsSource="{Binding}"SelectedIndex="{Binding SelectedIndex}"><ListBox.ItemTemplate><DataTemplate><StackPanelOrientation="Horizontal"><TextBlockText="{Binding FirstName}"/><TextBlockText="{Binding LastName}"Margin="15,0,0,0"/></StackPanel></DataTemplate></ListBox.ItemTemplate></ListBox>

5. A helper PersonsViewModelCreator class creates a PersonsViewModel and populates it with data:

[C#]
publicclassPersonsViewModel:LockableCollection<PersonViewModel>{publicPersonsViewModel(Personpersons):this(persons,null){}internalPersonsViewModel(Personspersons,Func<Person,PersonViewModel>creatingMethod){NewPersonCommand=newPersonsCommand(OnNewPersonCommadExecute);DeletePersonCommand=newPersonsCommand(OnDeletePersonCommadExecute,OnDeletePersonCommadCanExecute);CreatingMethod=creatingMethod;Persons=persons;Persons.CollectionChanged+=newNotifyCollectionChangedEventHandler(OnPersonsCollectionChanged);SyncCollection();}protectedvoidSyncCollection(){BeginUpdate();Clear();foreach(PersonpersoninPersons){PersonViewModelpersonViewModel;if(CreatingMethod!=null)personViewModel=CreatingMethod(person);elsepersonViewModel=newPersonViewModel(person);Add(personViewModel);}EndUpdate();if(SelectedIndex==-1 &&Count> 0)SelectedIndex= 0;}Func<Person,PersonViewModel>CreatingMethod=null;}publicstaticclassPersonsViewModelCreator{publicstaticreadonlyPersonsViewModelPersonsViewModel;staticPersonsViewModelCreator(){PersonsViewModel=newPersonsViewModel(PersonsCreator.Persons,CreatingMethod);}staticPersonViewModelCreatingMethod(Personperson){foreach(PersonViewModelpersonViewModelinPersonViewModelCreator.PersonViewModel)if(personViewModel.Person==person)returnpersonViewModel;returnnewPersonViewModel(person);}}

The following code sets a DataContext for a view.

[XML]
<UserControlx:Class="DXBarsAndMVVM.Views.PersonsView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"xmlns:dDx="http://schemas.devexpress.com/winfx/2008/xaml/core"xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars"xmlns:hlp="clr-namespace:DXBarsAndMVVM.Helpers"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"mc:Ignorable="d dDx"dDx:ThemeManager.ThemeName="Office2007Silver"d:DesignHeight="200"d:DesignWidth="200"d:DataContext="{x:Static hlp:PersonsViewModelCreator.PersonsViewModel}"><!--...--></UserControl>

6. Create a command class that implements the ICommand interface

[C#]
publicclassPersonsCommand:ICommand{publiceventEventHandlerCanExecuteChanged{add{if(canExecute!=null)canExecuteChanged+=value;}remove{if(canExecute!=null)canExecuteChanged-=value;}}publicPersonsCommand(Action<object>execute):this(execute,null){}publicPersonsCommand(Action<object>execute,Func<object,bool>canExecute){if(execute==null)thrownewArgumentNullException("execute");this.execute=execute;this.canExecute=canExecute;}publicboolCanExecute(objectparameter){if(canExecute==null)returntrue;returncanExecute(parameter);}publicvoidExecute(objectparameter){execute(parameter);}publicvoidRaiseCanExecuteChanged(){if(canExecuteChanged!=null)canExecuteChanged(this,EventArgs.Empty);}Action<object>execute;Func<object,bool>canExecute;eventEventHandlercanExecuteChanged;}

Create commands that are used to work with the Person collection:

[C#]
publicclassPersonsViewModel:LockableCollection<PersonViewModel>{publicPersonsCommandNewPersonCommand{get;privateset;}publicPersonsCommandDeletePersonCommand{get;privateset;}//...publicPersonsViewModel(Personspersons):this(persons,null){}internalPersonsViewModel(Personspersons,Func<Person,PersonViewModel>creatingMethod){NewPersonCommand=newPersonsCommand(OnNewPersonCommadExecute);DeletePersonCommand=newPersonsCommand(OnDeletePersonCommadExecute,OnDeletePersonCommadCanExecute);//...}protectedvirtualvoidOnNewPersonCommadExecute(objectparemeter){Personperson=newPerson(){FirstName="First Name",LastName="Last Name",};Persons.Add(person);Selected=this[person];}protectedvirtualvoidOnDeletePersonCommadExecute(objectparemeter){intselectedIndex=SelectedIndex;Persons.Remove(Selected.Person);if(IsValidSelectedIndex(selectedIndex))SelectedIndex=selectedIndex;elseif(selectedIndex>=Count)SelectedIndex=Count- 1;elseif(selectedIndex< 0)selectedIndex= 0;}protectedvirtualboolOnDeletePersonCommadCanExecute(objectparemeter){returnIsValidSelectedIndex(SelectedIndex);}//...}

7. Add visual elements (BarButtonItems) to the form and bind them to corresponding commands.

[XML]
<dxb:BarManager><dxb:BarManager.Items><dxb:BarButtonItemName="btNew"Content="New"Glyph="/DXBarsAndMVVM;component/Images/Icons/new-16x16.png"Command="{Binding NewPersonCommand}"/><dxb:BarButtonItemName="btDelete"Content="Delete"Glyph="/DXBarsAndMVVM;component/Images/Icons/close-16x16.png"Command="{Binding DeletePersonCommand}"/></dxb:BarManager.Items><dxb:BarManager.Bars><dxb:Bar><dxb:Bar.ItemLinks><dxb:BarButtonItemLinkBarItemName="btNew"/><dxb:BarButtonItemLinkBarItemName="btDelete"/></dxb:Bar.ItemLinks></dxb:Bar></dxb:BarManager.Bars><!--...--></dxb:BarManager>

Step 4. Building GUI

1. Add DXTabControl with two tabs to the main window. The first tab will allow a end-user to select a record (a Person object) while the second tab will be used to edit selected record's properties:

[XML]
<Windowx:Class="DXBarsAndMVVM.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"xmlns:dDx="http://schemas.devexpress.com/winfx/2008/xaml/core"xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars"xmlns:vw="clr-namespace:DXBarsAndMVVM.Views"xmlns:hlp="clr-namespace:DXBarsAndMVVM.Helpers"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="dDx"dDx:ThemeManager.ThemeName="Office2007Silver"Title="MainWindow"Height="350"Width="525"DataContext="{x:Static hlp:PersonsViewModelCreator.PersonsViewModel}"><dx:DXTabControlSelectedIndex="0"><dx:DXTabControl.View><dx:TabControlMultiLineViewHeaderLocation="Bottom"/></dx:DXTabControl.View><dx:DXTabItemContent="{Binding}"Header="Persons"><dx:DXTabItem.ContentTemplate><DataTemplate><vw:PersonsView/></DataTemplate></dx:DXTabItem.ContentTemplate></dx:DXTabItem><dx:DXTabItemContent="{Binding Selected}"Header="{Binding Selected}"><dx:DXTabItem.HeaderTemplate><DataTemplate><StackPanelOrientation="Horizontal"><TextBlockText="Details:"/><TextBlockText="{Binding FirstName}"Margin="5,0,0,0"/><TextBlockText="{Binding LastName}"Margin="5,0,0,0"/></StackPanel></DataTemplate></dx:DXTabItem.HeaderTemplate><dx:DXTabItem.ContentTemplate><DataTemplate><vw:PersonView/></DataTemplate></dx:DXTabItem.ContentTemplate></dx:DXTabItem></dx:DXTabControl></Window>

2. Tabs contain PersonsView and PersonView objects. Each view defines its own BarManager with a Bar. So, when switching between tabs, you will see a bar within a tab page. It is handy to have a single bar at the top of the main form instead. This can be implemented by using DXBars merging features.
To accomplish this, add BarManager with a bar at the top of the main window.

[XML]
<dxb:BarManager><dxb:BarManager.Bars><dxb:Barx:Name="MainBar"Caption="Bar"/></dxb:BarManager.Bars><!--...--></dxb:BarManager>

Here is the complete code of the main window:

[XML]
<Windowx:Class="DXBarsAndMVVM.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"xmlns:dDx="http://schemas.devexpress.com/winfx/2008/xaml/core"xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars"xmlns:vw="clr-namespace:DXBarsAndMVVM.Views"xmlns:hlp="clr-namespace:DXBarsAndMVVM.Helpers"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="dDx"dDx:ThemeManager.ThemeName="Office2007Silver"Title="MainWindow"Height="350"Width="525"DataContext="{x:Static hlp:PersonsViewModelCreator.PersonsViewModel}"><dxb:BarManager><dxb:BarManager.Bars><dxb:Barx:Name="MainBar"Caption="Bar"/></dxb:BarManager.Bars><dx:DXTabControlSelectedIndex="0"><dx:DXTabControl.View><dx:TabControlMultiLineViewHeaderLocation="Bottom"/></dx:DXTabControl.View><dx:DXTabItemContent="{Binding}"Header="Persons"><dx:DXTabItem.ContentTemplate><DataTemplate><vw:PersonsViewLoaded="PersonsView_Loaded"/></DataTemplate></dx:DXTabItem.ContentTemplate></dx:DXTabItem><dx:DXTabItemContent="{Binding Selected}"Header="{Binding Selected}"><dx:DXTabItem.HeaderTemplate><DataTemplate><StackPanelOrientation="Horizontal"><TextBlockText="Details:"/><TextBlockText="{Binding FirstName}"Margin="5,0,0,0"/><TextBlockText="{Binding LastName}"Margin="5,0,0,0"/></StackPanel></DataTemplate></dx:DXTabItem.HeaderTemplate><dx:DXTabItem.ContentTemplate><DataTemplate><vw:PersonViewLoaded="PersonView_Loaded"/></DataTemplate></dx:DXTabItem.ContentTemplate></dx:DXTabItem></dx:DXTabControl></dxb:BarManager></Window>

The code-behind class:

[C#]
publicpartialclassMainWindow:Window{publicMainWindow(){InitializeComponent();}voidPersonsView_Loaded(objectsender,RoutedEventArgse){PersonsViewpersonsView=(PersonsView)sender;MainBar.UnMerge();MainBar.Merge(personsView.ChildBar);}voidPersonView_Loaded(objectsender,RoutedEventArgse){PersonViewpersonView=(PersonView)sender;MainBar.UnMerge();MainBar.Merge(personView.ChildBar);}}

Conclusion
MVVM provides a flexible way to write complex GUI systems. This tutorial helps you understand the basic principles of writing applications by using the MVVM pattern. The use of the DXBars component will help you add an efficient navigation UI to your applications.


Viewing all articles
Browse latest Browse all 7205

Trending Articles