WPF MVC FTW!

The Model/View/Controller paradigm has been making big news in the ASP.NET development community as of late. Mac programmers and crusty old MFC devs may roll their eyes, but I think any attempt to draw developers’ attention to higher order design principles is laudable.

ASP.NET may have required some finagling to get it to work in the MVC pattern, but I’ve found that WPF is perfectly aligned for MVC design. Since you’re allowed to pass POCO’s (Plain Old C# Objects) to UI elements, it’s simple as pie to separate your model from your presentation. I don’t have to worry about instantiating and initializing the right control for a particular data type; I simply define a DataTemplate for the class, and it magically uses the UI in the template wherever it sees an instance of that class.

modelsOne of my recent personal projects is for managing Magic: The Gathering card collections. I created a data entry app to allow for quick entry of large collections of MTG cards. Each card comes from a set, so my models were pretty obvious. I created a library project, and added a Card and a Set class, as well as some support classes, to the Models namespace. As per the MVC pattern, these models are exclusively for storing data and data-related logic. They contain no UI functionality whatsoever.

Now that I had my data, it was time for the UI. I decided to display the list of sets in a combo box, but I had a couple special needs. First, the cards don’t say which set they come from; instead, they show the set’s symbol, a small graphic unique to each set. I needed to display the symbols in the combo box so that the user could choose the correct set, even if they didn’t know the name. Second each set has a standard three letter abbreviation. I wanted the user to be able to type the abbreviation into the combo box to quickly select the desired set, without requiring moving the hands from keyboard to mouse.

I set out by creating a Views folder in my Interface project. For lack of imagination, I named the views after their respective models, though there can be many views for the same model and I may find myself having to rename them later. Each view inherits from ViewBase (itself inheriting from UserControl), which defines a Model dependency property and events for the Model property changing.

The set view went through several iterations to get the right data displayed in a compact space. Initially it was displaying everything from the release date to the number of cards in the set, but eventually I ended up with a very reasonable set of controls:

<Grid Margin="2" VerticalAlignment="Center" DataContext="{ Binding }">
    <Grid.ColumnDefinitions>
        <ColumnDefinition MaxWidth="33" />
        <ColumnDefinition MaxWidth="8" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition MaxWidth="25" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition />
    </Grid.RowDefinitions>
    <TextBlock x:Name="lblAbbreviation" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{ Binding Path=Abbreviation }" />
    <TextBlock Grid.Column="1" Grid.Row="0" HorizontalAlignment="Left" Text="-" />
    <TextBlock x:Name="lblName" Grid.Column="2" Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold" Text="{ Binding Path=Name }" />
    <Image x:Name="imgSymbol" Grid.Column="3" Grid.Row="0" Stretch="None" HorizontalAlignment="Right" Source="{ Binding Path=Symbol }" />
</Grid>

Now for the magic of XAML and DataTemplate. I added a DataTemplate element to the resources for my main window:

<Window.Resources>
    <DataTemplate DataType="{x:Type Models:Set}">
        <Views:Set Model="{ Binding }" />
    </DataTemplate>
</Window.Resources>

Now whenever my window sees a Models.Set object, it will substitute it with a Views.Set control, passing in the Models.Set as the Model property.

Next, I created my combo box with the formatting I needed:

<ComboBox   x:Name="cboSet"
            IsEditable="False"
            IsReadOnly="True"
            TextSearch.TextPath="Abbreviation"
            HorizontalContentAlignment="Stretch"
            SelectionChanged="cboSet_SelectionChanged" />

Pretty simple stuff. The IsEditable and IsReadonly properties allow the user to type in the box to search the existing values (as opposed to adding new ones). The TextSearch.TextPath tells the combo box what property of the items to compare the user’s typing to. Setting it to “Abbreviation” means when the user types, it will be compared against the three-letter set abbreviation. Remember, I’m going to assign my POCO Models.Set class as the combo box items, not the Views.Set. WPF will take care of displaying my Models.Set’s as Views.Set’s. That means any properties I’m talking about will be on the model, not the view (the way it should be!)

Finally, it’s time for the big test.

cboSet.ItemsSource = Sets;

Compile and run…

combobox

Bingo!

I applied this same process to the Card view, as well as an internal class call HistoryItem. In the end, I got an app that looks like this:

MTG Collection Tracker

blog comments powered by Disqus