none
How to bind to DisplayMemberPath in a customercontrol that derived from ComboBox? RRS feed

  • Question

  • Hello, All:

    I need a customer control which derives from ComboBox.

    In its resource file I have the style:

      <Style TargetType="{x:Type local:MyComboBox}">
                <Setter Property="SnapsToDevicePixels" Value="True"></Setter>
                <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
                <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
                <Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
            <Setter Property="ItemTemplate">
                    <Setter.Value>
                        <HierarchicalDataTemplate>
                        <CheckBox Content="{Binding Path=DisplayMemberPath,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
                              />
                        </HierarchicalDataTemplate>
                    </Setter.Value>
                </Setter>

     

    I wanted the content of the checkbox to show the DisplayMemberPath, How can I bind to that property of the base combobox?

    I also tried

    Content="{TemplateBinding ContentPresenter.Content}" 

    This will show the type name of my data object.

     

    How should I do it?

    Thanks in advance

    -Rockdale

    Friday, August 20, 2010 4:03 PM

Answers

  • Hi Rockdale,

    So the problem you encounter is that Binding.Path is not a dependency property thus it cannot be bound to a DisplayText property.

    You can use MultiBinding to solve this problem. You can bind to both data and ComboBox.DisplayText property, and use a IMultiValueConverter to get value.

    Here is an example for a similar purpose. Hope it helps.

    MainWindow.xaml

    <Window x:Class="DynamicBindingPath.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="clr-namespace:DynamicBindingPath"
      Title="MainWindow" Height="350" Width="525">
     <Window.Resources>
      <local:PathConverter x:Key="DynamicPathConverter"/>
     </Window.Resources>
     <Grid>
      <StackPanel>
       <Border Name="MyUserControl" Tag="Num">
        <Grid>
         <StackPanel>
          <ComboBox ItemsSource="{Binding}">
           <ComboBox.ItemTemplate>
            <DataTemplate>
             <Label>
              <Label.Content>
               <MultiBinding Converter="{StaticResource DynamicPathConverter}">
                <Binding/>
                <Binding ElementName="MyUserControl" Path="Tag"/>
               </MultiBinding>
              </Label.Content>
             </Label>
            </DataTemplate>
           </ComboBox.ItemTemplate>
          </ComboBox>
         </StackPanel>
        </Grid>
       </Border>
       <Border Name="MyUserControl2" Tag="Num2">
        <Grid>
         <StackPanel>
          <ComboBox ItemsSource="{Binding}">
           <ComboBox.ItemTemplate>
            <DataTemplate>
             <Label>
              <Label.Content>
               <MultiBinding Converter="{StaticResource DynamicPathConverter}">
                <Binding/>
                <Binding ElementName="MyUserControl2" Path="Tag"/>
               </MultiBinding>
              </Label.Content>
             </Label>
            </DataTemplate>
           </ComboBox.ItemTemplate>
          </ComboBox>
         </StackPanel>
        </Grid>
       </Border>
      </StackPanel>
     </Grid>
    </Window>
    
    

    MainWindow.xaml.cs

     

    namespace DynamicBindingPath
    {
     public partial class MainWindow : Window
     {
      public MainWindow()
      {
       InitializeComponent();
       FigureRow data = new FigureRow() { new Figure(1), new Figure(2), new Figure(3) };
       this.DataContext = data;
      }
     }
    
     public class PathConverter : IMultiValueConverter
     {
      public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
      {
       Type dataType = values[0].GetType();
       string dataPath = values[1].ToString();
       Object data = dataType.InvokeMember(dataPath, BindingFlags.GetProperty, null, values[0], null);
    
       return data;
      }
    
      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
      {
       throw new NotImplementedException();
      }
     }
    
    
     public class Figure : INotifyPropertyChanged
     {
      protected void OnNotifyPropertyChanged(string p)
      {
       if (PropertyChanged != null)
       {
        PropertyChanged(this, new PropertyChangedEventArgs(p));
       }
      }
      public event PropertyChangedEventHandler PropertyChanged;
    
      int _Num;
      public int Num
      {
       get { return _Num; }
       set
       {
        if (_Num != value)
        {
         _Num = value;
         OnNotifyPropertyChanged("Num");
        }
       }
      }
      int _Num2;
      public int Num2
      {
       get { return _Num2; }
       set
       {
        if (_Num2 != value)
        {
         _Num2 = value;
         OnNotifyPropertyChanged("Num2");
        }
       }
      }
    
      public Figure() : this(0) { }
    
      public Figure(int num)
      {
       this.Num = num;
       this.Num2 = num * 2;
      }
     }
     public class FigureRow : ObservableCollection<Figure> { }
    }
     

    For more about MultiBinding, please refer to http://msdn.microsoft.com/en-us/library/system.windows.data.multibinding.aspx.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    Friday, August 27, 2010 7:09 AM
    Moderator

All replies

  • I have read the first link and I was able to create a user control using that approach.

     

    But I want a customer control (not a user control as the link did) which derived from combobox, apply the itemtemplate with check box in it.

    using myComboBox should just like the normal checkbox,e.g.

    <local:MyComboBox DisplayMemberPath="Name" DisplayValuePath="Id">

     

    then, in my derived combobox, I should be able to bind to  these to properties (DisplayMemberPath and DisplayValuePath). I was thinking using Dependency Properties for this purpose but thought why couldn't I use the property from base class?

    Most example I found is that

    create a new custom control that inherits from Combobox and alter the control's template to replace the control that sits in the popup with a list including the checkboxes.  Looks like nobody doing the customer control by change the itemtemplate only.

     

    Again, Thanks

    Rockdale

     

     

     

     

     

     

    Friday, August 20, 2010 7:28 PM
  • Hi Rockdale,

    DisplayMemberPath and ItemTemplate properties are exclusive. You can only set one of them. When you set DisplayMemberPath property, a default DataTemplate will be created and replace ItemTemplate.

    So I think you need to create another Dependency Property or re-use an existing one such as Tag property to allow user to filter members. Then you can use MultiBinding in a DataTemplate to bind to both data and that DP you created. And in an IMultiValueConverter you can fetch corresponding data related to the value of that dependency property.

    However, so far all these jobs can be done with in a style (if you use Tag property), and a custom control is not really neccesary needed.

    To create a custom control you need to define a control template since WPF controls will not inherit appearance from their base class.

    For more about control authoring, please refer to http://msdn.microsoft.com/en-us/library/ms745025.aspx.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    • Marked as answer by rockdale Thursday, August 26, 2010 12:49 PM
    • Unmarked as answer by rockdale Thursday, August 26, 2010 12:51 PM
    Thursday, August 26, 2010 1:54 AM
    Moderator
  • Hello, Min Zhu: 

    Thanks for the reply. It helped a lot.

    What I wanted to do is I want my Customer Control can be used as

    <mycontrollib: MyComboBox

                          DisplayText = {Binding stringfiledofmydataobject}

                          IsChecked = {Binding booleanfiledofmydataobject} />

     

     

    Suppose I defined the DisplayText as DependencyProperty in my Customer Control, How should I define the style to allow me doing the binding ?

     

      <Style TargetType="{x:Type local:MyComboBox}">
                <Setter Property="SnapsToDevicePixels" Value="True"></Setter>
                <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
                <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
                <Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
            <Setter Property="ItemTemplate">
                    <Setter.Value>
                        <HierarchicalDataTemplate>

                        <CheckBox Content="{Binding ???}" IsChecked ="{Binding ???}"/>

                        </HierarchicalDataTemplate>
                    </Setter.Value>
                </Setter>

     

     

    I can archieve add checkbox into combobox by doing

     <ComboBox
            x:Name="cboCore"
            OverridesDefaultStyle="True"
            ItemsSource="{Binding ElementName=me, Path=ItemsSource}"
            DataContext="{Binding ElementName=me, Path=DataContext}"
            SelectedValuePath="Value"
            IsEditable="True"
            >
            <ComboBox.ItemTemplate>
                <HierarchicalDataTemplate>
                    <CheckBox Content="{Binding Path=DisplayName}"
                              IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
                              Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"
                              Click="CheckBox_Click"
                              />
                </HierarchicalDataTemplate>
            </ComboBox.ItemTemplate>

    See how I hard code the DisplayName and IsSelected binding for the checkbox, but this requires that my data object must have fields called DisplayName and IsSelected. I am using this checkbox combobox in many places,  and I have to create this particular data object (with DisplayName and IsSelected) from my business object everywhere, this got me thinking that I should be able to just binding the content and ischeced properties of the checkbox to my business object?

     

    How can I archieve that ? (using Customer or user control?)

    Thanks a lot

    -Rockdale

     

     

     

     

     

     

    Thursday, August 26, 2010 1:10 PM
  • Hi Rockdale,

    So the problem you encounter is that Binding.Path is not a dependency property thus it cannot be bound to a DisplayText property.

    You can use MultiBinding to solve this problem. You can bind to both data and ComboBox.DisplayText property, and use a IMultiValueConverter to get value.

    Here is an example for a similar purpose. Hope it helps.

    MainWindow.xaml

    <Window x:Class="DynamicBindingPath.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="clr-namespace:DynamicBindingPath"
      Title="MainWindow" Height="350" Width="525">
     <Window.Resources>
      <local:PathConverter x:Key="DynamicPathConverter"/>
     </Window.Resources>
     <Grid>
      <StackPanel>
       <Border Name="MyUserControl" Tag="Num">
        <Grid>
         <StackPanel>
          <ComboBox ItemsSource="{Binding}">
           <ComboBox.ItemTemplate>
            <DataTemplate>
             <Label>
              <Label.Content>
               <MultiBinding Converter="{StaticResource DynamicPathConverter}">
                <Binding/>
                <Binding ElementName="MyUserControl" Path="Tag"/>
               </MultiBinding>
              </Label.Content>
             </Label>
            </DataTemplate>
           </ComboBox.ItemTemplate>
          </ComboBox>
         </StackPanel>
        </Grid>
       </Border>
       <Border Name="MyUserControl2" Tag="Num2">
        <Grid>
         <StackPanel>
          <ComboBox ItemsSource="{Binding}">
           <ComboBox.ItemTemplate>
            <DataTemplate>
             <Label>
              <Label.Content>
               <MultiBinding Converter="{StaticResource DynamicPathConverter}">
                <Binding/>
                <Binding ElementName="MyUserControl2" Path="Tag"/>
               </MultiBinding>
              </Label.Content>
             </Label>
            </DataTemplate>
           </ComboBox.ItemTemplate>
          </ComboBox>
         </StackPanel>
        </Grid>
       </Border>
      </StackPanel>
     </Grid>
    </Window>
    
    

    MainWindow.xaml.cs

     

    namespace DynamicBindingPath
    {
     public partial class MainWindow : Window
     {
      public MainWindow()
      {
       InitializeComponent();
       FigureRow data = new FigureRow() { new Figure(1), new Figure(2), new Figure(3) };
       this.DataContext = data;
      }
     }
    
     public class PathConverter : IMultiValueConverter
     {
      public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
      {
       Type dataType = values[0].GetType();
       string dataPath = values[1].ToString();
       Object data = dataType.InvokeMember(dataPath, BindingFlags.GetProperty, null, values[0], null);
    
       return data;
      }
    
      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
      {
       throw new NotImplementedException();
      }
     }
    
    
     public class Figure : INotifyPropertyChanged
     {
      protected void OnNotifyPropertyChanged(string p)
      {
       if (PropertyChanged != null)
       {
        PropertyChanged(this, new PropertyChangedEventArgs(p));
       }
      }
      public event PropertyChangedEventHandler PropertyChanged;
    
      int _Num;
      public int Num
      {
       get { return _Num; }
       set
       {
        if (_Num != value)
        {
         _Num = value;
         OnNotifyPropertyChanged("Num");
        }
       }
      }
      int _Num2;
      public int Num2
      {
       get { return _Num2; }
       set
       {
        if (_Num2 != value)
        {
         _Num2 = value;
         OnNotifyPropertyChanged("Num2");
        }
       }
      }
    
      public Figure() : this(0) { }
    
      public Figure(int num)
      {
       this.Num = num;
       this.Num2 = num * 2;
      }
     }
     public class FigureRow : ObservableCollection<Figure> { }
    }
     

    For more about MultiBinding, please refer to http://msdn.microsoft.com/en-us/library/system.windows.data.multibinding.aspx.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    Friday, August 27, 2010 7:09 AM
    Moderator
  • Hello, Min:

     

    Thanks for throwing an example but that's not what I was asking. I am sorry if I misleading you in my previous posts.

    To make my question more clear, I will post my source code and hope you can see what I am trying to achieve.

    I have a customer control derives from ComboBox, following is the default xaml for the style and the cs file.

    XAML for my ComboBoxEx

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:local="clr-namespace:UIControls"
                        >


            <ControlTemplate x:Key="Button_ToggleComboBoxTemplate" TargetType="ToggleButton">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition Width="20" />
                    </Grid.ColumnDefinitions>
                    <Border
                      x:Name="Border"
                      Grid.ColumnSpan="2"
                      CornerRadius="2"
                      Background="White"
                      BorderBrush="Black"
                      BorderThickness="1" />
                    <Border
                      Grid.Column="0"
                      CornerRadius="2,0,0,2"
                      Margin="1"
                      Background="White"
                      BorderBrush="Black"
                      BorderThickness="0,0,1,0" />
                    <Path
                      x:Name="Arrow"
                      Grid.Column="1"    
                      Fill="Black"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center"
                      Data="M 0 0 L 4 4 L 8 0 Z"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="ToggleButton.IsMouseOver" Value="true">
                        <Setter TargetName="Border" Property="Background" Value="Gray" />
                    </Trigger>
                    <Trigger Property="ToggleButton.IsChecked" Value="true">
                        <Setter TargetName="Border" Property="Background" Value="Gray" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>



            <Style x:Key="{x:Type ComboBoxItem}" TargetType="ComboBoxItem">
                <Setter Property="SnapsToDevicePixels" Value="true"/>
                <Setter Property="OverridesDefaultStyle" Value="true"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ComboBoxItem">
                            <Border
                              Name="Border"
                              Padding="2"
                              SnapsToDevicePixels="true">
                                <ContentPresenter />
                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsHighlighted" Value="true">
                                    <Setter TargetName="Border" Property="Background" Value="Yellow"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>


            <Style TargetType="{x:Type local:ComboBoxEx}">
                <Setter Property="SnapsToDevicePixels" Value="True"></Setter>
                <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
                <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
                <Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
            <Setter Property="ItemTemplate">
                    <Setter.Value>
                        <HierarchicalDataTemplate>
                       
                            <CheckBox Content="{Binding Path=DisplayText}"
                              IsChecked="{Binding Path=IsSelected}"
                              Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"
                              />
                        </HierarchicalDataTemplate>
                    </Setter.Value>
                   
                </Setter>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ComboBox">
                            <Grid>
                                <ToggleButton
                            Name="ToggleButton"
                            Template="{StaticResource Button_ToggleComboBoxTemplate}"
                            Grid.Column="2"
                            Focusable="false"
                            IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
                            ClickMode="Press">
                                </ToggleButton>

                                <ContentPresenter
                            x:Name="Presenter"
                            IsHitTestVisible="False"
                            Margin="3,3,23,3"
                            VerticalAlignment="Center"
                            HorizontalAlignment="Left">
                                    <ContentPresenter.Content>
                                        <TextBlock TextTrimming="CharacterEllipsis"
                                           Text="{Binding Path=Text,Mode=TwoWay,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
                                    </ContentPresenter.Content>
                                </ContentPresenter>
                                <Popup
                            Name="Popup"
                            Placement="Bottom"
                            IsOpen="{TemplateBinding IsDropDownOpen}"
                            AllowsTransparency="True"
                            Focusable="False"
                            PopupAnimation="Slide">
                                    <Grid
                                      Name="DropDown"
                                      SnapsToDevicePixels="True"               
                                      MinWidth="{TemplateBinding ActualWidth}"
                                      MaxHeight="{TemplateBinding MaxDropDownHeight}">
                                        <Border
                                        x:Name="DropDownBorder"
                                        Background="White"
                                        BorderThickness="1"
                                        BorderBrush="Black"/>
                                        <ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True" DataContext="{Binding}">
                                            <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
                                        </ScrollViewer>
                                    </Grid>
                                </Popup>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger Property="HasItems" Value="false">
                                    <Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
                                </Trigger>
                                <Trigger Property="IsGrouping" Value="true">
                                    <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                                </Trigger>
                                <Trigger SourceName="Popup" Property="Popup.AllowsTransparency" Value="true">
                                    <Setter TargetName="DropDownBorder" Property="CornerRadius" Value="4"/>
                                    <Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                   
                </Setter>
            </Style>
    </ResourceDictionary>

     

    CS file for ComboBoxEx

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;

    namespace UIControls
    {
        public class ComboBoxEx : ComboBox
        {
            static ComboBoxEx()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(ComboBoxEx), new FrameworkPropertyMetadata(typeof(ComboBoxEx)));

            }

        }
    }

     

    THIS IS THE BACKEND DATA CLASS:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;

    namespace UIControls
    {
        public class ComboBoxExData : INotifyPropertyChanged
        {

           
            private string _displayText;
            protected bool _isSelected = false;
            public virtual bool IsSelected
            {
                get { return _isSelected; }
                set
                {
                    _isSelected = value;
                    this.OnPropertyChanged("IsSelected");
                }
            }

            public string DisplayText
            {
                get { return _displayText; }
                set { _displayText = value; }
            }

            public ComboBoxExData(string disp)
            {
                _displayText = disp;
            }
            public ComboBoxExData(string disp, bool isSel)
            {
                _displayText = disp;
                _isSelected = isSel;
            }

            #region INotifyPropertyChanged Members

            public event PropertyChangedEventHandler PropertyChanged;

            protected void OnPropertyChanged(string prop)
            {
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
            }

            #endregion

        }
    }

    TO USE ComboBoxEx, In a test window:

    <myctrllib:ComboBoxEx  x:Name="cboTest"/>

     

    in TestWin.CS, set the ItemsSource

     

          public TestWin()
            {
                InitializeComponent();

                LoadData();
            }

            private void LoadData()
            {
                List<ComboBoxExData> list = new List<ComboBoxExData>()
                {
                new ComboBoxExData("Item One"),
                new ComboBoxExData("Item Two"),
                new ComboBoxExData("Item Three"),
                new ComboBoxExData("Item Four"),
                new ComboBoxExData("Item Five"),
                new ComboBoxExData("Item Six"),
                new ComboBoxExData("Item Seven"),
                new ComboBoxExData("Item Eight")
                };

                cboTest.ItemsSource = list;
            }

    //////////////////////////

    Everything works fine so far.

     

    Check the XAML, you will see the ItemDataTemplate for the combobox:

                        <HierarchicalDataTemplate>
                       
                            <CheckBox Content="{Binding Path=DisplayText}"
                              IsChecked="{Binding Path=IsSelected}"
                              Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"
                              />
                        </HierarchicalDataTemplate>

     

    I hardcoded tell the checkbox to binding to DisplayText and IsSelected fields of my ComboBoxExData object,This causes that I have to convert all my business object to ComboBoxExData if I want to use it as datasource for my ComboBoxEx.

    For example, if I have another data class called Folder with FolderName and HasSubFolder fields. Now I have to create the ComboBoxExData from the Folder class then set the ComboBoxEx ItemsSource to a list of the newly created ComboBoxExData.

    1. I want to define a DependencyProperties in the ComboBoxEx so that when other programmers use this control,they can just do (For example):

     

    <myctrllib:ComboBoxEx 

    DisplayText="{Binding FolderName}"

    IsSelected ="{Binding HasSubFolder}"

    x:Name="cboTest"/>

     

    Can it be done?

     

    2. In ComboBoxEx class, I want to get the checkbox element then add the click event to the checkbox  so that I can raise my customer event when user check/uncheck the checkbox, but I never succeed. (This is a problem that trouble me for a long time, most time I just end up using a user control and define the click event in the xaml of the user control).

     

    Thanks for read my long post and hope you can help me on these two questions.

    Thanks

    -Rockdale

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    Friday, August 27, 2010 8:45 PM
  • Hi Rockdale,

    Sorry for the confusion. I should explain my sample more. You first question could be resolved if the following binding is allowed:

      <CheckBox Content="{Binding Path={Binding Path=DisplayText,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}}"
           IsChecked="{Binding Path={Binding Path=IsSelected,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}}"
           Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"
           />
    

    However, this binding is illegal because Binding.Path cannot be bound. So a workaround to this is to use MultiBinding to bind to ComboBox.DisplayText property.

    That sample in my last post tells how to use MultiBinding to achieve this. You could adapt it to your need.

    The ItemTemplate should looks like the following:

         <Setter Property="ItemTemplate">
          <Setter.Value>
           <HierarchicalDataTemplate>
            <CheckBox Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}">
             <CheckBox.Content>
              <MultiBinding Converter="DisplayConverter">
               <Binding/>
               <Binding Path="DisplayText" RelativeSource="RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}"/>
              </MultiBinding>
             </CheckBox.Content>
             <CheckBox.IsChecked>
              <MultiBinding Converter="IsCheckedConverter">
               <Binding/>
               <Binding Path="IsChecked" RelativeSource="RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}"/>
              </MultiBinding>
             </CheckBox.IsChecked>
            </CheckBox>
           </HierarchicalDataTemplate>
          </Setter.Value>
         </Setter>
    

    And you need to implement these two converters for these MultiBindings to work. The converters should be very simlilar to the one in my last post.

    After that, you should be able to use this it like the following:

    <myctrllib:ComboBoxEx 
    
    DisplayText="FolderName"
    
    IsSelected ="HasSubFolder"
    
    x:Name="cboTest"/>
    

    Hope this helps.

     

    And since your second question is not directly related to the original issue, it would be best if you open up a new thread for the new question. In this way, our discussion here will not deviate too much from the original issue. This will make answer searching in the forum easier and be beneficial to other community members as well.

    Thank you for your understanding.

    Best regards,

    Min

     


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    • Marked as answer by Min ZhuModerator Thursday, September 2, 2010 1:59 AM
    • Unmarked as answer by rockdale Tuesday, September 14, 2010 12:42 PM
    Monday, August 30, 2010 3:26 AM
    Moderator
  • Hello, Min:

     

    Sorry for get back to this topic so late. I got redirect to something else last couple weeks. I have put together a test using multibinding approach you provided, but did not get anywhere, could you please take a look to see what I did wrong? Thanks a lot.

    ComboBoxEx xaml

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:local="clr-namespace:WpfApplication1"
                        >


        <local:BindingPathConverter x:Key="DynamicPathConverter"/>

        <ControlTemplate x:Key="Button_ToggleComboBoxTemplate" TargetType="ToggleButton">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition Width="20" />
                </Grid.ColumnDefinitions>
                <Border
                      x:Name="Border"
                      Grid.ColumnSpan="2"
                      CornerRadius="2"
                      Background="White"
                      BorderBrush="Black"
                      BorderThickness="1" />
                <Border
                      Grid.Column="0"
                      CornerRadius="2,0,0,2"
                      Margin="1"
                      Background="White"
                      BorderBrush="Black"
                      BorderThickness="0,0,1,0" />
                <Path
                      x:Name="Arrow"
                      Grid.Column="1"    
                      Fill="Black"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center"
                      Data="M 0 0 L 4 4 L 8 0 Z"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="ToggleButton.IsMouseOver" Value="true">
                    <Setter TargetName="Border" Property="Background" Value="Gray" />
                </Trigger>
                <Trigger Property="ToggleButton.IsChecked" Value="true">
                    <Setter TargetName="Border" Property="Background" Value="Gray" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>



        <Style x:Key="{x:Type ComboBoxItem}" TargetType="ComboBoxItem">
            <Setter Property="SnapsToDevicePixels" Value="true"/>
            <Setter Property="OverridesDefaultStyle" Value="true"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ComboBoxItem">
                        <Border
                              Name="Border"
                              Padding="2"
                              SnapsToDevicePixels="true">
                            <ContentPresenter />
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsHighlighted" Value="true">
                                <Setter TargetName="Border" Property="Background" Value="Yellow"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>


        <Style TargetType="{x:Type local:CheckableComboBoxEx}">
            <Setter Property="SnapsToDevicePixels" Value="True"></Setter>
            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
            <Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
            <Setter Property="ItemTemplate">
                <Setter.Value>
                    <HierarchicalDataTemplate>
                        <CheckBox Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}">
                            <CheckBox.Content>
                                <MultiBinding Converter="{StaticResource DynamicPathConverter}">
                                    <Binding/>
                                    <Binding Path="DisplayTextPath" RelativeSource="RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}"/>
                                </MultiBinding>
                            </CheckBox.Content>
                            <CheckBox.IsChecked>
                                <MultiBinding Converter="{StaticResource DynamicPathConverter}">
                                    <Binding/>
                                    <Binding Path="IsSelectedPath" RelativeSource="RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}"/>
                                </MultiBinding>
                            </CheckBox.IsChecked>
                        </CheckBox>
                    </HierarchicalDataTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ComboBox">
                        <Grid>
                            <ToggleButton
                            Name="ToggleButton"
                            Template="{StaticResource Button_ToggleComboBoxTemplate}"
                            Grid.Column="2"
                            Focusable="false"
                            IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
                            ClickMode="Press">
                            </ToggleButton>

                            <ContentPresenter
                            x:Name="Presenter"
                            IsHitTestVisible="False"
                            Margin="3,3,23,3"
                            VerticalAlignment="Center"
                            HorizontalAlignment="Left">
                                <ContentPresenter.Content>
                                    <TextBlock TextTrimming="CharacterEllipsis"
                                           Text="{Binding Path=Text,Mode=TwoWay,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
                                </ContentPresenter.Content>
                            </ContentPresenter>
                            <Popup
                            Name="Popup"
                            Placement="Bottom"
                            IsOpen="{TemplateBinding IsDropDownOpen}"
                            AllowsTransparency="True"
                            Focusable="False"
                            PopupAnimation="Slide">
                                <Grid
                                      Name="DropDown"
                                      SnapsToDevicePixels="True"               
                                      MinWidth="{TemplateBinding ActualWidth}"
                                      MaxHeight="{TemplateBinding MaxDropDownHeight}">
                                    <Border
                                        x:Name="DropDownBorder"
                                        Background="White"
                                        BorderThickness="1"
                                        BorderBrush="Black"/>
                                    <ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True" DataContext="{Binding}">
                                        <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
                                    </ScrollViewer>
                                </Grid>
                            </Popup>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasItems" Value="false">
                                <Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
                            </Trigger>
                            <Trigger Property="IsGrouping" Value="true">
                                <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                            </Trigger>
                            <Trigger SourceName="Popup" Property="Popup.AllowsTransparency" Value="true">
                                <Setter TargetName="DropDownBorder" Property="CornerRadius" Value="4"/>
                                <Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>

            </Setter>
        </Style>



    </ResourceDictionary>

    ComboBoxEx.cs

        public class CheckableComboBoxEx : ComboBox
        {
            static CheckableComboBoxEx()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckableComboBoxEx), new FrameworkPropertyMetadata(typeof(CheckableComboBoxEx)));

            }



            public static readonly DependencyProperty DisplayTextPathProperty =
               DependencyProperty.Register("DisplayTextPath", typeof(string), typeof(CheckableComboBoxEx));

            public string DisplayTextPath
            {

                get { return (string)GetValue(DisplayTextPathProperty); }
                set { SetValue(DisplayTextPathProperty, value); }
            }


            public static readonly DependencyProperty IsSelectedPathProperty =
               DependencyProperty.Register("IsSelectedPath", typeof(string), typeof(CheckableComboBoxEx));

            public string IsSelectedPath
            {

                get { return (string)GetValue(IsSelectedPathProperty); }
                set { SetValue(IsSelectedPathProperty, value); }
            }

            
        }




        public class BindingPathConverter : IMultiValueConverter
        {
            public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            {
                Type dataType = values[0].GetType();
                string dataPath = values[1].ToString();
                Object data = dataType.InvokeMember(dataPath, BindingFlags.GetProperty, null, values[0], null);

                return data;
            }

            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }

     

     

    ------------Test Window Xaml----------------

         <uictrl:CheckableComboBoxEx x:Name="cboTestEx" Margin="20"
                        DisplayTextPath="FolderName"
                        IsSelectedPath ="HasSubFolder"/>

     

    ------------Test Window.cs----------------

    public partial class TestWin : Window
        {
            public CheckableComboWin()
            {
                InitializeComponent();

                LoadDataEx();
            }

         
            private void LoadDataEx()
            {
                ObservableCollection<FolderData> list = new ObservableCollection<FolderData>()
                {
                new FolderData("Folder One", true),
                new FolderData("Folder Two", true),
                new FolderData("Folder Three", false),
                new FolderData("Folder Four", false),
                new FolderData("Folder Five", true),
                new FolderData("Folder Six", true),
                new FolderData("Folder Seven", false),
                new FolderData("Folder Eight", false)
                };
                cboTestEx.ItemsSource = list;
            }

            internal class FolderData:INotifyPropertyChanged
            {
                private string _foldername;
                private bool _hassubdolder;
                public string FolderName
                {
                    get { return _foldername;}
                    set { _foldername = value;
                        OnNotifyPropertyChanged("FolderName");
                    }
                }

                public bool HasSubFolder
                {
                        get { return _hassubdolder;}
                    set { _hassubdolder = value;
                        OnNotifyPropertyChanged("HasSubFolder");
                    }
                }

                public FolderData(string nm, bool sub)
                {
                    _foldername = nm;
                    _hassubdolder = sub;
                }

                protected void OnNotifyPropertyChanged(string p)
                {
                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs(p));
                    }
                }
                public event PropertyChangedEventHandler PropertyChanged;

            }

        }

     

    -----The error message -------------------

    'RelativeSource' type does not have a public TypeConverter class.  Error at Line 86 Position 65.

    but no disassembly is available.

     

    Again, Thanks a lot

     

     

    Tuesday, September 14, 2010 12:59 PM
  • It's may late, but thanks, it works like i need.
    Saturday, January 18, 2020 10:40 PM