Now that we got the very basics let's start doing something meaningful
As noticed in my previous tutorial the use of ObservableCollection is recommended everywhere on the net and is indeed one of the first ways to solve a very common problem of the MVVM pattern. The problem is that you actually lose some nice features and also can't actually send update to the control when one of the elements of the collection changed while the elements count remain intact. In order to solve that problem i came to the idea of adding the 'ObservableList'. It basically does the same as the ObservableCollection but it supports everything the ObservableCollection shoud've supported to begin with.
So let's start by using our previous project and do some changes in order to make it look better. I personally like the use of folders to store my logic so our first step is going to be move the classes where they actually belong:
1. Create a folder called 'Behaviours' and drop the EventBehaviour.cs there.
2. Create a folder called 'Interfaces' and drop the 'IManageable.cs' there.
3. Create another folder called 'ViewModels' and drop the MainViewModel.cs, ViewModelBase.cs and ViewModelManager.cs.
Now you can also fix the namespaces if you wish... so now it should look like this:
Now let's create another folder called 'Common' and a sub-folder called 'Collections' and let's create the OversableList. Here is the new common re-usable class to solve the problem:
Now that we added that class let's start doing something great now and let's implement a semi-advanced listbox with a picture and the name of the picture in a listbox. How do we do that? in order to accomplish that you need to think first what you need. As mentioned we have the following:
1. Image that is going to hold a picture.
2. A label to hold the name of the picture.
Simply enough right? now... in a codebehind enviroment you are going to start by loading the files then loading the picture and doing some crazy stuff just to put your picture inside your list. Why making your life that complicated? let's start doing it in a different way . First of all we need a class that is going to represent one item. That class is going to hold a Uri that points to the picture and a string to hold the name of it so let's get started!
Start by creating a class in your MainViewModel.cs or just create another Folder called 'Classes' and store it there if you wish. In this particular case i'm going to go straight ahead and just create the class in the MainViewModel.cs file but before i'm just going to delete all the previous code except the empty constructor. I'm also going to proceed and delete all the controls of the XAML as well.
Now my MainViewModel.cs should look like this:
Now that we got the item let's create a collection in our MainViewModel that is going to contain all the items we want to load in our list. Start by adding the 'using MVVMTutorial.Common.Collections;' namespace so that our new collection class is recognized and add the following to create your collection:
As i previously showed all i do here is create a private variable that is going to hold my collection. As the type i use my ObservableList and as a itemType i use the PictureItem class i just recently wrote. With that in mind let's go ahead and create a method that is going to load pictures from a specified path. Let's start by adding the 'System.IO' so that we can read the content of a specified folder like this:
As you can see i modify the private variable and after i'm done i notify the changes. This is very important to increase performance because if you use the public member when adding items is going to send update after adding every item causing the control to re-draw over and over. Now let's switch back to our main XAML and add a Listbox control in order to display the pictures:
Now run the application and everything should compile just fine... after running the application it should look like this:
As you can see the code works but what's wrong? what you see is just a visual representation of items and since the listbox doesn't know how to handle them it just display the string. However as we can clearly see each item is a PictureItem! in our next step let's go ahead and teach the Listbox how to deal with that special item type. To do that we're going to modify the Listbox ItemTemplate property container and teach the control how to propertly display each item. To do that we're going to need the following controls in our template:
1. Grid which is the holder of the content. The grid column definition is going to be also declared so that one portion is going to be the image and the rest the label.
2. A image control with a pixel of 50x50
3. A label control
To do that modify the Listbox XAML like this:
As you can see i modified the ItemTemplate by using the mentioned controls and added each control binding in order to display the desired content.
So now when we run our application it should look like this if the path is correct:
We are now free to modify the private variable and when we're done just notify the changes and we can interact with them anytime!
----------------------------------------------------------------------------------------------
The tutorial doesn't end here... i will complete it when i get enough time tomorrow. In the mean time it should keep those interested busy for a while also attached the latest tutorial project for those interested.
Regards
@ruantec
As noticed in my previous tutorial the use of ObservableCollection is recommended everywhere on the net and is indeed one of the first ways to solve a very common problem of the MVVM pattern. The problem is that you actually lose some nice features and also can't actually send update to the control when one of the elements of the collection changed while the elements count remain intact. In order to solve that problem i came to the idea of adding the 'ObservableList'. It basically does the same as the ObservableCollection but it supports everything the ObservableCollection shoud've supported to begin with.
So let's start by using our previous project and do some changes in order to make it look better. I personally like the use of folders to store my logic so our first step is going to be move the classes where they actually belong:
1. Create a folder called 'Behaviours' and drop the EventBehaviour.cs there.
2. Create a folder called 'Interfaces' and drop the 'IManageable.cs' there.
3. Create another folder called 'ViewModels' and drop the MainViewModel.cs, ViewModelBase.cs and ViewModelManager.cs.
Now you can also fix the namespaces if you wish... so now it should look like this:
Now let's create another folder called 'Common' and a sub-folder called 'Collections' and let's create the OversableList. Here is the new common re-usable class to solve the problem:
Code:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMTutorial.Common.Collections
{
public interface IObservableList<T> : IList<T>, INotifyCollectionChanged { }
public class ObservableList<T> : List<T>, IObservableList<T>, INotifyPropertyChanged
{
public bool IsNotifying {get; set;}
public event PropertyChangedEventHandler PropertyChanged;
//////////////////////////////////////////////////////////////////////////////
// Notify handling
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
//////////////////////////////////////////////////////////////////////////////
public ObservableList()
{
IsNotifying = true;
// interface to notify about Count changes.
CollectionChanged += delegate { OnPropertyChanged("Count"); };
}
public ObservableList(IEnumerable<T> collection)
{
IsNotifying = true;
// interface to notify about Count changes.
CollectionChanged += delegate { OnPropertyChanged("Count"); };
if (collection != null)
AddRange(collection);
}
public new void Add(T item)
{
base.Add(item);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item);
OnCollectionChanged(e);
}
public new void AddRange(IEnumerable<T> collection)
{
base.AddRange(collection);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(collection));
OnCollectionChanged(e);
}
public new void Clear()
{
base.Clear();
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(e);
}
public new void Insert(int i, T item)
{
base.Insert(i, item);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item);
OnCollectionChanged(e);
}
public new void InsertRange(int i, IEnumerable<T> collection)
{
base.InsertRange(i, collection);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection);
OnCollectionChanged(e);
}
public new void Remove(T item)
{
base.Remove(item);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item);
OnCollectionChanged(e);
}
public new void RemoveAll(Predicate<T> match)
{
var backup = FindAll(match);
base.RemoveAll(match);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, backup);
OnCollectionChanged(e);
}
public new void RemoveAt(int i)
{
T backup = this[i];
base.RemoveAt(i);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, backup);
OnCollectionChanged(e);
}
public new void RemoveRange(int index, int count)
{
List<T> backup = GetRange(index, count);
base.RemoveRange(index, count);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, backup);
OnCollectionChanged(e);
}
public new T this[int index]
{
get { return base[index]; }
set
{
T oldValue = base[index];
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue);
OnCollectionChanged(e);
}
}
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (IsNotifying && CollectionChanged != null)
try
{
CollectionChanged(this, e);
}
catch (System.NotSupportedException)
{
var alternativeEventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(alternativeEventArgs);
}
}
}
}
Now that we added that class let's start doing something great now and let's implement a semi-advanced listbox with a picture and the name of the picture in a listbox. How do we do that? in order to accomplish that you need to think first what you need. As mentioned we have the following:
1. Image that is going to hold a picture.
2. A label to hold the name of the picture.
Simply enough right? now... in a codebehind enviroment you are going to start by loading the files then loading the picture and doing some crazy stuff just to put your picture inside your list. Why making your life that complicated? let's start doing it in a different way . First of all we need a class that is going to represent one item. That class is going to hold a Uri that points to the picture and a string to hold the name of it so let's get started!
Start by creating a class in your MainViewModel.cs or just create another Folder called 'Classes' and store it there if you wish. In this particular case i'm going to go straight ahead and just create the class in the MainViewModel.cs file but before i'm just going to delete all the previous code except the empty constructor. I'm also going to proceed and delete all the controls of the XAML as well.
Now my MainViewModel.cs should look like this:
Now that we got the item let's create a collection in our MainViewModel that is going to contain all the items we want to load in our list. Start by adding the 'using MVVMTutorial.Common.Collections;' namespace so that our new collection class is recognized and add the following to create your collection:
Code:
private ObservableList<PictureItem> items;
/// <summary>
/// Collection that is going to hold our item collection
/// </summary>
public ObservableList<PictureItem> Items
{
get { return items; }
private set { SetProperty(ref items, value); }
}
Code:
private void LoadPictures(string path)
{
var files = Directory.GetFiles(path, "*.jpg", SearchOption.TopDirectoryOnly);
items = new ObservableList<PictureItem>();
//Fill the collection
foreach(var file in files)
{
// Create a new pictureItem instance
var item = new PictureItem();
item.Picture = new Uri(file);
item.Name = Path.GetFileNameWithoutExtension(file);
// Use the private variable instead of the public one to avoid un-necessary binding calls
items.Add(item);
}
//After the process is done update by notifying
InvokePropertyChanged("Items");
}
Code:
<Window x:Class="MVVMTutorial.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviours="clr-namespace:MVVMTutorial"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding Items}"/>
</Grid>
</Window>
As you can see the code works but what's wrong? what you see is just a visual representation of items and since the listbox doesn't know how to handle them it just display the string. However as we can clearly see each item is a PictureItem! in our next step let's go ahead and teach the Listbox how to deal with that special item type. To do that we're going to modify the Listbox ItemTemplate property container and teach the control how to propertly display each item. To do that we're going to need the following controls in our template:
1. Grid which is the holder of the content. The grid column definition is going to be also declared so that one portion is going to be the image and the rest the label.
2. A image control with a pixel of 50x50
3. A label control
To do that modify the Listbox XAML like this:
Code:
<Window x:Class="MVVMTutorial.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviours="clr-namespace:MVVMTutorial"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Width="50" Height="50" Source="{Binding Picture}"/>
<Label Grid.Column="1" Content="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
So now when we run our application it should look like this if the path is correct:
We are now free to modify the private variable and when we're done just notify the changes and we can interact with them anytime!
----------------------------------------------------------------------------------------------
The tutorial doesn't end here... i will complete it when i get enough time tomorrow. In the mean time it should keep those interested busy for a while also attached the latest tutorial project for those interested.
Regards
@ruantec