Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a WPF Application: Part 6

DZone's Guide to

Building a WPF Application: Part 6

·
Free Resource

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!

 

Topics:

Published at DZone with permission of Christopher Bennage, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}