Saturday, November 19, 2011

WindowItemsControl

The application I'm currently working on - top secret, mum's the word, your death for my indiscretion, you get the idea - includes a widget-style interface. In order to render these widgets, I use an ItemsControl and bind it to a collection of view models, each of which represents a widget. I use a Canvas to lay them out according to their XOffset and YOffset properties. Something like this:

<ItemsControl ItemsSource="{Binding Widgets}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Canvas.Left" Value="{Binding XOffset}"/>
            <Setter Property="Canvas.Top" Value="{Binding YOffset}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <views:WidgetView/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

This all works fine and I'd even go so far as to say it's a beautiful thing. However, it is only a widget-style interface by virtue of some trickery on my part. Without said trickery, it would be more of an MDI interface.

With an MDI interface, multiple child windows are contained within a parent window – they cannot appear outside the bounds of the parent. Whilst my widgets look like windows, they aren't. They're just regular WPF user controls with some additional smarts to allow positioning and what-have-you. And whilst it looks like they're free to roam wherever they like on the desktop, they're not. They're all contained within the ItemsControl, which is within the only Window in my application. Thus, they cannot be positioned outside the area in which the Window resides. It just so happens that I've stretched that Window across the entire desktop and made it transparent. This gives the illusion that I have a widget interface and am thus as cool as The Dude himself, but I actually have an MDI interface which makes me more Napolean Dynamite than Lebowski. Before the dance, that is.

All this wouldn't concern me terribly (one learns to live with it) but my users are actually privy to more screen real estate than I. And when I say "more", I mean they have six screens whilst I have two, one of which I can't use for anything of import because it frequently distorts and shows other signs of discontent (no audible screams as yet). Because of this abundance of screen real estate, my Window has to to stretch across a huge expanse of pixels in order to keep up this illusion of cool. This has dire consequences for performance. You see, if one's Window size exceeds the maximum texture size of one's video card (I don't have one of course, but my users do) then the video card won't be able to accelerate rendering of said Window. The Window will be software-rendered instead, which is probably going to be a lot slower and less capable of Dude-worthy effects and animations.

No problem, you say. Just host your widgets inside windows instead and be done with it. And this is indeed what I am going to do.

But I want it to be seamless with respect to the current code base. I don't want to have to go hook up a bunch of event handlers to create/show/close windows when my widget collection changes. I don't want to have to change the way my view models keep track of widget positions and sizes (all persisted across application restarts, of course). All I want to do is change this:

<ItemsControl ItemsSource="{Binding Widgets}">

to this:

<WindowItemsControl ItemsSource="{Binding Widgets}">

But unfortunately WPF doesn't have a WindowItemsControl. Boo. And it doesn't seem as though anyone in the community has written one.

Obviously, then, I set out to write my own.

My initial approach failed, but it's worth discussing anyway. I tried to have my WindowItemsControl create Window instances as containers. This failed because internal WPF code was attempting to add these Windows as visual children of the ItemsControl, and Windows must be top-level visual items (makes sense). So I tried to hack around this because I really wanted the logical connection between the Window and the WindowItemsControl, much the same way there's a logical connection between a ListBoxItem and its containing ListBox. If I could trick WPF into forgoing the visual connection, I could then attempt the logical connection.

Well, I tried all sorts of nastiness, and ended up reflectively invoking an internal member to trick WPF into not including the Window as a visual child. Success! Right!? Alas, no, because when I then added the Window as a logical child of the WindowItemsControl, I got another similar error. I can't remember the details, nor can I explain why a Window cannot be a logical child of another control (it's only a logical connection, after all). But it didn't work and I gave up on this approach entirely.

My second approach is much more sane but gives up on creating a logical connection between the Windows and their host. But I don't really need that anyway – it was a nice-to-have.

What I did instead was had the WindowItemsControl create WindowItemsControlItem instances as containers. These containers are really just surrogates for the Window they represent. When they're initialized, they display the Window. When they're destroyed, they close the Window. In addition, if a Window is closed ahead of time, the corresponding data item is removed from the underlying collection and thus too the surrogate from the visual tree.

The code is actually quite neat and compact. Here is the code for WindowItemsControl:

public class WindowItemsControl : ItemsControl
{
	public static readonly DependencyProperty ShowDialogProperty = DependencyProperty.Register(
		"ShowDialog",
		typeof(bool),
		typeof(WindowItemsControl));

	public static readonly DependencyProperty OwnerProperty = DependencyProperty.Register(
		"Owner",
		typeof(Window),
		typeof(WindowItemsControl),
		new FrameworkPropertyMetadata(OnOwnerChanged));

	public static readonly DependencyProperty WindowStartupLocationProperty = DependencyProperty.Register(
		"WindowStartupLocation",
		typeof(WindowStartupLocation),
		typeof(WindowItemsControl));

	public static readonly DependencyProperty RemoveDataItemWhenWindowClosedProperty = DependencyProperty.Register(
		"RemoveDataItemWhenWindowClosed",
		typeof(bool),
		typeof(WindowItemsControl),
		new FrameworkPropertyMetadata(true));

	static WindowItemsControl()
	{
		DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowItemsControl), new FrameworkPropertyMetadata(typeof(WindowItemsControl)));
	}

	public bool ShowDialog
	{
		get { return (bool)this.GetValue(ShowDialogProperty); }
		set { this.SetValue(ShowDialogProperty, value); }
	}

	public Window Owner
	{
		get { return this.GetValue(OwnerProperty) as Window; }
		set { this.SetValue(OwnerProperty, value); }
	}

	public WindowStartupLocation WindowStartupLocation
	{
		get { return (WindowStartupLocation)this.GetValue(WindowStartupLocationProperty); }
		set { this.SetValue(WindowStartupLocationProperty, value); }
	}

	public bool RemoveDataItemWhenWindowClosed
	{
		get { return (bool)this.GetValue(RemoveDataItemWhenWindowClosedProperty); }
		set { this.SetValue(RemoveDataItemWhenWindowClosedProperty, value); }
	}

	protected override DependencyObject GetContainerForItemOverride()
	{
		return new WindowItemsControlItem(this);
	}

	protected override bool IsItemItsOwnContainerOverride(object item)
	{
		return item is WindowItemsControlItem;
	}

	protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
	{
		(element as WindowItemsControlItem).Window.Content = item;
	}

	protected override bool ShouldApplyItemContainerStyle(DependencyObject container, object item)
	{
		// the item container style will be applied to the windows, not to the containers (which are surrogates for the window)
		return false;
	}

	private static void OnOwnerChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
	{
		var windowItemsControl = (WindowItemsControl)dependencyObject;
		var owner = (Window)e.NewValue;

		for (var i = 0; i < windowItemsControl.Items.Count; ++i)
		{
			var container = windowItemsControl.ItemContainerGenerator.ContainerFromIndex(i) as WindowItemsControlItem;

			if (container == null)
			{
				continue;
			}

			container.Window.Owner = owner;
		}
	}
}

Pretty straightforward stuff. Note the following:

  • it declares some properties (ShowDialog, Owner, WindowStartupLocation) that assist it in the display of child Windows
  • it declares a RemoveDataItemWhenWindowClosed property that can be used to prevent the control from removing data items when a window is closed. This can be useful in shutdown or other situations where windows are being closed programmatically rather than by the user
  • I don't apply the ItemContainerStyle to the containers themselves, but instead hold out so that I can apply them to the Windows they represent
  • I also make sure that any change of Owner is applied to any existing Windows
  • the default style is overridden to remove unnecessary stuff like the Border, because the WindowItemsControl will never actually be visible on screen

The WindowItemsControl works in conjunction with the WindowItemsControlItem, which looks like this:

public class WindowItemsControlItem : FrameworkElement
{
	private readonly WindowItemsControl windowItemsControl;
	private readonly Window window;

	static WindowItemsControlItem()
	{
		// there is no need for these items to be visible as they are simply surrogates for the windows that they display
		VisibilityProperty.OverrideMetadata(typeof(WindowItemsControlItem), new FrameworkPropertyMetadata(Visibility.Collapsed));
	}

	public WindowItemsControlItem(WindowItemsControl windowItemsControl)
	{
		windowItemsControl.AssertNotNull("windowItemsControl");

		this.windowItemsControl = windowItemsControl;
		this.window = this.CreateWindow(windowItemsControl);

		this.Loaded += delegate
		{
			if (this.windowItemsControl.ShowDialog)
			{
				this.window.ShowDialog();
			}
			else
			{
				this.window.Show();
			}
		};

		this.Unloaded += delegate
		{
			this.window.Close();
		};
	}

	public Window Window
	{
		get { return this.window; }
	}

	private Window CreateWindow(WindowItemsControl windowItemsControl)
	{
		var window = new Window
		{
			Owner = windowItemsControl.Owner,
			WindowStartupLocation = windowItemsControl.WindowStartupLocation
		};

		BindingOperations.SetBinding(window, Window.DataContextProperty, new Binding("Content") { Source = window });
		BindingOperations.SetBinding(window, Window.StyleProperty, new Binding("ItemContainerStyle") { Source = windowItemsControl });
		BindingOperations.SetBinding(window, Window.ContentTemplateProperty, new Binding("ItemTemplate") { Source = windowItemsControl });
		BindingOperations.SetBinding(window, Window.ContentTemplateSelectorProperty, new Binding("ItemTemplateSelector") { Source = windowItemsControl });

		window.Closed += delegate
		{
			// orphan the content because it might be hosted somewhere else later (including in another window)
			window.Content = null;

			// if the window closes, attempt to remove the original item from the underlying collection, which will result in this surrogate being removed too
			if (windowItemsControl.RemoveDataItemWhenWindowClosed)
			{
				var editableItems = windowItemsControl.Items as IEditableCollectionView;

				if (editableItems != null && editableItems.CanRemove)
				{
					editableItems.Remove(this.DataContext);
				}
			}
		};

		return window;
	}
}

This is all pretty self-explanatory, too. The important points to note are:

  • relevant properties on the WindowItemsControl are bound to the correct properties on the Windows themselves
  • Windows are displayed when the surrogate is initialized, and closed when the surrogate is unloaded
  • as mentioned earlier, Windows that are closed before the surrogate is destroyed (perhaps by the user clicking the close button) result in the related data item in the underlying collection being removed (unless the RemoveDataItemWhenWindowClosed property has been set to false). This, in turn, will cause the surrogate to be removed from the visual tree. In other words, if I close a widget Window, the corresponding WidgetViewModel will be removed from my collection of widget view models. Then, the ItemsControl will remove the related surrogate container from the visual tree.

Now, as I yearned for at the beginning of this post, I can simply change my ItemsControl to a WindowItemsControl, make minor adjustments to my ItemContainerStyle and it just magically works.

The Dude abides.

I created a little demo to show how it all works, which you can download below. Enjoy!

Saturday, November 12, 2011

LogExportProvider

We all know how important logging is to any non-trivial application, so it stands to reason that we should make it as pain free as possible to add logging to our application components. But at the same time, we don't want to sacrifice too much, such as having to deal with a sub-standard logging API, or with a logging infrastructure that doesn't log the obvious stuff on our behalf.

Wouldn't it be nice if logging were as simple as this:

namespace SomeNamespace
{
    public class SomeClass
    {
        [ImportingConstructor]
        public SomeClass(ILoggerService loggerService)
        {
            loggerService.Debug("Created an instance of {0}.", GetType().Name);
        }
    }
}

And, importantly, the resultant log entry looked something like this:

[2011-11-12 12:09:04,749] [1] [DEBUG] [SomeNamespace.SomeClass] Created an instance of SomeClass.

Prism ships with an ILoggerFacade interface that looks like this:

public interface ILoggerFacade 
{ 
    void Log(string message, Category category, Priority priority); 
}

This is obviously a very bare-bones interface. Every time you write a log statement you'll be forced to:

  1. Format any parameters yourself.
  2. Specify the category as a parameter (instead of having separate methods for each category)
  3. Choose and specify a priority, even if it makes no sense for your code.
  4. Incur the cost of any preparation for the log statement even if the pertinent log level is disabled because you have no way to check whether it is or not.

This is not very consumer-friendly at all, and you'll more than likely begin to find logging a more onerous task than it should be.

Another problem with using ILoggerFacade in Prism is the lack of any originating source in the output. There is nothing intrinsic to differentiate log statements from different components. If two components log the same message ("Initialized", for example), you will have no way to tell which component was initialized!

These problems forced me to come up with a custom solution. I wanted my logging to be based on log4net, and I wanted MEF to provide my components with a logging service instance. Moreover, that service must produce log entries that are specific to my component.

The first problem (poor API) was easiest to solve. I defined my own interface as follows:

public interface ILoggerService 
{ 
    bool IsVerboseEnabled 
    { 
        get; 
    }

    bool IsDebugEnabled 
    { 
        get; 
    }

    bool IsInfoEnabled 
    { 
        get; 
    }

    bool IsWarnEnabled 
    { 
        get; 
    }

    bool IsErrorEnabled 
    { 
        get; 
    }

    bool IsPerfEnabled 
    { 
        get; 
    }

    void Verbose(string message);

    void Verbose(string message, Exception exception);

    void Verbose(string message, params object[] args);

    void Debug(string message);

    void Debug(string message, Exception exception);

    void Debug(string message, params object[] args);

    void Info(string message);

    void Info(string message, Exception exception);

    void Info(string message, params object[] args);

    void Warn(string message);

    void Warn(string message, Exception exception);

    void Warn(string message, params object[] args);

    void Error(string message);

    void Error(string message, Exception exception);

    void Error(string message, params object[] args);

    IDisposable Perf(string message);

    IDisposable Perf(string message, params object[] args); 
}

As you can see, this interface provides many overloads for all the relevant combinations of parameters you might need. This saves you, the caller, from having to deal with the annoyance of formatting messages or exceptions. There are also properties that can be used to check whether a given log level is enabled, which can be crucial in performance-critical paths. Finally, notice the handy Perf overloads which can be used to measure the performance of a block of code like this:

using (loggerService.Perf("Authenticating the user")) 
{ 
    // do authentication here
}

The only thing I haven't included (because I haven't needed it) are generic Write methods that take the log level as a parameter instead of inferring the log level from the method name. Such methods can be useful in dynamic logging scenarios, so you may want to add your own.

With the API defined, it was time to write an implementation:

public sealed class Log4NetLoggerService : ILoggerService 
{ 
    private static readonly Level perfLevel = new Level(35000, "PERF"); 
    private readonly ILog log;

    public Log4NetLoggerService(ILog log) 
    { 
        log.AssertNotNull("log"); 
        this.log = log; 
    }

    public bool IsVerboseEnabled 
    { 
        get { return this.log.Logger.IsEnabledFor(Level.Verbose); } 
    }

    public bool IsDebugEnabled 
    { 
        get { return this.log.IsDebugEnabled; } 
    }

    public bool IsInfoEnabled 
    { 
        get { return this.log.IsInfoEnabled; } 
    }

    public bool IsWarnEnabled 
    { 
        get { return this.log.IsWarnEnabled; } 
    }

    public bool IsErrorEnabled 
    { 
        get { return this.log.IsErrorEnabled; } 
    }

    public bool IsPerfEnabled 
    { 
        get { return this.log.Logger.IsEnabledFor(perfLevel); } 
    }

    public void Verbose(string message) 
    { 
        this.log.Logger.Log(typeof(Log4NetLoggerService), Level.Verbose, message, null); 
    }

    public void Verbose(string message, Exception exception) 
    { 
        this.log.Logger.Log(typeof(Log4NetLoggerService), Level.Verbose, message, exception); 
    }

    public void Verbose(string message, params object[] args) 
    { 
        this.log.Logger.Log(typeof(Log4NetLoggerService), Level.Verbose, new SystemStringFormat(CultureInfo.InvariantCulture, message, args), null); 
    }

    public void Debug(string message) 
    { 
        this.log.Debug(message); 
    }

    public void Debug(string message, Exception exception) 
    { 
        this.log.Debug(message, exception); 
    }

    public void Debug(string message, params object[] args) 
    { 
        this.log.DebugFormat(CultureInfo.InvariantCulture, message, args); 
    }

    public void Info(string message) 
    { 
        this.log.Info(message); 
    }

    public void Info(string message, Exception exception) 
    { 
        this.log.Info(message, exception); 
    }

    public void Info(string message, params object[] args) 
    { 
        this.log.InfoFormat(CultureInfo.InvariantCulture, message, args); 
    }

    public void Warn(string message) 
    { 
        this.log.Warn(message); 
    }

    public void Warn(string message, Exception exception) 
    { 
        this.log.Warn(message, exception); 
    }

    public void Warn(string message, params object[] args) 
    { 
        this.log.WarnFormat(CultureInfo.InvariantCulture, message, args); 
    }

    public void Error(string message) 
    { 
        this.log.Error(message); 
    }

    public void Error(string message, Exception exception) 
    { 
        this.log.Error(message, exception); 
    }

    public void Error(string message, params object[] args) 
    { 
        this.log.ErrorFormat(CultureInfo.InvariantCulture, message, args); 
    }

    public IDisposable Perf(string message) 
    { 
        message.AssertNotNull("message"); 
        return new PerfBlock(this, message); 
    }

    public IDisposable Perf(string message, params object[] args) 
    { 
        message.AssertNotNull("message"); 
        args.AssertNotNull("args"); 
        return new PerfBlock(this, string.Format(CultureInfo.InvariantCulture, message, args)); 
    }

    private sealed class PerfBlock : IDisposable 
    { 
        private readonly Log4NetLoggerService owner; 
        private readonly string message; 
        private readonly Stopwatch stopwatch; 
        private bool disposed;

        public PerfBlock(Log4NetLoggerService owner, string message) 
        { 
            this.owner = owner; 
            this.message = message; 
            this.stopwatch = Stopwatch.StartNew(); 
        }

        public void Dispose() 
        { 
            if (!this.disposed) 
            { 
                this.disposed = true; 
                this.stopwatch.Stop(); 
                var messageWithTimingInfo = string.Format(CultureInfo.InvariantCulture, "{0} [{1}, {2}ms]", this.message, this.stopwatch.Elapsed, this.stopwatch.ElapsedMilliseconds); 
                this.owner.log.Logger.Log(typeof(Log4NetLoggerService), perfLevel, messageWithTimingInfo, null); 
            } 
        } 
    } 
}

It's all pretty straightforward - most of the code just delegates to log4net.

But notice how the constructor requires a log4net.ILog? Log4net provides various ways by which an ILog can be obtained, but we would like to use LogManager.GetLogger(Type ownerType), where ownerType is the type importing our service. How, then, can we expect MEF to provide instances of ILoggerService when our constructor has a dependency that it cannot satisfy?

MEF supports an abstraction called ExportProvider, which is an object that can dynamically provide exports to satisfy matching imports. The trick to making this all work seamlessly is a custom ExportProvider that creates instances of Log4NetLoggerService on the fly to satisfy imports of type ILoggerService. In order to create Log4NetLoggerService instances, the export provider must know the type of the object that is importing the service. Thankfully, MEF supports obtaining this information through its reflection services.

Here is the code for our custom ExportProvider:

public sealed class LoggerServiceExportProvider : ExportProvider 
{ 
    private static readonly ILoggerService log = new Log4NetLoggerService(LogManager.GetLogger(typeof(LoggerServiceExportProvider))); 
    private readonly IDictionary<Type, ILoggerService> loggerServiceCache;

    public LoggerServiceExportProvider() 
    { 
        this.loggerServiceCache = new Dictionary<Type, ILoggerService>(); 
    }

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition) 
    { 
        var contractName = definition.ContractName;

        log.Verbose("Attempting to resolve contract name '{0}' to a log instance.", contractName);

        if (string.IsNullOrEmpty(contractName)) 
        { 
            log.Verbose("Contract name is null or empty - cannot resolve."); 
            yield break; 
        }

        if (!string.Equals(typeof(ILoggerService).FullName, contractName, StringComparison.Ordinal)) 
        { 
            log.Verbose("Incorrect contract - cannot resolve."); 
            yield break; 
        }

        if (definition.Cardinality != ImportCardinality.ExactlyOne) 
        { 
            log.Verbose("Cardinality is {0} - cannot resolve.", definition.Cardinality); 
            yield break; 
        }

        // in order to get a log4net logger, we need the type importing the logger facade 
        Type ownerType = null;

        if (ReflectionModelServices.IsImportingParameter(definition)) 
        { 
            log.Verbose("Parameter import detected.");

            var importingParameter = ReflectionModelServices.GetImportingParameter(definition); 
            ownerType = importingParameter.Value.Member.DeclaringType; 
        } 
        else 
        { 
            log.Verbose("Property import detected.");

            var setAccessor = ReflectionModelServices 
                .GetImportingMember(definition) 
                .GetAccessors() 
                .Where(x => x is MethodInfo) 
                .Select(x => x as MethodInfo) 
                .FirstOrDefault(x => (x.Attributes & MethodAttributes.SpecialName) == MethodAttributes.SpecialName && x.Name.StartsWith("set_", StringComparison.Ordinal));

            if (setAccessor == null) 
            { 
                log.Verbose("Set accessor for property not found - cannot resolve."); 
                yield break; 
            }

            ownerType = setAccessor.DeclaringType; 
        }

        if (ownerType == null) 
        { 
            log.Verbose("Owner type could not be determined - cannot resolve."); 
            yield break; 
        }

        log.Verbose("Owner type is '{0}'.", ownerType.FullName);

        ILoggerService loggerService;

        if (!this.loggerServiceCache.TryGetValue(ownerType, out loggerService)) 
        { 
            log.Verbose("Logger facade for owner type '{0}' is not yet cached - creating it.", ownerType.FullName);

            var logInstance = LogManager.GetLogger(ownerType); 
            loggerService = new Log4NetLoggerService(logInstance); 
            this.loggerServiceCache[ownerType] = loggerService; 
        }

        var export = new Export(contractName, () => loggerService); 
        yield return export; 
    } 
}

There are several things to note about this implementation:

  1. Log service instances are cached. This means that if different instances of the same type import a logger service, they will (quickly) get the exact same instance.
  2. Imports can be either via constructors or through properties. The export provider supports both.
  3. The export provider itself includes logging statements, but it must do so with an explicitly created ILog instance. Obviously the export provider cannot import an ILoggerService itself or we'd have a chicken and egg problem!

We can tell MEF to use our custom ExportProvider in the usual fashion:

var compositionContainer = new CompositionContainer(new LoggerServiceExportProvider());

With this infrastructure in place, we achieve our objectives entirely. The code I included right at the beginning of this post will work, and any log entries will include the details of the originating type. And it is ridiculously easy for us to imbue components created by MEF with logging statements. Simply add the import and then invoke the simple-to-use APIs.

For a working example, click below. Enjoy!

Monday, August 29, 2011

Using InheritanceBehavior to Aid Theme Development

If you've ever put together a custom theme for WPF, you'll know the value of comparing your theme to the system theme. You can quickly spot things that you've missed, or compare and contrast behavior. However, if your theme targets controls by type rather than by key (and most themes should), how do you ensure your theme isn't applied to a given control? That is, how do you ensure a given control inherits the system theme regardless of the application-defined themes?

Suppose you're implementing a style for buttons. It looks like this:

<Style TargetType="Button">
    <Setter Property="Background">
        <Setter.Value>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                <GradientStop Offset="0" Color="#5f9ea0"/>
                <GradientStop Offset="0.5" Color="#3f7e80"/>
                <GradientStop Offset="0.5" Color="#7fbec0"/>
                <GradientStop Offset="1" Color="#1f5e80"/>
            </LinearGradientBrush>
        </Setter.Value>
    </Setter>
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="FontFamily" Value="Bauhaus 93"/>
    <Setter Property="FontSize" Value="12pt"/>
    <Setter Property="FontWeight" Value="Normal"/>
</Style>

You want to put together a little test control that shows your themed Button against a system-themed Button:

<StackPanel> 
    <Button>System Theme</Button> 
    <Button>Application Theme</Button> 
</StackPanel>

But that won't work - both buttons will inherit your theme. In this case, you can simply tell the second Button not to apply your Style, thus ensuring it inherits the system-defined one:

<Button Style="{x:Null}">System Theme</Button>

But what if you're theming something far more complex, like the DataGrid control? It has child controls (DataGridCell, DataGridRow, DataGridColumnHeader etc), each with their own style. How can you stop all those child controls from inheriting the styles provided by your theme? Well, sometimes the parent control (DataGrid in this case) will expose properties that allow you to override the styles for child controls:

<DataGrid Style="{x:Null}" CellStyle="{x:Null}" RowStyle="{x:Null}" .../>

But this is tedious, error-prone, and not all controls will expose such properties. What, then, is the diligent WPF journeyman to do?

The best way around this problem I've found is to make use of the little-known FrameworkElement.InheritanceBehavior property. This protected property specifies how resource and property inheritance lookups should behave from any FrameworkElement. We can define a simple control with an inheritance behavior to meet our needs as follows:

public sealed class UseSystemTheme : ContentControl 
{ 
    public UseSystemTheme() 
    { 
        this.InheritanceBehavior = System.Windows.InheritanceBehavior.SkipToThemeNow; 
    } 
}

Then, to prevent a control from inheriting our themed Style, we can simply wrap it in a UseSystemTheme. Going back to our Button example, it would look like:

<StackPanel> 
    <local:UseSystemTheme> 
        <Button>System Theme</Button> 
    </local:UseSystemTheme> 
    <Button>Application Theme</Button> 
</StackPanel>

imageNow it's easy to ensure we're comparing the application theme against the system theme. Moreover, you can use the same approach regardless of how complex the contained elements are. So it’ll work whether you’re styling a Button, a DataGrid, or whatever.

NOTE: I actually made my UseSystemTheme control a little more complex than presented here. I had it override its default Background value and set it to SystemColors.WindowBrush. Then I had to provide a template for it because, by default, a ContentControl does not honor its Background property. These changes ensure that every UseSystemTheme instance has the default brush as its background instead of inheriting whatever else you've set in your theme. It’s the child of the UseSystemTheme instance whose resource lookup is affected by InheritanceBehavior, not the UseSystemTheme instance itself!

Friday, July 01, 2011

Call of Duty 2 Startup Error

Kind of random, even for my blog, but I got this error when attempting to re-live the pure awesome that is Call of Duty 2:

---------------------------
WIN_ERROR
---------------------------
Error during initialization:
Couldn't load default_localize.cfg.  Make sure Call of Duty is run from the correct folder.

---------------------------
OK  
---------------------------

I couldn’t find any decent advice on this, so I fiddled around a bit and noticed that the shortcut did not specify a working directory. I tried setting it to the directory to which I’d installed (D:\Program Files (x86)\Activision\Call of Duty 2\) and all was good. I’d imagine this happening in a couple of cases:

  1. If you install to anywhere other than C:\
  2. If you’re installing on a 64 bit machine

These are just theories though – I really can’t be bothered validating this, as I’m too busy shooting nazis.

Saturday, April 09, 2011

RangeCollectionView

WPF and Silverlight both support an abstraction called collection views, manifested by the ICollectionView interface. Whenever you bind to a collection, you're actually binding to a collection view, whether you do so explicitly or not. The original collection is exposed via the ICollectionView.SourceCollection property, but is often referred to as the "underlying collection".

WPF/SL both ship with some commonly-used collection view implementations, but sometimes you need something a little different. In a previous post I showed a custom collection view that can be used to implement virtual paging in Silverlight. In this post I'm going to show you a collection view that exposes a range of items in its underlying collection. This is useful, for example, when you have a larger data set that the user is able to "zoom into". An obvious example is the Google finance charts:

Google Finance Chart

Notice how the chart requires the full set of data in order to render the lower preview area, as well as a subset of data in order to render the upper zoomed area. It is this subset that we want to be able to easily expose via a custom collection view.

My design for this collection view is quite straightforward. Here is the public API:

Class Diagram

As you can see, I called the custom collection RangeCollectionView. It extends the built-in ListCollectionView and adds two important properties: StartIndex and EndIndex. These indexes demarcate the subset of the underlying collection that will be exposed by the range collection view. They are both inclusive.

Suppose you have a list of month names in order ("January"..."December"). If you wrap that list in a range collection view and set StartIndex to 5 and EndIndex to 9, that range collection view will expose only the following items:

  • June
  • July
  • August
  • September
  • October

Internally, RangeCollectionView uses the StartIndex and EndIndex properties to redefine the behaviour of various members defined by ListCollectionView. ListCollectionView is sufficiently flexible to allow this, which is a very good thing because it means we get certain features for "free", like sorting and grouping.

Going back to our chart example, consider that we can simply wrap our primary data set in a RangeCollectionView and then modify StartIndex and EndIndex according to the set of data the user has zoomed into. As the user zooms in, the start and end indexes move closer together. As the user zooms out, they widen.

The attached solution provides the code for RangeCollectionView, related unit tests, and a working example of how RangeCollectionView can be used to achieve an experience similar to that on Google finance. Here is an example of it in action:

Range Collection View Demo

Download Sample

Wednesday, March 23, 2011

TieredVisualStateManager

A couple of posts ago, I discussed a custom visual state manager that allows you to impose a minimal amount of time for which a state must be active. In this post I'm going to push the visual state management infrastructure even further out of its comfort zone.

Imagine you've just finished developing a kick-ass WPF application that has extracted "ooooh"s and "aaaah"s from all the stakeholders. You're at the pub when your boss calls you and asks why the application keeps hanging on his machine. "Hanging?", you ask, reluctantly putting down the celebratory beer. "Yes, it stops and starts like a handsaw.", he replies. "Oh, you mean stuttering." "That's what I said - stuttering."

Intrigued, you head back into the office. Before your boss even fires up the application you notice the turbo button on his PC and your head drops into your hands. Predictably, your application takes a full minute to fire up and then renders at a frame rate of three Walt Disney animators.

"It's your machine.", you say dejectedly. "I don't care what it is - fix it. Today!"

You drag your feet all the way back to your desk, pondering the state of the job market in your area. You sit down at your desk and crack open your solution, the solution that was your pride and joy a mere hour ago. You don't bother firing up a profiler because you had already profiled and tweaked the performance of the application prior to shipping - you know there's nothing significant left to squeeze out of it.

At this point most of us would simply strip out animations, transitions, effects, and other non-critical niceties that are placing a strain on the frame rate. This has the advantage of getting your boss off your back (and you back at the pub), but the disadvantage of lowering the experience for users who have hardware dating post 1990. Not to mention the fact that you've likely had a UX designer put in significant amounts of thought and effort into producing the experience.

I anticipate this exact problem with the application I'm currently developing - there is a varied user base with varied hardware capabilities. OK, none have a turbo button that I'm aware of, but it wouldn't surprise me.

I decided to put some effort in to come up with a mechanism by which we could tier the experience in our application. That is, a mechanism by which the entire skin of the application can be scaled up or down based on some criteria. Whilst you could achieve this feat by maintaining multiple resource dictionaries - one for each tier in your scale - this results in a lot of duplication of work, since many tiers will include aspects present in other tiers. Another approach might involve some kind of global bindable property that determines the active tier, and then expecting XAML to trigger changes off that property. Again, this would work but would result in a mess of triggers in your templates and styles.

A far better approach, I think, is to create a custom visual state manager. Doing so can allow us to avoid duplication of work, and can keep our XAML relatively clean. This visual state manager, which I've called TieredVisualStateManager, must provide the following:

  • a global property tracking the tier that is currently active for the application
  • the ability to assign visual states and transitions to specific tiers
  • the ability to aggregate visual states and transitions together according to the tier to which they are assigned, and the active tier for the application

You can find a sample attached. In this sample, I've re-templated a CheckBox control as follows:TieredVSM

  • low tier: very simple graphic, no animations
  • medium tier: more appealing graphics, animates checking/unchecking by fading
  • high tier: same graphics as medium, same checking/unchecking transitions, but also adds another animation to shake the CheckBox up and down (when checking) or left and right (when unchecking). In addition, there is a non-stop glowing animation.

For the full control template, check out the attached sample. However, this outline gives you a taste for how things work:

<local:TieredVisualStateManager x:Key="TieredVisualStateManager"/>
    
<Style TargetType="CheckBox">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="CheckBox">
                <BulletDecorator VisualStateManager.CustomVisualStateManager="{StaticResource TieredVisualStateManager}">
                    <local:TieredVisualStateManager.TieredVisualStateGroups>
                        <local:TieredVisualStateGroupCollection>
                            <!--
                            CommonStates
                            -->
                            <local:TieredVisualStateGroup Name="CommonStates" Tier="Low">
                                <local:TieredVisualState Name="Normal"/>
                            </local:TieredVisualStateGroup>
                            <local:TieredVisualStateGroup Name="CommonStates" Tier="Medium">
                                <local:TieredVisualState Name="Normal">
                                    <!-- fancy brushes applied here -->
                                </local:TieredVisualState>
                            </local:TieredVisualStateGroup>
                                
                            <local:TieredVisualStateGroup Name="CommonStates" Tier="High">
                                <local:TieredVisualState Name="Normal">
                                    <!-- glowing animation applied here -->
                                </local:TieredVisualState>
                            </local:TieredVisualStateGroup>
                            <!--
                            CheckStates
                            -->
                            <local:TieredVisualStateGroup Name="CheckStates" Tier="Low">
                                <local:TieredVisualState Name="Unchecked"/>
                                <local:TieredVisualState Name="Checked">
                                    <!-- display check mark here -->
                                </local:TieredVisualState>
                            </local:TieredVisualStateGroup>
                            <local:TieredVisualStateGroup Name="CheckStates" Tier="Medium">
                                <local:TieredVisualStateGroup.Transitions>
                                    <VisualTransition From="Unchecked" To="Checked">
                                        <!-- fade check mark in here -->
                                    </VisualTransition>
                                    <VisualTransition From="Checked" To="Unchecked">
                                        <!-- fade check mark out here -->
                                    </VisualTransition>
                                </local:TieredVisualStateGroup.Transitions>
                            </local:TieredVisualStateGroup>
                                
                            <local:TieredVisualStateGroup Name="CheckStates" Tier="High">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition From="Unchecked" To="Checked">
                                        <!-- shake the check box up and down here -->
                                    </VisualTransition>
                                    <VisualTransition From="Checked" To="Unchecked">
                                        <!-- shake the check box left and right here -->
                                    </VisualTransition>
                                </VisualStateGroup.Transitions>
                            </local:TieredVisualStateGroup>
                        </local:TieredVisualStateGroupCollection>
                    </local:TieredVisualStateManager.TieredVisualStateGroups>
                    
                    ...
                </BulletDecorator>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
Importantly, notice how I have not repeated anything. For example, the transitions to fade the check mark in and out are defined in the medium tier, but they will also be present if the active tier is high. Similarly, the logic to turn on the check mark is defined in the low tier CheckStates, but is inherited by higher tiers. This aggregation is controlled by the TieredVisualStateManager.AggregateTiers property, which is true by default. Setting it to false may be useful in some scenarios, but it does mean you may need to repeat yourself.

When used carefully, the TieredVisualStateManager allows you to segregate your application experience so that users with rubbish hardware do not impose on the experience of users with decent hardware. Or it may be that some users have great machines, but just dislike animations, and would rather save that little bit of time by turning them off.

But the TieredVisualStateManager is not just about animations. You can control almost anything through this mechanism: turn effects on for high experiences, turn semi-transparency off for low experiences, remove parts of the visual tree for low experiences, etc. You can even change the tier structure to suit your needs - I just defined Low, Medium, and High because it makes sense for my scenario. Just make sure you define them in order from lowest to highest experience because the internal logic of TieredVisualStateManager depends on that order.

In order to actually set the active tier, you might use configuration, an options dialog, diagnostics of the user's machine, or some combination of both. I'm planning on just shipping with a high experience on, and exposing the setting somewhere in an options dialog.

"What about the Blend experience?", I hear you ask. Well, I personally don't use Blend the way in which it is purported to be used – I just don't think it works that well. I prefer using Blend for discrete tasks that are normally disconnected from my main solution. If I generate XAML with Blend, I always copy it manually and vet it for verbosity and other little pet hates of mine. Anyway, the fact is that we’ve taken the VSM sufficiently far enough out of its comfort zone that getting it to work in Blend is likely an impossibility. So I didn't put any effort into making the TieredVisualStateManager blendable. My plan is to obtain the high experience assets from the UX guy, and to "manually" apply tiering to them.

It’s an unfortunate story for Silverlight, too. For whatever reason, VisualStateGroup and VisualState are both sealed in Silverlight, which obviously breaks the entire approach to this problem. Perhaps it’s possible to hack around it, but until I need it in Silverlight I don’t plan on putting myself through that pain.

Download Sample

Yes, you could abstract the definition of tiers out into a property on TieredVisualStateManager, thus yielding even more flexibility.

Saturday, February 26, 2011

BooleanToVisibilityConverter

This is a small thing, really, but how often do you curse WPF's BooleanToVisibilityConverter (or Silverlight’s lack thereof) because it doesn't allow you to convert true to Visibility.Collapsed instead of Visibility.Visible? Or what about converting false to Visibility.Hidden instead of Visibility.Collapsed?

Well, it happens to me in just about every non-trivial project, so I tend to use my own BooleanToVisibilityConverter that addresses these requirements. Here it is:

public sealed class BooleanToVisibilityConverter : IValueConverter
{
    public bool IsReversed { get; set; }
    public bool UseHidden { get; set; }
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var val = System.Convert.ToBoolean(value, CultureInfo.InvariantCulture);
        if (this.IsReversed)
        {
            val = !val;
        }
        if (val)
        {
            return Visibility.Visible;
        }
        return this.UseHidden ? Visibility.Hidden : Visibility.Collapsed;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Now I can convert true to Visibility.Collapsed like this:

<local:BooleanToVisibilityConverter x:Key="whatever" IsReversed="true"/>
...
<Button Visibility="{Binding IsChecked, ElementName=someCheckBox, Converter={StaticResource whatever}}"/>

Or I can convert false to Visibility.Hidden rather than Visibility.Collapsed like this:

<local:BooleanToVisibilityConverter x:Key="whatever" UseHidden="true"/>
...
<Button Visibility="{Binding IsButtonEnabled, Converter={StaticResource whatever}}"/>

I can even convert true to Visibility.Hidden and false to Visibility.Visible like this:

<local:BooleanToVisibilityConverter x:Key="whatever" IsReversed="true" UseHidden="true"/>
...
<Button Visibility="{Binding IsButtonDisabled, Converter={StaticResource whatever}}"/>

Anyway, that's as much flexibility as I've needed when converting Boolean values to Visibility values.