Different ComboBox ItemTemplate for dropdown

May 13, 2012

Let’s say we want our ComboBox to display one thing for the selected item and another thing in the dropdown.

It sounds easy enough but the problem is that there is only one ItemTemplate. So how do we specify two different DataTemplates? This is a very common problem and it can be solved in a number of ways.

ItemTemplateSelector

An easy way, which I like, is to use ItemTemplateSelector instead of ItemTemplate. All we need is a new class, ComboBoxItemTemplateSelector, that derives from DataTemplateSelector and defines the two properties SelectedTemplate and DropDownTemplate. So instead of this..

<ComboBox ItemsSource="{Binding MyDataList}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ID}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

..we specify two DataTemplates like this

<ComboBox ItemsSource="{Binding MyDataList}">
    <ComboBox.ItemTemplateSelector>
        <ts:ComboBoxItemTemplateSelector>
            <ts:ComboBoxItemTemplateSelector.SelectedTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding ID}"/>
                </DataTemplate>
            </ts:ComboBoxItemTemplateSelector.SelectedTemplate>
            <ts:ComboBoxItemTemplateSelector.DropDownTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Details}"/>
                </DataTemplate>
            </ts:ComboBoxItemTemplateSelector.DropDownTemplate>
        </ts:ComboBoxItemTemplateSelector>
    </ComboBox.ItemTemplateSelector>
</ComboBox>

ComboBoxItemTemplateSelector

So how does the ComboBoxItemTemplateSelector know if it should return the SelectedTemplate or the DropDownTemplate? Only the items in the dropdown are wrapped in a ComboBoxItem so if we find a ComboBoxItem parent in the visual tree, we’re dealing with the dropdown.

public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate SelectedTemplate { get; set; }
    public DataTemplate DropDownTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item,
                                                DependencyObject container)
    {
        ComboBoxItem comboBoxItem = container.GetVisualParent<ComboBoxItem>();
        if (comboBoxItem == null)
        {
            return SelectedTemplate;
        }
        return DropDownTemplate;
    }
}

Shorter version with attached properties

It can also be shortened a bit (if this is going to be used many times in a project for example) by using attached properties.
I would like to call this one ComboBoxItemTemplateSelector as well but since I’m including them both in the demo project below I had to come up with another name :)

<ComboBox ItemsSource="{Binding MyDataList}"
          ItemTemplateSelector="{StaticResource ComboBoxItemTemplateChooser}">
    <cbp:ComboBoxItemTemplateChooser.SelectedTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ID}"/>
        </DataTemplate>
    </cbp:ComboBoxItemTemplateChooser.SelectedTemplate>
    <cbp:ComboBoxItemTemplateChooser.DropDownTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Details}"/>
        </DataTemplate>
    </cbp:ComboBoxItemTemplateChooser.DropDownTemplate>
</ComboBox>

And in SelectTemplate we do the same thing but instead of returning SelectedTemplate/DropDownTemplate we find the parent ComboBox and return the attached property SelectedTemplate/DropDownTemplate.

public class ComboBoxItemTemplateChooser : DataTemplateSelector
{
    #region SelectedTemplate..
    #region DropDownTemplate..

    public override DataTemplate SelectTemplate(object item,
                                                DependencyObject container)
    {
        ComboBox comboBox = null;
        ComboBoxItem comboBoxItem = container.GetVisualParent<ComboBoxItem>();
        if (comboBoxItem == null)
        {
            comboBox = container.GetVisualParent<ComboBox>();
            return ComboBoxItemTemplateChooser.GetSelectedTemplate(comboBox);
        }
        comboBox =
            ComboBox.ItemsControlFromItemContainer(comboBoxItem) as ComboBox;
        return ComboBoxItemTemplateChooser.GetDropDownTemplate(comboBox);
    }
}

A small demo project with examples of both methods can be downloaded below.
Download demo project here.

Hope you find it useful! :)
/Fredrik


DelayBinding and DelayMultiBinding with Source and Target delay

May 2, 2012

In a recent project I had the need for a Binding that supports specifying a delay for both source and target updates. This is what I ended up with (UpdateSourceDelay and/or UpdateTargetDelay can be specified)

<!-- Binding with Delay -->
<CheckBox IsChecked="{db:DelayBinding Path=BoolProperty,
                                      UpdateTargetDelay='00:00:00.50',
                                      UpdateSourceDelay='00:00:01.00'}"/>
<!-- MultiBinding with Delay -->
<cs:ColorSelector>
    <cs:ColorSelector.SelectedColor>
        <db:DelayMultiBinding Mode="TwoWay"
                              Converter="{StaticResource ColorConverter}"
                              UpdateSourceDelay="00:00:02">
            <Binding Path="Red" />
            <Binding Path="Green" />
            <Binding Path="Blue" />
        </db:DelayMultiBinding>
    </cs:ColorSelector.SelectedColor>
</cs:ColorSelector>

A project containing source code and demo usage can be downloaded in the link below
Download Project Here

Implemention details

Adding a delay option for source updates can be solved by setting UpdateSourceTrigger to Explicit combined with calling UpdateSource on the BindingExpression after the delay has passed as shown in this great article: DelayBinding: a custom WPF Binding.

However, an update to the target is always instant so I needed another Dependency Property to mirror the source and I actually ended up with two, one to mirror the source and one to mirror the target.
 
Extending Binding and MultiBinding

Binding can’t just be extended because ProvideValue is sealed. To workaround this I used a similar approach as described here: A base class for custom WPF binding markup extensions. I wanted a reusable solution that allowed extending both Binding and MultiBinding so I ended up with the following classes

• BindingBaseExtensionBase (BindingBase) – Extends MarkupExtension
• BindingExtensionBase (Binding) – Extends BindingBaseExtensionBase
• MultiBindingExtensionBase (MultiBinding) – Extends BindingBaseExtensionBase

And with those classes in place I could create the classes for DelayBinding and DelayMultiBinding

DelayBindingExtensionExtends BindingExtensionBase
DelayMultiBindingExtension- Extends MultiBindingExtensionBase
 
Dependency Properties and inheritance context for MarkupExtensions

Besides these custom Binding MarkupExtensions, dependency properties are needed so that means an object that derives from DependencyObject. But the Bindings above already derives from MarkupExtension so another class was created. I called this one DelayBindingController and it is used by both DelayBinding and DelayMultiBinding. However, for Bindings to resolve correctly within DelayBindingController an inheritance context is needed because it won’t be in the visual or logical tree. And how do we get an inheritance context? Just inherit from Freezable instead of DependencyObject.

public class DelayBindingController : Freezable
{
    // ...
}

So how does it know who what to inherit the context from? It needs to be connected to whatever DependencyObject this Binding was specified on. This was solved with an Attached Property of type FreezableCollection<DelayBindingController>. This property, in turn, was created in another class called DelayBindingManager. Everytime a DelayBinding is added to a DependencyObject, a corresponding DelayBindingController is added to the attached collection for that DependencyObject. And if it is removed, the DelayBindingController is removed from the collection as well.

public class DelayBindingControllerCollection :
    FreezableCollection<DelayBindingController { }

public class DelayBindingManager
{
    public static DependencyProperty DelayBindingControllersProperty =
        DependencyProperty.RegisterAttached(
            "DelayBindingControllers",
            typeof(DelayBindingControllerCollection),
            typeof(DelayBindingManager),
            new FrameworkPropertyMetadata(null));
    // ...
}

Delay Bindings in Code behind

At last, an option to add or remove DelayBindings through code behind was added in a static class called DelayBindingOperations.

public static class DelayBindingOperations
{
    public static void ClearBinding(DependencyObject targetObject,
                                    DependencyProperty targetProperty) // ...

    public static void SetBinding(DependencyObject targetObject,
                                  DependencyProperty targetProperty,
                                  BindingBaseExtensionBase delayBinding) // ...
}

A small class diagram for how this fits together can be viewed here
 
The DelayBindingController

Finally, a word on DelayBindingController. It has, like I said earlier, two dependency properties. SourcePropertyMirror and TargetPropertyMirror. TargetPropertyMirror binds TwoWay the target property and SourcePropertyMirror binds TwoWay to whatever was specified in the Binding. When the source value is updated a timer is started and when the time runs out the value is written to the target mirror property. The target property works the same way. It looks like this


public DelayBindingController(DependencyObject targetObject,
                              DependencyProperty targetProperty,
                              TimeSpan updateSourceDelay,
                              TimeSpan updateTargetDelay,
                              BindingBase binding,
                              BindingMode mode)
{
    TargetObject = targetObject;
    TargetProperty = targetProperty;
    if (mode == BindingMode.TwoWay || mode == BindingMode.OneWayToSource)
    {
        m_delaySource = true;
        m_updateSourceTimer = new DispatcherTimer();
        // ...
    }
    if (mode != BindingMode.OneWayToSource)
    {
        m_delayTarget = true;
        m_updateTargetTimer = new DispatcherTimer();
        // ...
    }

    Binding = binding;
}

public DependencyObject TargetObject { get; private set; }
public DependencyProperty TargetProperty { get; private set; }
public BindingBase Binding { get; private set; }

public void SetupBindingListeners()
{
    Binding targetBinding = new Binding()
    {
        Source = TargetObject,
        Path = new PropertyPath(TargetProperty),
        Mode = BindingMode.TwoWay
    };
    BindingOperations.SetBinding(this, TargetPropertyMirrorProperty, targetBinding);
    BindingOperations.SetBinding(this, SourcePropertyMirrorProperty, Binding);
}

private void SourcePropertyValueChanged()
{
    if (m_delayTarget == true)
    {
        m_updateSourceTimer.Stop();
        m_updateTargetTimer.Stop();
        m_updateTargetTimer.Start();
    }
}

private void TargetPropertyValueChanged()
{
    if (m_delaySource == true)
    {
        m_updateTargetTimer.Stop();
        m_updateSourceTimer.Stop();
        m_updateSourceTimer.Start();
    }
}

private void UpdateSourceTimer_Tick(object sender, EventArgs e)
{
    m_updateSourceTimer.Stop();
    object targetValue = GetValue(TargetPropertyMirrorProperty);
    this.SetValue(SourcePropertyMirrorProperty, targetValue);
}

private void UpdateTargetTimer_Tick(object sender, EventArgs e)
{
    m_updateTargetTimer.Stop();
    object sourceValue = GetValue(SourcePropertyMirrorProperty);
    this.SetValue(TargetPropertyMirrorProperty, sourceValue);
}

This became a pretty long post and I was probably to detailed about the implementation part. Anyway, there are many use cases for a Binding delay to the source. The use cases for a Binding delay to the target are fewer but it can be nice to have when needed so hopefully you’ll find this post useful. :D

And for those of you who missed the download link to the demo project at the top, it can be downloaded here

Enyoj! :)
/Fredrik


OneWayToSource Binding for ReadOnly Dependency Property

August 28, 2011

Have you ever tried to do something like this?

<TextBlock Name="myTextBlock"
           ActualWidth="{Binding Path=Width, Mode=OneWayToSource}"
           ActualHeight="{Binding Path=Height, Mode=OneWayToSource}"/>

If you did, you probably received the following error

error MC3065: ‘ActualWidth’ property is read-only and cannot be set from markup.

A Binding can’t be set on a ReadOnly Dependency Property. Fair enough. But why can’t it do a OneWayToSource Binding? This should only affect the Source and not the Target. Apperently, this is by design and the problem is reported here on connect:
OneWayToSource binding from a readonly dependency property

The Solution
Various solutions and workarounds to this have been posted across the Internet and here is my take on it.

The aim was to create an intuitive solution which

• Adds support for a “PushBinding” on any Dependency Property in the Target

• Works with DataContext, ElementName, RelativeSource and Source Bindings

• Can specify any number of “PushBindings” on any FrameworkElement

And here is the result

<TextBlock Name="myTextBlock">
    <pb:PushBindingManager.PushBindings>
        <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
        <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
    </pb:PushBindingManager.PushBindings>
</TextBlock>

The attached project contains source code and demo usage
Download Project Here

Implemention

PushBindings is an Attached Property of type PushBindingCollection which can be set on any FrameworkElement.

PushBinding won’t be in the Visual or Logical tree so it will need an Inheritance Context to be able to do DataContext, ElementName and RelativeSource Bindings. One way to get an Inheritance Context is to inherit from Freezable (More details of why this works can be found here and here). PushBindingCollection needs Inheritance Context as well so it derives from FreezableCollection.

PushBinding has two Dependency Properties

• Listener – Binds OneWay to the TargetProperty and calls SetValue on Mirror whenever it changes through the PropertyChangedCallback

• Mirror – Binds OneWayToSource to whatever is specified in the Binding (DataContext, RelativeSource etc.)

public class PushBinding : FreezableBinding
{
    #region Dependency Properties

    public static DependencyProperty TargetPropertyMirrorProperty = ...
    public static DependencyProperty TargetPropertyListenerProperty = ...

    public string TargetProperty{get; set;}

    public void SetupTargetBinding(FrameworkElement targetObject)
    {
        Binding listenerBinding = new Binding
        {
            Source = targetObject,
            Path = new PropertyPath(TargetProperty),
            Mode = BindingMode.OneWay
        };
        BindingOperations.SetBinding(this,
                                     TargetPropertyListenerProperty,
                                     listenerBinding);

        BindingOperations.SetBinding(this,
                                     TargetPropertyMirrorProperty,
                                     Binding);
    }

    private void TargetPropertyValueChanged()
    {
        object targetPropertyValue = GetValue(TargetPropertyListenerProperty);
        this.SetValue(TargetPropertyMirrorProperty, targetPropertyValue);
    }
}

At first, I used Reflection to get the specified DependencyProperty and used it to subscribe to ValueChanged of DependencyPropertyDescriptor. The problem with this was that it leaked memory and I had a hard time to find out when it was time to call RemoveValueChanged. Instead I introduced the Listener Dependency Property and now it’s leak-free.

Update: Using PushBinding in a Style

Initially, there were some problem when trying to use PushBindings in a Style.

• The workaround used to be able to add items directly to the attached property PushBindings in Xaml (without having to create a new instance of PushBindingsCollection) doesn’t work in a Style. Instead, we just get an exception.

• A FreezableCollection created in Xaml is Freezed. This means that the PushBindings in the collection becomes Freezed as well and a Freezed object can’t be edited. So when trying to setup the Bindings in PushBinding we’ll get another exception.

• Unlike a regular Binding, a PushBinding can only be used for one object. This means that each PushBinding in a Style have to be cloned before it can be used.

To overcome these issues, I created another attached property in PushBindingManager called StylePushBindings and implemented the overridable methods CreateInstanceCore and CloneCore in PushBinding and its base-class FreezableBinding. And when a PushBinding is added to StylePushBindings, it is cloned and added to the PushBindings collection.

public static void StylePushBindingsChanged(DependencyObject target,
                                        DependencyPropertyChangedEventArgs e)
{
    FrameworkElement targetObject = target as FrameworkElement;
    if (targetObject != null)
    {
        PushBindingCollection stylePushBindings =
            e.NewValue as PushBindingCollection;
        PushBindingCollection pushBindingCollection =
            GetPushBindings(targetObject);
        foreach (PushBinding pushBinding in stylePushBindings)
        {
            PushBinding pushBindingClone = pushBinding.Clone() as PushBinding;
            pushBindingCollection.Add(pushBindingClone);
        }
    }
}

So when using a PushBinding in a Style, use StylePushBindings, create a new PushBindingCollection and add the PushBindings like this

<TextBlock ...>
  <TextBlock.Style>
    <Style TargetType="TextBlock">
      <Setter Property="pb:PushBindingManager.StylePushBindings">
        <Setter.Value>
          <pb:PushBindingCollection>
            <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
            <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
          </pb:PushBindingCollection>
        </Setter.Value>
      </Setter>
    </Style>
  </TextBlock.Style>
</TextBlock>

I added a new demo project with sample usage for PushBindings in both Styles and DataTemplates.
It can be downloaded here

Enjoy! :)

One last note, since .NET 4.0 we are even further away from a solution to this, since a OneWayToSource Binding reads the value it set back from the Source after it has updated it so I doubt we will ever see built-in-support for this.


Follow

Get every new post delivered to your Inbox.