More than one way to skin the WPF cat
Posted on February 13th, 2010 in .NET, UI | No Comments »
My plunge down the hole of all that is WPF continues. So far I’m still impressed with the view (pun intended). For some time I kept expecting to find some juicy nugget to be the barrier to using WPF in practice but I’m just not finding it. If anything the biggest issue seems to be that there are so many features it is hard to get your head around which one to use at what time.
At this point, I’m trying to take a very purest approach to MVVM. I’ve got a great Model layer and I find creating View-Models rather common-sense-able (I’m patenting that word so watch out) and even enjoyable. I’ve been known in the past to create command line utilities for all sorts of needs and while I do enjoy the command line for many tasks, there have been times when I wanted a good UI but just didn’t have the time to invest in cobbling one together. The more fluent I get with WPF the more I start to think that creating command line utilities is no longer an easier/faster way to get a small tool up and running. WPF just makes it so easy once the monstrous learning curve is surpassed.
So the M in M-V-VM is all good and the VM in M-V-VM is all good….now lets talk a bit about the V.
I’m currently taking the approach of trying very hard to keep the view to purely XAML. No code behind with the exception of the already mentioned ViewModel. Yes there will be a converter or 6 to map properties of one type and value in the Model to another value and type in the View but these are small, simple, reusable components. What I want to avoid is the more cumbersome type of code that UIs can so often become overrun with such as event handlers that scrape values from one UI element and pass them along to another. Essentially trying to avoid writing any C# code which is purely View focused and which begins to represent some sort of business logic.
I initially slipped off of this band wagon for a small feature and it seems like an OK thing to do at the time and then it bit me. I have since come up with a palatable alternate solution which is working well. I trust however that this is in no way to only solution or even the best. WPF just has so many features and abilities, there seems to always be more than one way to solve any given issue.
Let us look at my issue.
I have a ListView to which new items are added from time to time as the application runs. I want the most recent item at the bottom to scroll into view when it is added. Simple enough requirement.
I initially implemented this using a ScrollViewer around the ListView and then listening for the SizeChanged event and scrolling to the bottom of the ScrollViewer in the event handler. Something like this.
<ScrollViewer SizeChnaged="ScrollViewer_SizeChanged">
<ListView AlternationCount="1"
ItemsSource="{Binding MyCollection}"
IsSynchronizedWithCurrentItem="True">
</ListView>
</ScrollViewer>
private void ScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e)
{
ScrollViewer sv = sender as ScrollViewer;
sv.ScrollToEnd();
}
Now this worked, and all of the code is in a user control so it really does seem a fine place to relax a bit in terms of trying to avoid code in the view layer. But….
During some later refactoring I pulled a good portion of the XAML for this user control out into a resource dictionary as a ControlTemplate to both reduce the growing code in the user control as well as make what was a generally reusable bit of UI formatting into a reusable package….the ControlTemplate. All worked as expected except that you can’t make use of an event handler in a ControlTemplate. This makes sense as a traditional event handler needs some code to wire up the event listener. Now if this was a Button then we would just move away from the older style Click event and make use of the new WPF wiz-bang Command. These work just fine in a template….and yes this is where an appreciation for the WPF Command architecture really sets in. But alas the ScrollViewer does not have an equivalent command version of the SizeChanged event and so we need another solution.
Now again, I’m not suggesting this is the only solution and I would even bet that there is an easier one. And I would love to hear what they are. But my solution does have some nice aspects and is relatively short, sweet, and reusable.
I created a custom control which sub-classes the ScrollViewer and called it ScrollToEndScrollViewer. This new control exposes a single new property AutoScrollActive which controls turning on/off the new feature. This new property is bindable and so it can be activated via data in your ViewModel. While I initially felt like a new control type was heavy handed for this solution, I do like several aspects of this solution. Firstly the logic for the scrolling feature is hidden away from both the View XAML and from the View Model. Secondly, allowing the control over turning this feature on/off to be bindable allows for simple connectivity between the presentation and the data without any hard dependency. Thirdly, by providing this feature via the ScrollViewer it is not tied at all to the content actually being displayed. As an alternative, one could likely devise a solution using a ListView or a DataView more directly but then other typos of content would not benefit.
So lets look at the code….
The custom control simply sub classes the standard ScrollViewer and implements the ability to always scroll to the bottom when new content is added.
public class ScrollToEndScrollViewer : ScrollViewer
{
public ScrollToEndScrollViewer() : base()
{
}
static ScrollToEndScrollViewer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ScrollToEndScrollViewer), new FrameworkPropertyMetadata(typeof(ScrollToEndScrollViewer)));
}
/// <summary>
/// Controls whether the viewer auto scrolls to the bottom when new content is added.
/// </summary>
public Boolean AutoScrollActive
{
get { return (Boolean)this.GetValue(AutoScrollActiveProperty); }
set { this.SetValue(AutoScrollActiveProperty, value); }
}
/// <summary>
/// Supporting dependency property for AutoScrollActive
/// </summary>
/// <remarks>
/// The property much be implemented as a dependency property to allow data binding.
/// </remarks>
public static readonly DependencyProperty AutoScrollActiveProperty =
DependencyProperty.Register("AutoScrollActive",
typeof(Boolean),
typeof(ScrollToEndScrollViewer),
new PropertyMetadata(true));
/// <summary>
/// Overloaded event handler
/// </summary>
/// <remarks>
/// Monitor for when the viewable area is larger than the total area and the height of the
/// total area has changed. In this case scroll to the bottom.
/// </remarks>
/// <param name="e"></param>
protected override void OnScrollChanged(ScrollChangedEventArgs e)
{
base.OnScrollChanged(e);
if (AutoScrollActive)
{
if (e.ViewportHeight < e.ExtentHeight &&
e.ExtentHeightChange > 0)
{
base.ScrollToBottom();
}
}
}
}
Then the XAML makes use of this control including binding the boolean control value to a value in the ViewModel.
<cc:ScrollToEndScrollViewer AutoScrollActive="{Binding SomeMVPropToControlScrolling}">
<ListView AlternationCount="1"
ItemsSource="{Binding MyCollection}"
IsSynchronizedWithCurrentItem="True">
</ListView>
</cc:ScrollToEndScrollViewer>
All in all this does the trick. I still suspect I will come to find another solution in time, but even then this has been a useful learning tool in exploring the process and value in sub-classing an existing control to gain similar but enhanced behavior in the view.

