Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Building a WPF Application: Part 6

ChumChase Table of Contents

I opened up the ChumChase code this evening with the sad realization that my last commits were on November 14th. Ouch. In reading over the code, something jumped out at me immediately. In the code-behind for Shell.xaml, I had a lot of logic that didn't need to be there. It was clumsy and not tested, but at least I had left myself a comment to that effect.

The code handled switching from the default view to the 3D view. If you don't know what I'm talking about, go back and read the older posts. The important part of Shell.xaml looked like this:

<Grid>
    <ContentControl x:Name="MainView" />

    <Button Content="Toggle View"
            VerticalAlignment="Top"
            HorizontalAlignment="Right"
            Click="ToggleView_Click" />
</Grid>

and the handler for the click (along with a dependent method) looked like this:

private void ToggleView_Click(object sender, RoutedEventArgs e)
{
    if (MainView.Content is DefaultFeedView)
    {
        SetView(new _2Don3DView());
    }
    else
    {
        SetView(new DefaultFeedView());
    }
}

private void SetView(IFeedView view)
{
    MainView.Content = view;
    view.RefreshButton.Click += Refresh_Click;
}

Yes, _2Don3DView is not a good name. I'll fix it. Aside from the appalling appellation, this code is not very WPF-ish. What is it doing anyway?

ContentControl is really a place holder. It represents the area in the application's shell where we want to stick the main content. In the handler, we check to see what is currently in the placeholder and we switch it out. Since each view had it's own button for refreshing the feed we needed to wire it up each time we switched the view. (Remember this way is naughty-naughty.) Our views implemented IFeedView so we could access their respective Refresh buttons.

A More Excellent Way

My ApplicationController class should really be responsible for this behavior. In order to make that happen, I created a property on it called CurrentPresenter. This property is the presenter that will back the current view. (A presenter is a class that contains the logic for a portion of the UI, the corresponding view is the visual part used to render that presenter.) Since the data context for Shell.xaml is already set to an instance of ApplicationController (it's named _controller in the code-behind), I was able to changed the markup to look like this:

<Grid>
    <ContentControl Content="{Binding CurrentPresenter}" />

    <Button Content="Toggle View"
            VerticalAlignment="Top"
            HorizontalAlignment="Right"
            Click="ToggleView_Click" />
</Grid>

and then the event handler to this:

private void ToggleView_Click(object sender, RoutedEventArgs e)
{
    _controller.ToggleView();
}

And now, let's examine the tests for the desired behavior. I wanted ToggleView to alternate between an instance of DefaultFeedPresenter and an instance of _2Don3DPresenter. (Bad Christopher, bad!) I did not want ToggleView to create new instances, but to reuse existing ones.

[TestFixture]
public class The_application_controller
{
    [SetUp]
    public void given_a_context_of()
    {
        // stuff omitted for brevity //
        _controller = new ApplicationController();
    }

    [Test]
    public void raises_change_notification()
    {
        _controller
            .AssertThatAllProperties()
            .RaiseChangeNotification();
    }

    [Test]
    public void uses_the_expected_presenter_by_default()
    {
        Assert.That(_controller.CurrentPresenter, Is.InstanceOfType(typeof(DefaultFeedPresenter)));
    }

    [Test]
    public void toggles_to_the_3D_view_when_the_default_is_current()
    {
        _controller.ToggleView();
        Assert.That(_controller.CurrentPresenter, Is.InstanceOfType(typeof(_2Don3DPresenter)));
    }

    [Test]
    public void toggles_to_the_default_when_the_3D_view_is_current()
    {
        var default_presenter = _controller.CurrentPresenter;
        _controller.CurrentPresenter = new _2Don3DPresenter(_controller);
        _controller.ToggleView();

        Assert.That(_controller.CurrentPresenter, Is.EqualTo(default_presenter));
    }
}

Ooo, hey, what's that first test? That raises notification bit? Oh, that just some cool stuff in Caliburn, you can read more about that here.

I added the following lines to ApplicationController in order to pass these tests:

private readonly IList<IPresenter> _presenters = new List<IPresenter>();
private IPresenter _currentPresenter;

public IPresenter CurrentPresenter
{
    get { return _currentPresenter; }
    set
    {
        _currentPresenter = value;
        RaisePropertyChanged("CurrentPresenter");
    }
}

public void ToggleView()
{
    CurrentPresenter = (CurrentPresenter is DefaultFeedPresenter)
        ? _presenters[1]
        : _presenters[0];
}

I initialized _presenters in the constructor for ApplicationController with the instances of the presenters. (I was tempted here to introduce an IoC container, but I didn't. We'll talk more about that later.)

So now, when ToggleView is called, the CurrentPresenter property is updated and change notification is raised, but what happens in the UI? How does it render the presenter instances? Well, given the markup from Shell.xaml we listed above, it doesn't do anything.

I Love DataTemplates So Much, Why Don't I Marry Them?

We need to tell WPF how to render each presenter. We already have user controls that define each view. We can reuse those. I added the following to the Grid containing my ContentControl:

<Grid.Resources>
    <DataTemplate DataType="{x:Type Model:DefaultFeedPresenter}">
        <Views:DefaultFeedView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type Model:_2Don3DPresenter}">
        <Views:_2Don3DView />
    </DataTemplate>
</Grid.Resources>

The DataType property tells WPF that anything bound to an instance of the given type should use the template. Since I placed these in the resources for the grid, it will affect any bindings inside the grid. Each data template simply contains the corresponding user control. I could have inlined the user controls, but I already had tests in place and I believe this makes the markup easier to read.

In making these changes, I did break something. Something that was not being tested... but it's time for bed so I'll leave that for another night.

More to come!


Posted 01-13-2009 12:29 AM by Christopher Bennage

[Advertisement]

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)