Twitter GitHub Facebook Instagram dirv.me

Daniel Irvine on building software

Avoiding value converters in WPF and Silverlight

15 January 2013

I’ve always had a dislike for WPF value converters, for a variety of reasons.  They can be quite obtuse when reading XAML, and I’m fairly sure they make a nasty dent on application performance.  It always pleases me when I can get rid of them and use another method, so here’s one technique you can use to banish them from your code.

<ItemsControl ItemsSource="{Binding ColumnList}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                    <YourView DataContext="{Binding}">
                        <YourView.Width>
                            <MultiBinding Converter="{StaticResource ColumnCountToWidthConverter}">
                                <Binding Path="{Binding ColumnCount}" />
                                <Binding Path="{Binding AcutalWidth}" />
                            </MultiBinding>
                        </YourView.Width>
                        </YourView>
                </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

The challenge is this: in your view, create x columns that fill the entire width of the screen, where x is variable depending on the data context.  Each column should be of equal width, and should resize if the parent control is resized.

You might think of  using an implementation of IMultiValueConverter, taking two parameters: the number of columns, x, and the ActualWidth of the parent.  Your XAML would then look a little something like this:

The key here is the ColumnCountToWidthConverter.  Now this would all be fine and dandy... except I’m using Silverlight and Silverlight doesn’t support MultiBindings!  So this approach definitely won’t work, but it will work in WPF, even though it’s fugly.

So what’s my preferred approach?  Simple: break MVVM and programmatically create the UIElements in the code-behind.  Doing that means we can use a Grid to handle the auto-resizing widths for us.

Here’s the entire class, so you can see what’s going on.

using System.Windows;
using System.Windows.Controls;

namespace TaskList.UI.Workflow {
    public partial class EqualWidthColumnsWorkflowView : UserControl {
        public EqualWidthColumnsWorkflowView() {
            InitializeComponent();

            this.Loaded += (s, e) => HandleLoaded();
        }

        public void HandleLoaded() {
            GenerateGrid(DataContext as WorkflowViewModel);
        }

        void GenerateGrid(WorkflowViewModel workflowViewModel) {

            var sharedWidth = new GridLength(1, GridUnitType.Star);
            var grid = new Grid();
            int currentColumn = 0;
            foreach (var state in workflowViewModel.WorkflowStates) {
                grid.ColumnDefinitions.Add(new ColumnDefinition { Width = sharedWidth });
                var gridObject = new EqualWidthColumnsWorkflowStateView() { DataContext = state };
                gridObject.SetValue(Grid.ColumnProperty, currentColumn++);
                grid.Children.Add(gridObject);
            }

            _parent.Child = grid;
        }
    }
}
This is the sort of code that makes me feel pretty damn good about myself.  Another value converter disaster successfully averted!

About the author

Daniel Irvine is a software craftsman at 8th Light, based in London. These days he prefers to code in Clojure and Ruby, despite having been a C++ and C# developer for the majority of his career.

For a longer bio please see danielirvine.com. To contact Daniel, send a tweet to @d_ir or use the comments section below.

Twitter GitHub Facebook Instagram dirv.me