Building a WPF Application: Part 6
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 WayMy 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!
Christopher Bennage is the President and cofounder of Blue Spire Consulting, Inc., a Florida based software consulting firm specializing in .NET technologies, user experience, and interface design. Christopher began programming on his Texas Instrument in elementary school, but fell in love with computers with the advent of the Commodore Amiga. More recently he coauthored Sams Teach Yourself WPF in 24 Hours with Rob Eisenberg. In his free time, Christopher is usually very distracted by a dozen different, competing creative ideas. He lives in Tallahassee, FL with his wife, Sandra, and their three children. Christopher is a DZone MVB and is not an employee of DZone and has posted 9 posts at DZone.
- Login or register to post comments
- 623 reads
- Printer-friendly version
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)









