Colin Eberhardt's Adventures in WPF

Silverlight MultiBinding updated, adding support for ElementName and TwoWay binding

August 12th, 2010

This blog post describes an update to the Silverlight 4 MultiBinding technique I blogged about a couple of months ago to add support for ElementName binding and TwoWay binding.

A few months ago I posted an update to my MultiBinding solution for Silverlight 4. This technique allows you to perform the same kind of multibindings which are possible in WPF, where a property value is bound to multiple sources via a special value converter that implements the IMultiValueConverter interface, which describes how these values are combined. This update proved popular once again, and I received a few requests to add support for ElementName and TwoWay binding. I like challenge! This blog post describes how these two features were implemented, but if you just want to grab the code, you will find the link at the end of this article.

A brief recap

Before I go into the implementation details for the new features I will provide a brief recap of how my multibinding solution works. The XAML for creating a multibinding looks like the following:

<TextBlock Foreground="White" 
  <local:BindingUtil.MultiBindings>
    <local:MultiBindings>
      <local:MultiBinding TargetProperty="Text"
                          Converter="{StaticResource TitleConverter}">
        <local:BindingCollection>
          <Binding Path="Surname"/>
          <Binding Path="Forename"/>
        </local:BindingCollection>
      </local:MultiBinding>
      <local:MultiBinding TargetProperty="ToolTipService.ToolTip"
                          Converter="{StaticResource TitleConverter}">
        <local:BindingCollection>
          <Binding Path="Surname"/>
          <Binding Path="Forename"/>
        </local:BindingCollection>
      </local:MultiBinding>
    </local:MultiBindings>
  </local:BindingUtil.MultiBindings>
</TextBlock>

Here the Text property and attached ToolTip property of the TextBlock are bound to both the Surname and Forename property of the business object which is set as the DataContext of our view.

The value converter is as follows:

public class TitleConverter : IMultiValueConverter
{
 
  public object Convert(object[] values, Type targetType,
    object parameter, CultureInfo culture)
  {
    string forename = values[0] as string;
    string surname = values[1] as string;
 
    return string.Format("{0}, {1}", surname, forename);
  }
 
  ...
}

In the example below you can see that as you change the forename or surname, the title text and tooltip are updated by the multibinding.

So how does this work?

When a multibinding is created and added to an element via the BindingUtil.MultiBindings attached property, the MultiBinding instance is added to a virtual-branch, this is a branch of the visual tree that obtains the DataContext of the element to which it is bound, but is not added to the visual tree itself. The MultiBinding then creates a BindingSlave instance for each of the given Bindings. These ‘slave’ elements inherit the MultiBinding DataContext and are used to evaluate each of the individual Bindings. The MultiBinding instance aggregate the results of each binding with the IMultiValueConverter used to compute the ConvertedValue property which is bound to the target property on the element to which this MultiBinding as attached.

ElementName binding

So why does the above solution not work if one of the multibindings uses ElementName to locate the source rather than using the inherited DataContext? The problem here is that the MultiBinding and its BindingSlave instances are located in a virtual branch and are therefore not in the same namescope as the target element. As a result, they cannot perform look up of named elements.

In order to solve this problem I created a BindingSlave subclass specifically for ElementName binding. This slave locates the source element named via the ElementName property and sets it as the Source of the binding it uses to compute its Value property.

The only minor complication I encountered is that the MultiBinding might be constructed before the element referenced via ElementName. For this reason the binding slave must handle LayoutUpdated events to ensure that the named element is located if it is constructed after the multibinding.

public class ElementNameBindingSlave : BindingSlave
{
  private FrameworkElement _multiBindingTarget;
 
  /// <summary>
  /// The source element named in the ElementName binding
  /// </summary>
  private FrameworkElement _elementNameSource;
 
  private Binding _binding;
 
  public ElementNameBindingSlave(FrameworkElement target, Binding binding)
  {
    _multiBindingTarget = target;
    _binding = binding;
 
    // try to locate the named element
    ResolveElementNameBinding();
 
    _multiBindingTarget.LayoutUpdated += MultiBindingTarget_LayoutUpdated;
  }
 
  /// <summary>
  /// Try to locate the named element. If the element can be located, create the required
  /// binding.
  /// </summary>
  private void ResolveElementNameBinding()
  {
    _elementNameSource = _multiBindingTarget.FindName(_binding.ElementName) as FrameworkElement;
    if (_elementNameSource != null)
    {
      SetBinding(BindingSlave.ValueProperty, new Binding()
      {
        Source = _elementNameSource,
        Path = _binding.Path,
        Converter = _binding.Converter,
        ConverterParameter = _binding.ConverterParameter
      });
    }
  }
 
  private void MultiBindingTarget_LayoutUpdated(object sender, EventArgs e)
  {
    // try to locate the named element 
    ResolveElementNameBinding();
  }
}

The example below demonstrates element name binding by binding the Value of two Slider elements to a TextBlock which displays the sum of the two values.

<TextBlock Grid.Column="1">
  <local:BindingUtil.MultiBindings>
    <local:MultiBindings>
      <local:MultiBinding TargetProperty="Text"
                          Converter="{StaticResource SliderValueConverter}">
        <local:BindingCollection>
          <Binding ElementName="sliderOne" Path="Value"/>
          <Binding ElementName="sliderTwo" Path="Value"/>
        </local:BindingCollection>
      </local:MultiBinding>
    </local:MultiBindings>
  </local:BindingUtil.MultiBindings>
</TextBlock>
 
<Slider x:Name="sliderOne" Value="10"/>
 
<Slider x:Name="sliderTwo"  Value="20"/>

Here is the value converter that is used to compute the sum:

public class SliderValueConverter : IMultiValueConverter
{
 
  public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    if (values.Length != 2 ||
        values[0] == null ||
        values[1] == null)
      return null;
 
    return (double)values[0] + (double)values[1];
  }
 
  ...
}

You could of course use element name binding to combine the surname / forename in the first example in this blog post, but in this context it is more likely that you will want to back your UI with a business object. It is also possible to mix element name and regular (i.e. Source=DataContext) binding.

I find multibindings which use ElementName references most useful when creating complex UI layouts that do not lend themselves to being implemented using Panels. For a good example see my article on the implementation of a BulletGraph control which makes extensive use of this technique in order that the layout of the control is performed entirely within XAML.

TwoWay Binding

A two-way multibinding must be able to handle updates to the combined value and split it up into its constituent parts in order to update the multiple source bindings. In the first example where a forename and surname property are combined into a “surname, forename” string it is possible to convert back the other way, but in the example above where the values of two Sliders are combined a reverse conversion is not possible.

Modifying this multibinding solution to permit two way binding was relatively straightforward. A property changed event handler was added to the MultiBindig.ConverterValue (which is bound to the multi binding target) so that we can determine when changes have been made. This handler then uses the IMultiValueConverter to convert the value into the multiple source properties and updates the binding slaves accordingly:

/// <summary>
/// Handles property changes for the ConvertedValue property
/// </summary>
private void OnConvertedValuePropertyChanged()
{
    OnPropertyChanged("ConvertedValue");
 
    // if the value is being updated, but not due to one of the multibindings
    // then the target property has changed.
    if (!_updatingConvertedValue)
    {
      // convert back
      object[] convertedValues = Converter.ConvertBack(ConvertedValue, null,
          ConverterParameter, CultureInfo.InvariantCulture);
 
      // update all the binding slaves
      if (Children.Count == convertedValues.Length)
      {
        for (int index = 0; index < convertedValues.Length; index++)
        {
          ((BindingSlave)Children[index]).Value = convertedValues[index];
        }
      }
    }
}

In order to use two way multibinding both the multibinding mode and the bindings within the BindingCollection must be set to TwoWay. See the example given below:

<TextBlock Text="Surname:"/>
<TextBox  Grid.Column="1" Text="{Binding Path=Surname, Mode=TwoWay}"/>
 
<TextBlock Grid.Row="1" Text="Forename:"/>
<TextBox Grid.Row="1"  Grid.Column="1" Text="{Binding Path=Forename, Mode=TwoWay}"/>
 
<TextBlock Grid.Row="2" Text="Combined:"/>
<TextBox Grid.Row="2"  Grid.Column="1" >
  <local:BindingUtil.MultiBindings>
    <local:MultiBindings>
      <local:MultiBinding TargetProperty="Text"
                        Converter="{StaticResource TitleConverter}"
                        Mode="TwoWay">
        <local:BindingCollection>
          <Binding Path="Surname" Mode="TwoWay"/>
          <Binding Path="Forename" Mode="TwoWay"/>
        </local:BindingCollection>
      </local:MultiBinding>
    </local:MultiBindings>
  </local:BindingUtil.MultiBindings>
</TextBox>

You can have try of two way multibinding below where the values of the forename and surname text boxes are combined in the box below. However, you ca also make updates to the combined result which will then be converted back into its constituent parts:

And here is the value converter:

public class TitleConverter : IMultiValueConverter
{
  #region IMultiValueConverter Members
 
  public object Convert(object[] values, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    string forename = values[0] as string;
    string surname = values[1] as string;
 
    return string.Format("{0}, {1}", surname, forename);
  }
 
  public object[] ConvertBack(object value, Type[] targetTypes,
    object parameter, System.Globalization.CultureInfo culture)
  {
    string source = value as string;
    var pos = source.IndexOf(", ");
 
    string forename = source.Substring(pos + 2);
    string surname = source.Substring(0, pos);
 
    return new object[] { forename, surname };
  }
 
  #endregion
}

So, there you have it, two-way and element name multibinding :-)

You can download the full source, including a WPF build (thanks to Stefan Olson) here: SLMultiBinding.zip

Now … go forth and multibind.

Regards, Colin E.

Exposing and Binding to a Silverlight ScrollViewer’s Scrollbars

July 21st, 2010

The Silverlight ScrollViewer exposes readonly properties which indicate the current vertical and horizontal scroll offset, and methods for setting the current offset. In this blog post I demonstrate a simple attached behaviour that exposes these offsets as read / write dependency properties allowing them to be bound to.

The Silverlight ScrollViewer is a very useful control, if you have some content that is larger than the space available in your application, just sit it inside a ScrollViewer and it will automatically add vertical or horizontal scrollbars as required. Simple.

The ScrollViewer exposes readonly properties which indicate the current vertical and horizontal scroll offset. I have been used a ScrollViewer on many occasions without finding this to be an issue, however, recently I was creating an MVVM application which contained a list of items within a ScrollViewer. When the user clicks on an item, they are taken to its ‘details’ page, they then have the option to return to the list. I wanted the list to have the same state when the user returned to it, i.e. the same scroll location. Naturally, with this being an MVVM application, the scroll position belongs on the ViewModel and should be relayed to the View via a binding. However, with the offset properties on the ScrollViewer being readonly, this was not possible. Time to get creative!

The template of the ScrollViewer contains two ScrollBar instances, one horizontal and one vertical. The basic idea behind my solution is to add an attached behaviour (i.e. an attached property, that on attachment performs some logic on the target element) which walks the visual tree to find these scrollbars exposing their offset values.

Firstly, we’ll start by defining an attached property called VerticalOffset, which will be used to expose the position of the vertical scrollbar. Dependency property boiler plate code follows:

public static class ScrollViewerBinding
{
  #region VerticalOffset attached property
 
  /// <summary>
  /// Gets the vertical offset value
  /// </summary>
  public static double GetVerticalOffset(DependencyObject depObj)
  {
    return (double)depObj.GetValue(VerticalOffsetProperty);
  }
 
  /// <summary>
  /// Sets the vertical offset value
  /// </summary>
  public static void SetVerticalOffset(DependencyObject depObj, double value)
  {
    depObj.SetValue(VerticalOffsetProperty, value);
  }
 
  /// <summary>
  /// VerticalOffset attached property
  /// </summary>
  public static readonly DependencyProperty VerticalOffsetProperty =
      DependencyProperty.RegisterAttached("VerticalOffset", typeof(double),
      typeof(ScrollViewerBinding), new PropertyMetadata(0.0, OnVerticalOffsetPropertyChanged));
 
  #endregion
}

We’ll also define a private attached property of type ScrollBar, which will be used to store a reference to the scrollbar once it has been located within the ScrollViewers visual tree:

/// <summary>
/// An attached property which holds a reference to the vertical scrollbar which
/// is extracted from the visual tree of a ScrollViewer
/// </summary>
private static readonly DependencyProperty VerticalScrollBarProperty =
    DependencyProperty.RegisterAttached("VerticalScrollBar", typeof(ScrollBar),
    typeof(ScrollViewerBinding), new PropertyMetadata(null));

Because this attached property is private, I haven’t bothered to create CLR property accessors which are only used for convenience.

The VerticalOffset attached property has a changed event handler. When the property is first attached to a ScrollViewer, this allows us to walk the visual tree constructed from the ScrollViewer’s template and extract the scrollbars. It is this extra logic that adds new functionality to the ScrollViewer which is what makes this an attached behaviour, as opposed to a regular attached property which merely holds state.

When an attached property is first associated with its target, the visual tree described by the target’s template has not yet been constructed. With WPF, you would typically register for the Loaded event on the target, and on handling this event walk the visual tree that will have been constructed. However, in Silverlight there is a very subtle difference, apparently the Loaded event is not guaranteed to occur after the template is applied. Oh dear. The only other alternative is to handle the LayoutUpdated which is fired whenever changes are made to the visual tree. However, there is another complication! The LayoutUpdated event always has a null sender, this might look like a bug, but it is actually by design. This means that if we handle the LayoutUpdated event on the ScrollViewer element which the property is attached to, the method invoked as the event handler no longer has a reference to the ScrollViewer!

In order to get around this problem, the attached property changed handler is as follows:

/// <summary>
/// Invoked when the VerticalOffset attached property changes
/// </summary>
private static void OnVerticalOffsetPropertyChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
  ScrollViewer sv = d as ScrollViewer;
  if (sv != null)
  {
    // check whether we have a reference to the vertical scrollbar
    if (sv.GetValue(VerticalScrollBarProperty) == null)
    {
      // if not, handle LayoutUpdated, which will be invoked after the
      // template is applied and extract the scrollbar
      sv.LayoutUpdated += (s, ev) =>
        {
          if (sv.GetValue(VerticalScrollBarProperty) == null)
          {
            GetScrollBarsForScrollViewer(sv);
          }
        };
    }
    else
    {
      // update the scrollviewer offset
      sv.ScrollToVerticalOffset((double)e.NewValue);
    }
  }
}

Firstly, we check whether we have already got a reference to the scrollbar, if not, we attached a LayoutUpdated event handler. But instead of defining this handler as a method, it is written as an lambda expression (i.e. an anonymous delegate), so that the ScrollViewer reference is captured using closure.

When the LayoutUpdated event is fired the following method is invoked:

/// <summary>
/// Attempts to extract the scrollbars that are within the scrollviewers
/// visual tree. When extracted, event handlers are added to their ValueChanged events.
/// </summary>
private static void GetScrollBarsForScrollViewer(ScrollViewer scrollViewer)
{
  ScrollBar scroll = GetScrollBar(scrollViewer, Orientation.Vertical);
  if (scroll != null)
  {
    // save a reference to this scrollbar on the attached property
    scrollViewer.SetValue(VerticalScrollBarProperty, scroll);
 
    // scroll the scrollviewer
    scrollViewer.ScrollToVerticalOffset(
      ScrollViewerBinding.GetVerticalOffset(scrollViewer));
 
 
    // handle the changed event to update the exposed VerticalOffset
    scroll.ValueChanged += (s, e) =>
      {
        SetVerticalOffset(scrollViewer, e.NewValue);
      };
  }
}
 
/// <summary>
/// Searches the descendants of the given element, looking for a scrollbar
/// with the given orientation.
/// </summary>
private static ScrollBar GetScrollBar(FrameworkElement fe, Orientation orientation)
{
  return fe.Descendants()
            .OfType<ScrollBar>()
            .Where(s => s.Orientation == orientation)
            .SingleOrDefault();
 
}

The GetScrollBar method is invoked, which then uses Linq-to-VisualTree to walk the descendants of the ScrollViewer in an attempt to find scrollbars of a given orientation. If one is found, it is stored in the private attached property. We also subscribe to the events which the scrollbar raises when its value changes, in order to update the VerticalOffsetProperty. Again, a lambda expression is used to capture the ScrollViewer which this scrollbar is associated with, so that we do not have to store a relationship from scrollbar to scrollviewer.

As an aside, it would have been a bit more elegant to use binding to connect our VerticalScrollOffset to the scrollbar value as follows:

scroll.SetBinding(ScrollBar.ValueProperty,
  new Binding("(ScrollViewerBinding.VerticalOffset)")
  {
    Source = scrollViewer
  });

However, it appears that there is a bug in Silverlight which makes binding to custom attached properties in code behind fail with a critical error. Until this is fixed, we have to take care of the ‘binding’, by handling change events from both properties, manually.

Using this attached behaviour is as simple as the following:

<ScrollViewer 
    local:ScrollViewerBinding.VerticalOffset="{Binding YPosition, Mode=TwoWay}"
    local:ScrollViewerBinding.HorizontalOffset="{Binding XPosition, Mode=TwoWay}">
    <!-- Big content goes here! -->
</ScrollViewer>

The following little demo binds the X & Y offset of the scrollviewer to two CLR properties (with property changed notifications) defined in code behind. These same properties are bound to the text fields below:

You can download the complete sourcecode for this blog post: ScrollOffsetBinding.zip

Regards, Colin E.

Modal dialogs in cross-platform WPF/Silverlight applications

June 18th, 2010

This blog post looks at the problem of showing modal dialog windows in applications that target both the Silverlight and WPF platforms. A solution is provided which allows modal dialogs to be written that work well for both technologies.

Silverlight is, roughly speaking, a subset of Windows Presentation Foundation (WPF). This means that it is possible to write applications that target both frameworks, allowing you to develop applications that work both on the desktop and on the web using the same codebase.

However, Silverlight is not a strict subset of WPF, the differences between the two frameworks can be summed up as follows:

  1. Things that WPF has that Silverlight does not
  2. Things that Silverlight has that WPF has not
  3. Things that both framework have but are different!

There are a great many things that the WPF framework has that Silverlight does not, mostly due to the size constraints of Silverlight being a browser plugin (1). There are a few things that Silverlight has, such as Out Of Browser (OOB) support and local storage which are not in WPF because they relate to the browser context (2). If these areas are avoided, the creation of a cross-platform application can be straightforward, however it is point (3) that causes problems.

When developing an application which targets both frameworks it makes sense to do most of your development primarily in Silverlight in order to avoid accidentally using WPF features. In practical terms the way most people  achieve this is to create two projects, one Silverlight, one WPF. The WPF project will include links to the files within the Silverlight project (This is achieved by selecting “Add existing item …”, then selecting the “Add as link” option). The following diagram highlights the linked files in the attached project:

The first obstacle to overcome is the entry point into the application. Silverlight applications render a single UserControl as the RootVisual of the instantiated application, wheres WPF applications will create a Window instance when launched. This problem is easily solved by simply hosting the UserControl, which is the starting point for the Silverlight application, within a Window:

<Window x:Class="ModalDialogDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ModalDialogDemo"
        Title="Modal dialog demo" SizeToContent="WidthAndHeight">
    <Grid>
        <local:MainControl/>
    </Grid>
</Window>

With this small obstacle cleared, you can create some pretty decent cross-framework applications (as long as you avoid the temptation of looking at all the WPF controls you are missing out on!).

I have created quite a few WPF / Silverlight applications in this manner, and the process, although a little clunky (I usually have two Visual Studio instances open so that I can keep the two versions in synch), works pretty well. However, the next major obstacle I experienced is that of creating a modal dialog. This falls firmly into category (3).

Modal dialog functionality was added in Silverlight 3. To pop-up a modal dialog, create a page which inherits from ChildWindow (rather than the usual UserControl). This class provides a Show method which pops up the dialog, and a Closed event which is raised when the user hits the OK or Cancel. For example, you might create an error dialog and use it as follows:

ErrorDialog dlg = new ErrorDialog ();
dlg.Closed += new EventHandler(OnErrorDialogClosed);
dlg.Show();

Visual Studio has a template for the creating of modal dialogs via ChildWindow:

However, with WPF things are a bit different. Whilst Silverlight modal dialogs are constrained to live within the Silverlight container on your web page, WPF dialogs are proper windows that you can move around your desktop (Interestingly, someone on stackoverflow has emulated Silverlight’s ChildWindow in WPF, although I am not convinced that this will look right on a desktop application). Visual Studio does not have a template for creating modal dialogs in WPF, however, there is more than enough documentation regarding the various types of dialog on MSDN.

With WPF a Window can be shown as modal as follows:

ErrorDialog dlg = new ErrorDialog ();
dlg.ShowDialog();

There are a few differences here, firstly Window has a Show method just like Silverlight’s ChildWindow, however, this will result in showing a modeless window. Instead we invoke the ShowDialog method which shows a modal dialog, but there is another subtle difference, whilst ChildWindow.Show returns immediately, with an event handler required to capture the modal dialog being closed, WPF’s Window.ShowDialog blocks until the dialog is closed.

So … each framework has different classes for modal dialogs, and different interfaces for each. How do we resolve these differences?

Early in this blog post we saw how the UserControl which is the entry point into our Silverlight application can be hosted in a WPF Window. The same approach can be used for modal dialogs. We can create our modal dialog as a UserControl:

<UserControl x:Class="ModalDialogDemo.ModalDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">    
    <Grid x:Name="LayoutRoot">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
 
        <TextBlock Text="What is your favourite colour? " VerticalAlignment="Center"/>
        <TextBox x:Name="colourInput" Grid.Column="1" VerticalAlignment="Center" Width="80"/>
    </Grid>
</UserControl>

With the above control hosted in a ChildWindow or Window depending on the platform. The problem is, we cannot explicitly make reference to either of these types in our common code (i.e. the code shared by WPF / Silverlight via file linking). This problem can be solved by employing the Gang of Four Adapter pattern.

We define an interface that gives the functionality that we require for hosting our UserControl as a modal dialog:

/// <summary>
/// An interface which provides a host for some content which should be rendered
/// as a modal dialog
/// </summary>
public interface IModalDialogHost
{
    /// <summary>
    /// Gets or sets the content to host
    /// </summary>
    UserControl HostedContent { get; set; }
 
    /// <summary>
    /// Gets the dialog title
    /// </summary>
    string Title { set; }
 
    /// <summary>
    /// Gets the dialog result (i.e. OK, Cancel)
    /// </summary>
    bool? DialogResult { get; }
 
    /// <summary>
    /// Shows the dialog, invoking the given callback when it is closed
    /// </summary>
    void Show(DialogClosed closedCallback);        
}
 
/// <summary>
/// A callback which is invoked when a modal dialog is closed.
/// </summary>
public delegate void DialogClosed(IModalDialogHost host);

Within the Silverlight project we create a concrete implementation of this interface based on ChildWindow:

<controls:ChildWindow x:Class="ModalDialogDemo.ModalDialogHost"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:local="clr-namespace:ModalDialogDemo"
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
 
        <!-- the hosted content -->
        <ContentPresenter x:Name="contentHost"/>
 
        <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75"
                Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23"
                HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
    </Grid>
</controls:ChildWindow>

With the interface implemented in code-behind as follows:

/// <summary>
/// A Silverlight implementation of IModalDialogHost
/// </summary>
public partial class ModalDialogHost : ChildWindow, IModalDialogHost
{
    ...
 
    public UserControl HostedContent
    {
        get
        {
            return (UserControl)contentHost.Content;
        }
        set
        {
            contentHost.Content = value;
        }
    }
 
    public void Show(DialogClosed closedCallback)
    {
        Show();
 
        // handle the Closed event, invoking the callback provided by the client
        Closed += (s, e) =>
            {
                closedCallback(this);
            };
    }
 
    public new string Title
    {
        set { base.Title = value; }
    }
}

Within WPF we have a similar implementation based on Window:

<Window x:Class="ModalDialogDemo.ModalDialogHost"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        SizeToContent="WidthAndHeight"
        ShowInTaskbar="False">
    <Grid Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
 
        <!-- the hosted content -->
        <ContentPresenter x:Name="contentHost" Margin="10"/>
 
        <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75"
                Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75"
                Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
    </Grid>
</Window>

Note that with WPF we have a few extra configuration options to consider, such as the ShowInTaskbar property. The code-behind again implements our adapter interface:

/// <summary>
/// A WPF implementation of IModalDialogHost
/// </summary>
public partial class ModalDialogHost : Window, IModalDialogHost
{
    ...
 
    public UserControl HostedContent
    {
        get
        {
            return (UserControl)contentHost.Content;
        }
        set
        {
            contentHost.Content = value;
        }
    }
 
    public void Show(DialogClosed closedCallback)
    {
        ShowDialog();
 
        // invoke the callback when the dialog is closed
        closedCallback(this);
    }
}

So, within each project we have a framework specific implementation of this host. This allows us to create modal dialogs from the common / core code as follows:

IModalDialogHost dlg = new ModalDialogHost();
dlg.Title = "Pick a Colour";
dlg.HostedContent = new ModalDialog();
dlg.Show(DialogClosedHandler);
 
...
 
private void DialogClosedHandler(IModalDialogHost dialogHost)
{
    string colour = ((ModalDialog)dialogHost.HostedContent).SelectedColour;
    resultTextBox.Text = colour;                        
}

Here is a Silverlight application with a dialog implemented using this hosting concept:

And here is a screenshot of the equivalent WPF application, where you can see the modal dialog is popped up as a window:

(You can download the sourcecode below if you want to have a go with the WPF app)

One final note … I have deliberately steered clear of the whole patterns business in this blog post. As you are probably aware, the Model-View-ViewModel (MVVM) pattern is very popular within Silverlight and WPF, and often other patterns such as Service Locator and Dependency Injection are thrown into the mix. However, the community has been struggling a little with how best to model modal dialog behaviours from within an MVVM ViewModel. Interestingly a couple of developers have supplied solutions on codeproject and personal blogs, both faced questions and even criticisms that their approaches were not correct and unit testable (one of the central tenets of MVVM) and quickly followed up with articles demonstrating that you could in fact unit test their code (published here and here)

Whilst the use of a pattern such as MVVM which enforces a strong separation of application logic from the view will certainly help to solve problems such as WPF / Silverlight framework differences, in this particular instance I wanted to show a simple technical solution rather than walk into the awaiting patterns minefield!

You can download the full sourcecode for this article here: ModalDialogDemo.zip

Regards, Colin E.

Silverlight MultiBinding solution for Silverlight 4

May 10th, 2010

In this post I describe an update to the Silverlight MultiBinding solution I presented last year. This update includes support for Silverlight 4, attached properties and multiple bindings on a single object.

UPDATE: I have updated this code to include ElementName and TwoWay binding. Grab the latest copy here.

MultiBinding is a WPF feature that allows you to bind a single property to a number of sources, with the source values being combined by a value converter. This is a feature that is missing from Silverlight. About a year ago I developed a MultiBinding solution for Silverlight, which has proven very popular! I even had an email from an Microsoft Attorney asking if they could use it in the Silverlight Facebook client (How cool is that :-) ). I was also very happy when Stefan Olson made a few updates to this code to allow multiple MultiBindings on a single object and spotted the SL4 issue. This blog post is a quick demonstration of these new features …

The following application is an example of MultiBindings in action:

In the above application, the TextBox at the top of the page is bound to both the surname and forename properties of our data-object via converter that takes the first letter of the forename and the surname. If you edit the surname or forename (hitting enter or changing focus to commit), the title is updated automatically. The title tooltip is bound to the all three object properties via a different value converter that concatenates all three.

The XAML for this binding is show below:

<TextBlock x:Name="Block" Foreground="White" FontSize="13" Margin="5,0,0,0">
    <local:BindingUtil.MultiBindings>
        <local:MultiBindings>
            <local:MultiBinding TargetProperty="Text"
                                Converter="{StaticResource TitleSummaryConverter}">
                <local:MultiBinding.Bindings>
                    <local:BindingCollection>
                        <Binding Path="Surname"/>                            
                        <Binding Path="Forename"/>
                        </local:BindingCollection>
                </local:MultiBinding.Bindings>
            </local:MultiBinding>
            <local:MultiBinding TargetProperty="ToolTipService.ToolTip"
                                Converter="{StaticResource TitleConverter}">
                <local:MultiBinding.Bindings>
                    <local:BindingCollection>
                        <Binding Path="Surname"/>                            
                        <Binding Path="Forename"/>
                        <Binding Path="Age"/>
                    </local:BindingCollection>
                </local:MultiBinding.Bindings>
            </local:MultiBinding>
        </local:MultiBindings>
    </local:BindingUtil.MultiBindings>
</TextBlock>

In the above you can see that our TextBlock has two multibindings, one on Forename and Surname, and the other which includes all three properties. Note, the second multibinding is on the ToolTipService.ToolTip attached property.

You can download the source for Silverlight MultiBinding here: SLMultiBindingUpdate.zip – thanks again to Stefan Olson who added WPF support to this technique.

If you are interested in the technical details of how this works, I refer you to the original article which describes how the code builds a ‘virtual’ branch on the visual tree in order to evaluate your bindings:

I hope this update is of use to SL4 developers. If you have any feedback, please leave a comment below.

You can download the source for Silverlight MultiBinding here: SLMultiBindingUpdate.zip

Regards, Colin E.