3
votes

I have a WPF custom Control, which contains a ComboBox. I'd like to bind the ItemsSource of this ComboBox to a Dependency Property of the custom control class via a CollectionViewSource, but I couldn't figure out how to make the CollectionViewSource recognizing the correct DataContext (my custom control properties, in this case).

I googled a lot, read almost all SO custom control\CollectionViewSource\Collection binding\dependency properties binding\etc. questions and tried the solutions of some similar questions like this one and this and some more and it's still not working. I probably miss something, but I don't know what.

Here are the related parts from the Custom Control class:

public class CustomComboBox : Control
{
   static CustomComboBox()
   {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomComboBox), new FrameworkPropertyMetadata(typeof(CustomComboBox)));
   }

   public CustomComboBox()
   {
      CustomItems = new ObservableCollection<ComboBoxItem>();
      DataContext = this;
   }

   internal ObservableCollection<ComboBoxItem> CustomItems 
   {
      get { return (ObservableCollection<ComboBoxItem>)GetValue(CustomItemsProperty); }
      set { SetValue(CustomItemsProperty, value); }
   }

   // Using a DependencyProperty as the backing store for CustomItems.  This enables animation, styling, binding, etc...
   public static readonly DependencyProperty CustomItemsProperty =
            DependencyProperty.Register("CustomItems", typeof(ObservableCollection<ComboBoxItem>), typeof(CustomComboBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

   private string text;
   public string Text
   {
       get { return text; }
       set
       {
           text = value;
           OnPropertyChanged("Text");
       }
   }

  // More properties, events and functions...

}

and the relevant parts of the xaml code:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MyNamespace"
                    xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
                    x:Class="CustomComboBox">

<CollectionViewSource x:Key="GroupedData"
                      Source="{Binding Path=CustomItems, RelativeSource={RelativeSource FindAncestor, AncestorType=local:CustomComboBox}, diag:PresentationTraceSources.TraceLevel=High}">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="GroupName" />
        </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

<!-- ...Some more Styles and DataTemplates... -->

<Style TargetType="{x:Type local:CustomComboBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomComboBox}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <ComboBox IsEditable="True"
                                  Text="{Binding Text}"
                                  ItemsSource="{Binding Source={StaticResource GroupedData}}">
                                  <!-- ...Some more properties... --> 
                        </ComboBox>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

I get this output from System.Diagnostics:

System.Windows.Data Warning: 58 : Path:'CustomItems'

System.Windows.Data Warning: 60 : BindingExpression (hash=28932383): Default mode resolved to OneWay

System.Windows.Data Warning: 61 : BindingExpression (hash=28932383): Default update trigger resolved to PropertyChanged

System.Windows.Data Warning: 62 : BindingExpression (hash=28932383): Attach to System.Windows.Data.CollectionViewSource.Source (hash=23914501)

System.Windows.Data Warning: 66 : BindingExpression (hash=28932383): RelativeSource (FindAncestor) requires tree context

System.Windows.Data Warning: 65 : BindingExpression (hash=28932383): Resolve source deferred

System.Windows.Data Warning: 67 : BindingExpression (hash=28932383): Resolving source

System.Windows.Data Warning: 70 : BindingExpression (hash=28932383): Found data context element: (OK)

System.Windows.Data Warning: 67 : BindingExpression (hash=28932383): Resolving source

System.Windows.Data Warning: 70 : BindingExpression (hash=28932383): Found data context element: (OK)

System.Windows.Data Warning: 67 : BindingExpression (hash=28932383): Resolving source (last chance)

System.Windows.Data Warning: 70 : BindingExpression (hash=28932383): Found data context element: (OK)

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='MyNamespace.CustomComboBox', AncestorLevel='1''. BindingExpression:Path=CustomItems; DataItem=null; target element is 'CollectionViewSource' (HashCode=23914501); target property is 'Source' (type 'Object')

I tried first this binding:

<CollectionViewSource x:Key="GroupedData"
                          Source="{Binding CustomItems}">

but then I get the error:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=CustomItems; DataItem=null; target element is 'CollectionViewSource' (HashCode=37908782); target property is 'Source' (type 'Object')

BTW- binding inside the ComboBox to another properties works- like the Text binding.

3

3 Answers

5
votes

Although @KyloRen's answer is generally correct (and the Inheritance Context is particularly useful), I'd like to offer alternative solution. The key point is that the CollectionViewSource in question should be defined as a resource of a FrameworkElement being a part of the template's visual tree (a good choice in my opinion is the root element). Here's the template:

<ControlTemplate TargetType="{x:Type local:CustomComboBox}">
    <Border (...)>
        <Border.Resources>
            <CollectionViewSource x:Key="GroupedData"
                                  Source="{Binding CustomItems, RelativeSource={RelativeSource TemplatedParent}}">
                (...)
            </CollectionViewSource>
        </Border.Resources>
        <ComboBox IsEditable="True"
                  Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}}"
                  ItemsSource="{Binding Source={StaticResource GroupedData}}">
            (...)
        </ComboBox>
    </Border>
</ControlTemplate>

Here are some advantages of this solution:

  • It's completely contained within the template, and therefore:
    • changing the template won't leave any unnecessary resources and/or have any impact on the new template
    • it's totally reusable "out-of-the-box", i.e. it doesn't require any additional setup (such as setting properties or adding resources)
  • It's DataContext-independent, which lets you use it in any data context without breaking the functionality of the control

Note though that for the latter to be true you should modify the bindings within the template to use RelativeSource={RelativeSource TemplatedParent} (or use TemplateBinding where applicable). Also, I'd recommend removing the DataContext = this; line from the constructor, because it would prevent data context inheritance.

2
votes

I see a couple of problems in your code and has why have you designed this way? But I'll skip all that part(you must have some reason to do so like setting the DataContext, initialization of CustomItems etc.) and just edit the XAML to make it work. To make your Binding work you need to understand the concept of Inheritance Context in WPF. Google it you'll find plenty of stuff about it. According to that I've changed your code as below:

<local:CustomComboBox>
    <local:CustomComboBox.Resources>
        <CollectionViewSource x:Key="GroupedData"
                  Source="{Binding Path=CustomItems}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="GroupName" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </local:CustomComboBox.Resources>
    <local:CustomComboBox.Style>
        <Style TargetType="{x:Type local:CustomComboBox}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:CustomComboBox}">
                        <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                            <ComboBox IsEditable="True"
                              Text="{Binding Text}"
                              ItemsSource="{Binding Source={StaticResource GroupedData}}">                                    
                            </ComboBox>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </local:CustomComboBox.Style>
</local:CustomComboBox>

And it works. Above is not perfect but I just made minimal changes(change structure & binding) to make above code work. Also added DataSource as:

 public CustomComboBox()
    {
        CustomItems = new ObservableCollection<ComboBoxItem>();
        CustomItems.Add(new ComboBoxItem() { Content = "4" });
        CustomItems.Add(new ComboBoxItem() { Content = "5" });
        DataContext = this;
    }

Output:

Custom Combo

1
votes

I created a solution here : https://github.com/orhtun/WPFCustomControlBinding

I kept it intentionally very simple, no View Models, GroupedData (as in your example) etc.. If I got your problem wrong, please comment and I will try to fix it :)

Usage is like this;

 <wpfCustomControlBinding:CustomControl Grid.Row="0" Grid.Column="0" CustomItems="{Binding DataStringList}"></wpfCustomControlBinding:CustomControl>

enter image description here