Now that we have the data fetching work sorted out, we can turn our attention to the front-end.
I am going to make a very simple view in this post listing the artists and associated albums with their tracks. It is going to look like this:
Now, this is very basic, but it serves its purpose to show the tools we are going to be using – a later post will use the power of WPF to style and transform the screen into something more palatable.
The first thing to do is add a WPF project (I have named mine MusicApp.WPF.Client
). If you rename the MainWindow.xaml file, then don’t forget to change the startup URI in app.xaml.
<Application x:Class="MusicApp.WPF.Client.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Main.xaml">
In terms of the structure for the project, I have separate folders for views, view models and common files.
The View
Once the basic structure is in place, we can add our view to the Views folder. The view in this case is a user control that we will add to the main window.
The view itself is very simplistic – 1 grid containing 3 ListBox
es and is created as follows:
<UserControl x:Class="MusicApp.WPF.Client.Views.ArtistList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="Auto" Width="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.7*"/>
<RowDefinition Height="0.3*"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Path=Artists}" Margin="5"
DisplayMemberPath="Name" Grid.Column="0"
Grid.ColumnSpan="2" Grid.Row="0"
SelectedItem="{Binding SelectedArtist}" />
<ListBox ItemsSource="{Binding Path=SelectedArtist.Albums}" Margin="5"
DisplayMemberPath="Title" Grid.Column="0" Grid.Row="1"
SelectedItem="{Binding SelectedAlbum}" />
<ListBox ItemsSource="{Binding Path=SelectedAlbum.Tracks}" Margin="5"
DisplayMemberPath="Name" Grid.Column="1" Grid.Row="1" />
</Grid>
</UserControl>
This is a view with a 2 x 2 grid with columns of equal width (1/2 of the grid each) and one row at 70% width and one at 30%. Inside the grid, there is a ListBox
that takes up the whole top row (ColumnSpan=2
) and one ListBox
in each of the columns in the lower row.
I have included all of the bindings so that I know what is required on the view model when I write it. I will need a collection property called ‘Artists
’ and 2 properties for the selected artist and selected album.
The DisplayMemberPath
details which property of the object will be displayed as text in the ListBoxItem
.
Once the control has been written, then we can add it to the main window XAML. There are 2 things to do to add a usercontrol
to another XAML file:
- Add the correct
namespace
- Add the control
Adding the namespace
requires that we add an xmlns to the top of the main window file:
xmlns:views="clr-namespace:MusicApp.WPF.Client.Views"
Once we have this, then we can simply add the control as a new object on the window:
<views:ArtistList/>
which gives us a Main window with the following XAML:
<Window x:Class="MusicApp.WPF.Client.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MusicApp.WPF.Client.Views"
Title="Main" Height="475" Width="425">
<Grid>
<views:ArtistList/>
</Grid>
</Window>
The ViewModel
Now that the view is done, I can create a ViewModel
to support it. The ViewModel
will need to implement INotifyPropertyChanged
and I have the following snippet method that will fire the event implemented by it:
private void Notify(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Unity
I want to be able to use a different data provider here if I want to so I am going to introduce the Unity IoC container to the application at this point. Unity can be installed into the current project directly from NuGet (install-package unity
) or you can download directly. It needs a little configuration before we can use it.
I am going to use it to resolve a reference to the IDataProvider
interface we created previously so I add a new app.config file to the client project and add the following configuration:
<configuration>
<configSections>
<section name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity>
<containers>
<container>
<type type="MusicApp.Model.IDataProvider, MusicApp.Model"
mapTo="MusicApp.Model.EFCodeFirstDataProvider, MusicApp.Model" />
</container>
</containers>
</unity>
</configuration>
This is simply adding a mapping between IDataProvider
and EFCodeFirstDataProvider
which allows me to resolve the former to the latter.
We need a way of referencing this container in our application, so I use a factory class with a static
member to hold the actual UnityContainer
:
class ContainerFactory
{
private static UnityContainer container;
public ContainerFactory()
{
if (container == null)
{
container = new UnityContainer();
UnityConfigurationSection section =
(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(container);
}
}
public UnityContainer Container
{
get
{
return container;
}
}
}
I can then have a reference to an IDataProvider
in the ViewModel
and resolve it from the constructor as follows:
ContainerFactory factory = new ContainerFactory();
provider = factory.Container.Resolve<IDataProvider>();
The whole ViewModel
including properties now looks like this:
public class ArtistListViewModel : INotifyPropertyChanged
{
private IDataProvider provider;
private ObservableCollection<Artist> artists;
private Artist selectedArtist;
private Album selectedAlbum;
public ArtistListViewModel()
{
ContainerFactory factory = new ContainerFactory();
provider = factory.Container.Resolve<IDataProvider>();
GetArtists();
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Artist> Artists
{
get
{
return artists;
}
private set
{
artists = value;
Notify("Artists");
}
}
public Artist SelectedArtist
{
get
{
return selectedArtist;
}
set
{
selectedArtist = value;
Notify("SelectedArtist");
}
}
public Album SelectedAlbum
{
get
{
return selectedAlbum;
}
set
{
selectedAlbum = value;
Notify("SelectedAlbum");
}
}
private void GetArtists()
{
Artists = new ObservableCollection<Artist>(provider.GetArtists());
}
private void Notify(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Wire It Up
Now that we have the ViewModel
and view in place, we need to wire them together. This is just a case of setting the views DataContext
from its constructor.
public ArtistList()
{
InitializeComponent();
this.DataContext = new ArtistListViewModel();
}
Wrapping Up
Now that we have these wired, we simply need to add the ConnectionStrings
to the config file (that we added the unity config to earlier), change the startup project to the new WPF app and run it. This will now display the window shown at the start and I can change the artist to update the albums/tracks at the bottom.
Next time – making the view look good.