Silverlight relay base class

UPDATE: Silverlight 3 (beta 1 at time of this post) supports ElementName and RelativeSource binding :)  See here.

Silverlight 2 doesn’t support WPF’s ElementName or RelativeSource binding.  The simplest solution is to use a “relay” class and have multiple UI elements binding to it.  There are some great posts about it here, here and here.

If you want to write a very simple relay, here’s a base class that can handle the basic plumbing:

public abstract class MultiBindableBase<TKey> : INotifyPropertyChanged
{
    /// <summary>
    /// Dictionary of bindable properties.
    /// </summary>
    private Dictionary<TKey, object> bindables = new Dictionary<TKey,object>();

    /// <summary>
    /// Gets or sets a child value with the specified key.
    /// </summary>
    /// <value>The value to get or set.</value>
    /// <remarks>Listens for PropertyChanged notifications if the value implements <see cref="INotifyPropertyChanged"/>.</remarks>
    protected object this[ TKey key ]
    {
        get
        {
            return bindables.ContainsKey( key )
                ? bindables[ key ]
                : bindables[ key ] = null;
        }

        set
        {
            if ( bindables.ContainsKey( key ) )
            {
                var oldNotify = bindables[ key ] as INotifyPropertyChanged;
                if ( oldNotify != null ) oldNotify.PropertyChanged -= OnNotifyPropertyChanged;
            }

            bindables[ key ] = value;

            var notify = value as INotifyPropertyChanged;
            if ( notify != null ) notify.PropertyChanged += OnNotifyPropertyChanged;

            OnPropertyChanged( key );
        }
    }

    /// <summary>
    /// Called when a property of a child value changes.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
    private void OnNotifyPropertyChanged( object sender, PropertyChangedEventArgs e )
    {
        foreach( var bindable in bindables )
        {
            if ( bindable.Value == sender )
            {
                OnPropertyChanged( bindable.Key );
                break;
            }
        }
    }

    /// <summary>
    /// Raises the PropertyChanged event for the specified key.
    /// </summary>
    /// <param name="key">The key.</param>
    protected void OnPropertyChanged( TKey key )
    {
        OnPropertyChanged( GetPropertyName( key ) );
    }

    /// <summary>
    /// Gets the name of a child property.
    /// </summary>
    /// <param name="key">The key.</param>
    /// <returns>Returns the name of the specified child property.</returns>
    protected abstract string GetPropertyName( TKey key );

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises the PropertyChanged event for the specified property name.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    protected void OnPropertyChanged( string propertyName )
    {
        if ( PropertyChanged != null ) PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
    }

    #endregion
}

It’ll raise its own PropertyChanged events for values you assign, or if those values raise their own PropertyChanged events (if they implement INotifyPropertyChanged).  Here’s a trivial single-property implementation:

public class DependencyValue : MultiBindableBase<int>
{
    public object Value
    {
        get { return this[ 0 ]; }
        set { this[ 0 ] = value; }
    }

    protected override string GetPropertyName( int key )
    {
        return "Value";
    }
}

You could use this as a StaticResource in your XAML, perhaps to make a list of items share a common width (in the snippet below, assume “sharedWidth.Value” is set in code-behind to a calculated value):

<Border x:Name="tooltip" BorderBrush="SteelBlue" CornerRadius="10" Background="#D0FFFFFF" Padding="10" Opacity="0">
    <Border.Resources>
        <local:DependencyValue x:Key="sharedWidth"/>
    </Border.Resources>
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Age" Width="{Binding Value, Source={StaticResource sharedWidth}}" FontSize="9" TextAlignment="Right" Margin="0,0,5,0"/>
            <TextBlock x:Name="tooltipXValue" FontSize="9" FontWeight="Bold"/>
        </StackPanel>
        <ItemsControl x:Name="tooltipContent">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Title}" Width="{Binding Value, Source={StaticResource sharedWidth}}" FontSize="9" TextAlignment="Right" Margin="0,0,5,0"/>
                        <TextBlock Text="{Binding TooltipValue, Converter={StaticResource stringFormatConverter}, ConverterParameter=C0}" FontSize="9" FontWeight="Bold"/>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</Border>

Let me know if it’s any use! 🙂

6 Comments

    1. Fallon – Colin’s solution lets you use attached properties to emulate ElementName and RelativeSource binding. It’s much more refined and robust than my code (which I’m hoping will be made obsolete eventually) 🙂

      Hope this helps!

      Reply

  1. Thanks Chris, that does help.

    Unfortunately, I’m a bit frozen until I can evaluate the changes in Silverlight 3. I don’t know if this is resolved, but with as many changes as they are promising, it’s a bit dicy using any work arounds at this time.

    Reply

  2. Hey Scott – just saw your trackback. Yes .. our ElementName binding techniques will be redundant in the very near future.

    However, my relative source approach might still have some merit, SL3 has Self and TempatedParent bindings but no FindAncestor type. At least not all my work was in vain!

    Regards, Colin E.

    Reply

Leave a comment