Pages

Sunday, March 15, 2015

Simple MVVM implementation in WPF

I have seen that people usually struggle with MVVM pattern in WPF and most of the people code WPF as similar to Windows forms application. It is a wrong implementation. We need to use all the features of WPF so that we can benefit from WPF. We also can admire the beauty of the programming excellence in WPF.

In this article I will demonstrate a small example of how to implement MVVM in WPF.

Introduction
MVVM is an GUI architectural design pattern.It is an extension of MVC pattern. It is derived from Martin Fowler's Presentation Model.The Idea behind MVVM or MVC or MVP is to remove the dependency on code behind files.MVVM is helps in developing loosely coupled applications in WPF. The main components of MVVM are:-

1. Model :- This is the entity class which will have the data.
2. View :-  This is nothing but the xaml.
3. ViewModel :- The view model is where we have the business logic and we bind the view and the model.


In the following example we will bind data to a ListView using MVVM model.Along with that we will also bind the title of the  WPF Window from the view model.


The xaml code is as follows:- 

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="{Binding Title}" Height="400" Width="525">
    <Grid>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="726*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="350"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0" Grid.ColumnSpan="1" Margin="5 5 5 5">
            <ListView Name="dtgrid" Grid.Row="0" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.CanContentScroll="False" Style="{x:Null}" 
                      HorizontalAlignment="Stretch" VerticalAlignment="Top" ItemsSource="{Binding ItemsData}" Background="Transparent"
                     BorderBrush="Transparent">
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="Height" Value="53"></Setter>
                        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                        <Setter Property="HorizontalAlignment" Value="Stretch"/>
                        <Style.Triggers>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Background" Value="#ECF8F9"></Setter>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </ListView.ItemContainerStyle>
                <ListView.View>
                    <GridView>
                        <GridView.Columns>
                            <GridViewColumn Header="Item" Width="200">
                                <GridViewColumn.CellTemplate>
                                    <DataTemplate>
                                        <TextBlock x:Name="TxtBlockItem" Text="{Binding Path=Item}" VerticalAlignment="Top" Margin="10,0,0,0" TextTrimming="WordEllipsis"/>
                                    </DataTemplate>
                                </GridViewColumn.CellTemplate>
                            </GridViewColumn>
                            <GridViewColumn Header="Description" Width="320">
                                <GridViewColumn.CellTemplate>
                                    <DataTemplate>
                                        <ScrollViewer HorizontalScrollBarVisibility="Disabled" MaxHeight="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListViewItem}, Path=ActualHeight}" VerticalScrollBarVisibility="Auto">
                                            <TextBlock Name="txtDescription" Margin="10 0 0 0" Text="{Binding Path=Description,Mode=OneWay}" TextWrapping="Wrap"/>
                                        </ScrollViewer>
                                    </DataTemplate>
                                </GridViewColumn.CellTemplate>
                            </GridViewColumn>
                        </GridView.Columns>
                    </GridView>
                </ListView.View>
            </ListView>
            <Border BorderBrush="Black" Grid.RowSpan="2" BorderThickness="1" />
        </Grid>
     </Grid>
</Window>



The code behind is as follows:- 

/// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new ItemsViewModel();
        }
    }

In the constructor of the window we are bind the DataContext property with the ViewModel. 
We can do this in xaml also but I don't usually prefer to do it from xaml if we hard code in xaml then again there will be tight coupling between with the View and ViewModel. By setting the datacontext in the codebehind gives us the flexibility to change the data context whenever we want let's say I want a different data context to be bound to this window I can do that. 

In the xaml we are doing the binding using the syntax 

   Title="{Binding Title}"

Similarly we are binding the data from the ObservableCollection to the List View using the ItemsSource property as  ItemsSource="{Binding ItemsData}". Then we need to bind the individual properties to the textblock or what ever control is used in the ListView. In our example we have used a textblock and the Text property is binded as Text="{Binding Path=Item}".The point to be observed here is we need to specify the property prefixed by Path. So this says that in the Observable collection we have a property named Item. We have specified this inside into a DataTemplate of a <GridViewColumn.CellTemplate>.

                            <GridViewColumn Header="Item" Width="200">
                                <GridViewColumn.CellTemplate>
                                    <DataTemplate>
                                        <TextBlock x:Name="TxtBlockItem" Text="{Binding Path=Item}" VerticalAlignment="Top" Margin="10,0,0,0" TextTrimming="WordEllipsis"/>
                                    </DataTemplate>
                                </GridViewColumn.CellTemplate>
                            </GridViewColumn>



Now, moving on to the View Model. The code is as follows

public class ItemsViewModel : INotifyPropertyChanged
    {

        #region View Model Logic

        /// <summary>
        /// Observable Collection of  Items Model
        /// </summary>
        private ObservableCollection<ItemsModel> itemsData;

        public ObservableCollection<ItemsModel> ItemsData
        {
            get { return itemsData; }
            set
            {
                itemsData = value;

                Refresh("ItemsData");

            }
        }

        private string title = "WPF MVVM Demo";

        public string Title
        {
            get { return title; }
            set
            {
                title = value;
                Refresh("Title");
            }
        }

        public ItemsViewModel()
        {
            ItemsDAL dataAccessLayer = new ItemsDAL();
            List<ItemsModel> lstItemsModel = dataAccessLayer.LoadItems();
            PopulateItems(lstItemsModel);
        }

        ObservableCollection<ItemsModel> obColl = new ObservableCollection<ItemsModel>();
        private void PopulateItems(List<ItemsModel> lstItemsModel)
        {

            foreach (ItemsModel item in lstItemsModel)
            {
                obColl.Add(item);
            }
     
           ItemsData = obColl;
       }

        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The name of the changed property</param>
        private void Refresh(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                this.PropertyChanged(this, e);
            }
        }

        #endregion INotifyPropertyChanged Members
    }

We have inherited from the INotifyPropertyChanged interface. So that we can refresh a control when ever a property is changed. In the code the we have used Refresh("Title"); in the setter property so that whenever a value is changed we can do a refresh the value in the view. Similarly for the ItemsData Observable collection. 
We have the Refresh(string propertyName)method to check if property is changed if it is changed then we are doing a refresh.  The collection should always be an ObservableCollection.


The code for ItemsModel is as follows:- 

public class ItemsModel
    {
        private string item;

        public string Item
        {
            get { return item; }
            set { item = value; }
        }

        private string description;

        public string Description
        {
            get { return description; }
            set { description = value; }
        }
    }

It is just a model.

The refresh can be done in the ViewModel as well as Model but I prefer to do it in the ViewModel.

ItemsDAL is used here to mimic that the data is coming from data access layer.The code is as follows

public class ItemsDAL
    {
        public List<ItemsModel> LoadItems()
        {
            List<ItemsModel> lstItemsModel = new List<ItemsModel>();

            for (int i = 1; i <= 50; i++)
            {
                ItemsModel item = new ItemsModel();
                item.Item = "Item " + i;
                item.Description = "Description for Item " + i;
                lstItemsModel.Add(item);
            }

            return lstItemsModel;

        }
    }

So, here you go you have implemented a simple WPF application using MVVM.
The output looks as follows:- 






No comments:

Post a Comment