Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

EF Code First and WPF with the Chinook Database. Part 2 – The Client

5.00/5 (1 vote)
12 Jan 2015CPOL3 min read 7.3K  
EF Code First and WPF with the Chinook Database. Part 2 – The Client

Now that we have the data fetching work sorted out, we can turn our attentions 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:

image

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.

XML
<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 ListBoxes and is created as follows:

XML
<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:

  1. Add the correct namespace
  2. Add the control

Adding the namespace requires that we add an xmlns to the top of the main window file.

XML
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...

XML
<views:ArtistList/>

...which gives us a Main window with the following XAML.

XML
<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.

C#
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:

XML
<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.

C#
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:

C#
ContainerFactory factory = new ContainerFactory();
provider = factory.Container.Resolve<IDataProvider>();

The whole ViewModel including properties now looks like this:

C#
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.

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)