How can I correctly bind a ComboBox in my custom Control to its dependency property in a WPF C# application?

Mesh Ka 345 Reputation points
2023-12-19T11:27:52.59+00:00

I have a ComboBox in my custom Control, and I want to bind its "Text" and "Converter" properties to their dependency properties correctly so that I can reuse the custom Control. In some instances, I want to use ComboBoxItems as the source. For example, I have two instances of my CustomControl, one for "StudentName" and the other for "Student's BloodGroup." For StudentName, I want to bind it to StudentName, and for the BloodGroup, I want to use <ComboBoxItem>. How can I achieve that? You can find a sample of my code on Github here:

https://github.com/MeshkaM/FloatingLabelComboBox

sample

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,783 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,995 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
814 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-12-22T08:55:04.9633333+00:00

    HI,
    if you want use converters in your approach you can try following demo. In this demo ComboBoxItems are marked (BackGround) if BloodGroup is not set.

    XAML:

    <Window x:Class="WpfApp128.Window128"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp128"
            mc:Ignorable="d"
            Title="Mesh Ka_231219" Height="200" Width="400">
      <Window.DataContext>
        <local:ViewModel/>
      </Window.DataContext>
      <Window.Resources>
        <Style TargetType="{x:Type local:FloatingLabelComboBox}">
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="{x:Type local:FloatingLabelComboBox}">
                <StackPanel FlowDirection="LeftToRight">
                  <Label Content="{TemplateBinding LabelText}"
                         Foreground="{TemplateBinding LabelForeground}"
                         FontSize="{TemplateBinding LabelFontSize}"
                         VerticalAlignment="Stretch"
                         Margin="-5 0 0 0" />
    
                  <ComboBox ItemsSource="{TemplateBinding ItemsSource}"
                            DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
                            SelectedItem="{Binding Path=SelectedItem, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                            SelectedValue="{Binding Path=SelectedValue, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Center"
                            Foreground="{TemplateBinding Foreground}">
                  </ComboBox>
                </StackPanel>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
        <local:BoolToColorConverter x:Key="BoolToColorConverter"/>
      </Window.Resources>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="auto"/>
          <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <local:FloatingLabelComboBox Grid.Column="0" 
                                     LabelText="Student"
                                     ItemsSource="{Binding StudentsView}"
                                     SelectedItem="{Binding Student}"
                                     DisplayMemberPath="StudentName"
                                     CBConverter="{StaticResource BoolToColorConverter}"
                                     LabelForeground="Black"
                                     Margin="10"/>
    
        <local:FloatingLabelComboBox Grid.Column="1" 
                                     LabelText="Blood Group"
                                     ItemsSource="{Binding BloodGroups}"
                                     SelectedValue="{Binding Student.BloodGroup}"
                                     LabelForeground="Black"
                                     Margin="10"/>
    
        <Grid Grid.Row="1" Grid.Column="0" Margin="10">
          <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
          </Grid.RowDefinitions>
          <Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right"
               Content="Student:"/>
          <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Student.StudentName}" Margin="4"/>
          <Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right"
               Content="Bloodgroup:"/>
          <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Student.BloodGroup}" Margin="4"/>
        </Grid>
      </Grid>
    </Window>
    

    Code:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Globalization;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Media;
    
    namespace WpfApp128
    {
    	public class ViewModel : INotifyPropertyChanged
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    			=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
    		// contructor: load StudentList 
    		public ViewModel()
    		{
    			for (int i = 1; i <= 10; i++)
    				_studentList.Add(new Student() { StudentId = i, StudentName = $"Student {i}" });
    			_cvsStudentList.Source= _studentList;
    		}
    
    		internal ObservableCollection<Student> _studentList { get; set; } = new ObservableCollection<Student>();
    		private CollectionViewSource _cvsStudentList = new CollectionViewSource();
    		public ICollectionView StudentsView { get => _cvsStudentList.View; }
    		public ObservableCollection<string> BloodGroups { get; set; } = new ObservableCollection<string>() { "A", "B", "AB", "0" };
    
    		private Student _student;
    		public Student Student
    		{
    			get => _student;
    			set
    			{
    				_student = value;
    				OnPropertyChanged();
    				StudentsView.Refresh();	
    			}
    		}
    
    	}
    	public class Student : INotifyPropertyChanged
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "") =>
    			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
    		public int StudentId { get; set; }
    
    		private string _studentName;
    		public string StudentName
    		{
    			get => _studentName;
    			set
    			{
    				_studentName = value;
    				OnPropertyChanged();
    			}
    		}
    
    		private string _bloodgroup;
    		public string BloodGroup
    		{
    			get => _bloodgroup;
    			set
    			{
    				_bloodgroup = value;
    				OnPropertyChanged();
    			}
    		}
    	}
    	public class FloatingLabelComboBox : Control, INotifyPropertyChanged
    	{
    		static FloatingLabelComboBox()
    		{
    			DefaultStyleKeyProperty.OverrideMetadata(typeof(FloatingLabelComboBox), new FrameworkPropertyMetadata(typeof(FloatingLabelComboBox)));
    		}
    		public FloatingLabelComboBox() => this.Loaded += FloatingLabelComboBox_Loaded;
    
    		private void FloatingLabelComboBox_Loaded(object sender, RoutedEventArgs e)
    		{
    			Style st = new Style(typeof(ComboBoxItem));
    			Binding b = new Binding() { Converter = CBConverter };
    			st.Setters.Add(new Setter(ComboBoxItem.BackgroundProperty, b));
    			if (CBConverter != null) this.Resources.Add(typeof(ComboBoxItem), st);
    		}
    
    		public static readonly DependencyProperty LabelTextProperty =
    			 DependencyProperty.Register("LabelText", typeof(string),
    				 typeof(FloatingLabelComboBox), new PropertyMetadata(string.Empty));
    		public string LabelText
    		{
    			get { return GetValue(LabelTextProperty).ToString(); }
    			set { SetValue(LabelTextProperty, value); }
    		}
    
    		public static readonly DependencyProperty LabelForegroundProperty =
    				DependencyProperty.Register("LabelForeground", typeof(Brush),
    					typeof(FloatingLabelComboBox), new PropertyMetadata(Brushes.AliceBlue));
    		public Brush LabelForeground
    		{
    			get { return (Brush)GetValue(LabelForegroundProperty); }
    			set { SetValue(LabelForegroundProperty, value); }
    		}
    
    		public static readonly DependencyProperty LabelFontSizeProperty =
    				DependencyProperty.Register("LabelFontSize", typeof(double),
    					typeof(FloatingLabelComboBox), new PropertyMetadata(10.0));
    		public double LabelFontSize
    		{
    			get { return (double)GetValue(LabelFontSizeProperty); }
    			set { SetValue(LabelFontSizeProperty, value); }
    		}
    
    		public static readonly DependencyProperty ItemsSourceProperty =
    				DependencyProperty.Register("ItemsSource", typeof(object),
    					typeof(FloatingLabelComboBox), new PropertyMetadata(null));
    		public object ItemsSource
    		{
    			get { return GetValue(ItemsSourceProperty); }
    			set { SetValue(ItemsSourceProperty, value); }
    		}
    
    		public static readonly DependencyProperty SelectedItemProperty =
    				DependencyProperty.Register("SelectedItem", typeof(object),
    					typeof(FloatingLabelComboBox),
    						new FrameworkPropertyMetadata(null,
    						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
    						OnSelectedItemChanged));
    		public object SelectedItem
    		{
    			get { return GetValue(SelectedItemProperty); }
    			set { SetValue(SelectedItemProperty, value); }
    		}
    		private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    		{
    			FloatingLabelComboBox cc = d as FloatingLabelComboBox;
    			cc.SelectedItem = e.NewValue;
    			if (cc != null) cc.OnPropertyChanged(nameof(SelectedItem));
    		}
    
    		public static readonly DependencyProperty DisplayMemberPathProperty =
    				DependencyProperty.Register("DisplayMemberPath", typeof(string),
    					typeof(FloatingLabelComboBox), new PropertyMetadata(null));
    		public string DisplayMemberPath
    		{
    			get { return GetValue(DisplayMemberPathProperty).ToString(); }
    			set { SetValue(DisplayMemberPathProperty, value); }
    		}
    
    		public static readonly DependencyProperty SelectedValueProperty =
    				DependencyProperty.RegisterAttached("SelectedValue", typeof(object),
    					typeof(FloatingLabelComboBox),
    						new FrameworkPropertyMetadata(null,
    						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
    						new PropertyChangedCallback(OnSelectedValueChanged)));
    		public object SelectedValue
    		{
    			get { return GetValue(SelectedValueProperty); }
    			set { SetValue(SelectedValueProperty, value); }
    		}
    		private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    		{
    			FloatingLabelComboBox cc = d as FloatingLabelComboBox;
    			cc.SelectedValue = e.NewValue;
    			if (cc != null) cc.OnPropertyChanged(nameof(SelectedValue));
    		}
    
    		public static readonly DependencyProperty CBConverterProperty =
    		DependencyProperty.Register("CBConverter", typeof(IValueConverter),
    			typeof(FloatingLabelComboBox), new PropertyMetadata(null));
    		public IValueConverter CBConverter
    		{
    			get { return (IValueConverter)GetValue(CBConverterProperty); }
    			set { SetValue(CBConverterProperty, value); }
    		}
    
    		public event PropertyChangedEventHandler PropertyChanged;
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    			=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    	}
    
    	public class BoolToColorConverter : IValueConverter
    	{
    		public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    		{
    			Student st = value as Student;
    			return (st != null && !string.IsNullOrEmpty(st.BloodGroup)) ? Brushes.LightGreen : Brushes.LightPink;
    		}
    		public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    		{
    			throw new NotImplementedException();
    		}
    	}
    }
    

    Result:

    x

    1 person found this answer helpful.
    0 comments No comments

4 additional answers

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-12-19T18:50:35.9633333+00:00

    Hi,
    you can try following demo.

    XAML:

    <Window x:Class="WpfApp126.Window126"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp126"
            mc:Ignorable="d"
            Title="Mesh Ka_231219" Height="200" Width="400">
      <Window.DataContext>
        <local:ViewModel/>
      </Window.DataContext>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="auto"/>
          <RowDefinition Height="auto"/>
          <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <ComboBox Grid.Column="0"
                  ItemsSource="{Binding StudentList}"
                  SelectedItem="{Binding Student}"
                  DisplayMemberPath="Name"
                  Margin="10"/>
        <ComboBox Grid.Column="1"
                  ItemsSource="{Binding BloodGroups}"
                  SelectedValue="{Binding Student.BloodGroup}"
                  Margin="10"/>
        <Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right"
               Content="Student:"/>
        <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Student.Name}" Margin="4"/>
        <Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right"
               Content="Bloodgroup:"/>
        <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Student.BloodGroup}" Margin="4"/>
      </Grid>
    </Window>
    

    Code:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    
    namespace WpfApp126
    {
    	/// <summary>
    	/// Interaction logic for Window126.xaml
    	/// </summary>
    	public partial class Window126 : Window
    	{
    		public Window126()
    		{
    			InitializeComponent();
    		}
    	}
    
    	public class ViewModel : INotifyPropertyChanged
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    			=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
    		// contructor: load StudentList 
    		public ViewModel() { for (int i = 1; i <= 10; i++) StudentList.Add(new Student() { Name = $"Student {i}" }); }
    
    		public ObservableCollection<Student> StudentList { get; set; } = new ObservableCollection<Student>();
    		public ObservableCollection<string> BloodGroups { get; set; } = new ObservableCollection<string>() { "A", "B", "AB", "0"};
    
    		private Student _student;
    		public Student Student
    		{
    			get => _student;
    			set
    			{
    				_student = value;
    				OnPropertyChanged();
    			}
    		}
    
    	}
    	public class Student : INotifyPropertyChanged
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    		{
    			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    		}
    
    		private string _name;
    		public string Name
    		{
    			get => _name;
    			set
    			{
    				_name = value;
    				OnPropertyChanged();
    			}
    		}
    
    		private string _bloodgroup;
    		public string BloodGroup
    		{
    			get => _bloodgroup;
    			set
    			{
    				_bloodgroup = value;
    				OnPropertyChanged();
    			}
    		}
    	}
    
    }
    
    

    Result:

    x


  2. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-12-20T14:34:20.1633333+00:00

    Hi,
    try following demo with CustomControl:

    XAML:

    <Window x:Class="WpfApp127.Window127"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp127"
            mc:Ignorable="d"
            Title="Mesh Ka_231219" Height="200" Width="400">
      <Window.DataContext>
        <local:ViewModel/>
      </Window.DataContext>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="auto"/>
          <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <local:CustomControl Grid.Column="0"
                             Header="Student"
                             ItemsSource="{Binding StudentList}"
                             SelectedItem="{Binding Student}"
                             DisplayMemberPath="Name"
                             Margin="10"/>
        <local:CustomControl Grid.Column="1"
                             Header="Blood Group"
                             ItemsSource="{Binding BloodGroups}"
                             SelectedValue="{Binding Student.BloodGroup}"
                             Margin="10"/>
        <Grid Grid.Row="1" Grid.Column="0" Margin="10">
          <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
          </Grid.RowDefinitions>
          <Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right"
               Content="Student:"/>
          <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Student.Name}" Margin="4"/>
          <Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right"
               Content="Bloodgroup:"/>
          <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Student.BloodGroup}" Margin="4"/>
        </Grid>
      </Grid>
    </Window>
    

    Code (ViewModel, Data, CustomControl):

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace WpfApp127
    {
    	public class ViewModel : INotifyPropertyChanged
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    			=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
    		// contructor: load StudentList 
    		public ViewModel() { for (int i = 1; i <= 10; i++) StudentList.Add(new Student() { Name = $"Student {i}" }); }
    
    		public ObservableCollection<Student> StudentList { get; set; } = new ObservableCollection<Student>();
    		public ObservableCollection<string> BloodGroups { get; set; } = new ObservableCollection<string>() { "A", "B", "AB", "0" };
    
    		private Student _student;
    		public Student Student
    		{
    			get => _student;
    			set
    			{
    				_student = value;
    				OnPropertyChanged();
    			}
    		}
    
    	}
    	public class Student : INotifyPropertyChanged
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    		{
    			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    		}
    
    		private string _name;
    		public string Name
    		{
    			get => _name;
    			set
    			{
    				_name = value;
    				OnPropertyChanged();
    			}
    		}
    
    		private string _bloodgroup;
    		public string BloodGroup
    		{
    			get => _bloodgroup;
    			set
    			{
    				_bloodgroup = value;
    				OnPropertyChanged();
    			}
    		}
    	}
    
    	public class CustomControl : UserControl, INotifyPropertyChanged
    	{
    		public CustomControl() => CreateContent();
    		private Label header = new Label();
    		private ComboBox cb = new ComboBox();
    
    		public static readonly DependencyProperty HeaderProperty =
    				DependencyProperty.Register("Header", typeof(string), typeof(CustomControl), new PropertyMetadata(string.Empty));
    		public string Header
    		{
    			get { return GetValue(HeaderProperty).ToString(); }
    			set { SetValue(HeaderProperty, value); }
    		}
    
    		public static readonly DependencyProperty ItemsSourceProperty =
    				DependencyProperty.Register("ItemsSource", typeof(object), typeof(CustomControl), new PropertyMetadata(null));
    		public object ItemsSource
    		{
    			get { return GetValue(ItemsSourceProperty); }
    			set { SetValue(ItemsSourceProperty, value); }
    		}
    
    		public static readonly DependencyProperty SelectedItemProperty =
    				DependencyProperty.Register("SelectedItem", typeof(object),
    					typeof(CustomControl),
    					new FrameworkPropertyMetadata(null,
    						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
    						new PropertyChangedCallback(OnSelectedItemChanged)));
    		public object SelectedItem
    		{
    			get { return GetValue(SelectedItemProperty); }
    			set { SetValue(SelectedItemProperty, value); }
    		}
    		private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    		{
    			CustomControl cc = d as CustomControl;
    			if (cc != null) cc.OnPropertyChanged(nameof(SelectedItem));
    		}
    
    		public static readonly DependencyProperty DisplayMemberPathProperty =
    					DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(CustomControl), new PropertyMetadata(null));
    		public string DisplayMemberPath
    		{
    			get { return GetValue(DisplayMemberPathProperty).ToString(); }
    			set { SetValue(DisplayMemberPathProperty, value); }
    		}
    
    		public static readonly DependencyProperty SelectedValueProperty =
    		DependencyProperty.RegisterAttached("SelectedValue", typeof(object),
    			typeof(CustomControl),
    			new FrameworkPropertyMetadata(null,
    				FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
    				new PropertyChangedCallback(OnSelectedValueChanged)));
    		public object SelectedValue
    		{
    			get { return GetValue(SelectedValueProperty); }
    			set { SetValue(SelectedValueProperty, value); }
    		}
    		private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    		{
    			CustomControl cc = d as CustomControl;
    			if (cc != null) cc.OnPropertyChanged(nameof(SelectedValue));
    		}
    
    		public event PropertyChangedEventHandler PropertyChanged;
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    			=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
    		void CreateContent()
    		{
    			Grid _grid = new Grid();
    			_grid.RowDefinitions.Add(new RowDefinition());
    			_grid.RowDefinitions.Add(new RowDefinition());
    			_grid.Children.Add(header);
    			header.Foreground = System.Windows.Media.Brushes.Green;
    			header.SetBinding(Label.ContentProperty, new Binding("Header") { Source = this });
    			_grid.Children.Add(cb);
    			cb.SetValue(Grid.RowProperty, 1);
    			cb.SetBinding(ComboBox.ItemsSourceProperty, new Binding("ItemsSource") { Source = this });
    			cb.SetBinding(ComboBox.SelectedItemProperty, new Binding("SelectedItem") { Source = this });
    			cb.SetBinding(ComboBox.DisplayMemberPathProperty, new Binding("DisplayMemberPath") { Source = this });
    			cb.SetBinding(ComboBox.SelectedValueProperty, new Binding("SelectedValue") { Source = this });
    			this.Content = _grid;
    		}
    	}
    }
    

    Result:

    x


  3. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-12-21T07:05:35.05+00:00

    Hi,
    if you want to use XAML-Template you can try following demo:

    XAML:

    <Window x:Class="WpfApp128.Window128"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp128"
            mc:Ignorable="d"
            Title="Mesh Ka_231219" Height="200" Width="400">
      <Window.DataContext>
        <local:ViewModel/>
      </Window.DataContext>
      <Window.Resources>
        <Style TargetType="{x:Type local:FloatingLabelComboBox}">
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="{x:Type local:FloatingLabelComboBox}">
                <StackPanel FlowDirection="LeftToRight">
                  <Label Content="{TemplateBinding LabelText}"
                         Foreground="{TemplateBinding LabelForeground}"
                         FontSize="{TemplateBinding LabelFontSize}"
                         VerticalAlignment="Stretch"
                         Margin="-5 0 0 0" />
    
                  <ComboBox x:Name="textBox" 
                            ItemsSource="{TemplateBinding ItemsSource}"
                            DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
                            SelectedItem="{Binding Path=SelectedItem, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                            SelectedValue="{Binding Path=SelectedValue, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Center"
                            Foreground="{TemplateBinding Foreground}" />
                </StackPanel>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
      </Window.Resources>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="auto"/>
          <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <local:FloatingLabelComboBox Grid.Column="0" 
                                     LabelText="Student"
                                     ItemsSource="{Binding StudentList}"
                                     SelectedItem="{Binding Student}"
                                     DisplayMemberPath="StudentName"
                                     LabelForeground="Black"
                                     Margin="10"/>
    
        <local:FloatingLabelComboBox Grid.Column="1" 
                                     LabelText="Blood Group"
                                     ItemsSource="{Binding BloodGroups}"
                                     SelectedValue="{Binding Student.BloodGroup}"
                                     LabelForeground="Black"
                                     Margin="10"/>
    
        <Grid Grid.Row="1" Grid.Column="0" Margin="10">
          <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
          </Grid.RowDefinitions>
          <Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right"
               Content="Student:"/>
          <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Student.StudentName}" Margin="4"/>
          <Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right"
               Content="Bloodgroup:"/>
          <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Student.BloodGroup}" Margin="4"/>
        </Grid>
      </Grid>
    </Window>
    

    Code:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace WpfApp128
    {
    	public class ViewModel : INotifyPropertyChanged
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    			=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
    		// contructor: load StudentList 
    		public ViewModel() { for (int i = 1; i <= 10; i++) 
    				StudentList.Add(new Student() { StudentId = i, StudentName = $"Student {i}" }); }
    
    		public ObservableCollection<Student> StudentList { get; set; } = new ObservableCollection<Student>();
    		public ObservableCollection<string> BloodGroups { get; set; } = new ObservableCollection<string>() { "A", "B", "AB", "0" };
    
    		private Student _student;
    		public Student Student
    		{
    			get => _student;
    			set
    			{
    				_student = value;
    				OnPropertyChanged();
    			}
    		}
    
    	}
    	public class Student : INotifyPropertyChanged
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "") =>
    			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
    		public int StudentId { get; set; }
    
    		private string _studentName;
    		public string StudentName
    		{
    			get => _studentName;
    			set
    			{
    				_studentName = value;
    				OnPropertyChanged();
    			}
    		}
    
    		private string _bloodgroup;
    		public string BloodGroup
    		{
    			get => _bloodgroup;
    			set
    			{
    				_bloodgroup = value;
    				OnPropertyChanged();
    			}
    		}
    	}
    	public class FloatingLabelComboBox : Control, INotifyPropertyChanged
    	{
    		static FloatingLabelComboBox()
    		{
    			DefaultStyleKeyProperty.OverrideMetadata(typeof(FloatingLabelComboBox), new FrameworkPropertyMetadata(typeof(FloatingLabelComboBox)));
    		}
    
    		public static readonly DependencyProperty LabelTextProperty =
    			 DependencyProperty.Register("LabelText", typeof(string),
    				 typeof(FloatingLabelComboBox), new PropertyMetadata(string.Empty));
    		public string LabelText
    		{
    			get { return GetValue(LabelTextProperty).ToString(); }
    			set { SetValue(LabelTextProperty, value); }
    		}
    
    		public static readonly DependencyProperty LabelForegroundProperty =
    				DependencyProperty.Register("LabelForeground", typeof(Brush),
    					typeof(FloatingLabelComboBox), new PropertyMetadata(Brushes.AliceBlue));
    		public Brush LabelForeground
    		{
    			get { return (Brush)GetValue(LabelForegroundProperty); }
    			set { SetValue(LabelForegroundProperty, value); }
    		}
    
    		public static readonly DependencyProperty LabelFontSizeProperty =
    				DependencyProperty.Register("LabelFontSize", typeof(double),
    					typeof(FloatingLabelComboBox), new PropertyMetadata(10.0));
    		public double LabelFontSize
    		{
    			get { return (double)GetValue(LabelFontSizeProperty); }
    			set { SetValue(LabelFontSizeProperty, value); }
    		}
    
    		public static readonly DependencyProperty ItemsSourceProperty =
    				DependencyProperty.Register("ItemsSource", typeof(object),
    					typeof(FloatingLabelComboBox), new PropertyMetadata(null));
    		public object ItemsSource
    		{
    			get { return GetValue(ItemsSourceProperty); }
    			set { SetValue(ItemsSourceProperty, value); }
    		}
    
    		public static readonly DependencyProperty SelectedItemProperty =
    				DependencyProperty.Register("SelectedItem", typeof(object),
    					typeof(FloatingLabelComboBox),
    						new FrameworkPropertyMetadata(null,
    						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
    						OnSelectedItemChanged));
    		public object SelectedItem
    		{
    			get { return GetValue(SelectedItemProperty); }
    			set { SetValue(SelectedItemProperty, value); }
    		}
    		private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    		{
    			FloatingLabelComboBox cc = d as FloatingLabelComboBox;
    			cc.SelectedItem = e.NewValue;
    			if (cc != null) cc.OnPropertyChanged(nameof(SelectedItem));
    		}
    
    		public static readonly DependencyProperty DisplayMemberPathProperty =
    				DependencyProperty.Register("DisplayMemberPath", typeof(string),
    					typeof(FloatingLabelComboBox), new PropertyMetadata(null));
    		public string DisplayMemberPath
    		{
    			get { return GetValue(DisplayMemberPathProperty).ToString(); }
    			set { SetValue(DisplayMemberPathProperty, value); }
    		}
    
    		public static readonly DependencyProperty SelectedValueProperty =
    				DependencyProperty.RegisterAttached("SelectedValue", typeof(object),
    					typeof(FloatingLabelComboBox),
    						new FrameworkPropertyMetadata(null,
    						FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
    						new PropertyChangedCallback(OnSelectedValueChanged)));
    		public object SelectedValue
    		{
    			get { return GetValue(SelectedValueProperty); }
    			set { SetValue(SelectedValueProperty, value); }
    		}
    		private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    		{
    			FloatingLabelComboBox cc = d as FloatingLabelComboBox;
    			cc.SelectedItem = e.NewValue;
    			if (cc != null) cc.OnPropertyChanged(nameof(SelectedValue));
    		}
    
    		public event PropertyChangedEventHandler PropertyChanged;
    		private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    			=> PropertyChanged?.BeginInvoke(this, new PropertyChangedEventArgs(propertyName), null, null);
    	}
    }
    
    

    Result:

    x

    0 comments No comments

  4. Hui Liu-MSFT 48,566 Reputation points Microsoft Vendor
    2023-12-21T07:09:56.27+00:00

    Hi,@Mesh Ka. Welcome to Microsoft Q&A Forum.

    I'm trying to implement functionality, but FloatingLabelComboBox can't to set binding data to codebehind.
    I'm still looking for the cause of the problem and will come back with updates.

    Currently I have the code below. Maybe it will inspire you a little bit.

    
    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:materialdesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        xmlns:local="clr-namespace:FloatingLabelComboBox"
        xmlns:custom="clr-namespace:FloatingLabelComboBox.CustomControls">
        
        <Style TargetType="{x:Type custom:FloatingLabelComboBox }">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type  custom:FloatingLabelComboBox }">
                        <StackPanel FlowDirection="LeftToRight">
                            <TextBlock Text="{TemplateBinding LabelText}"
                                   Foreground="{TemplateBinding LabelForeground}"
                                   FontSize="{TemplateBinding LabelFontSize}"
                                   VerticalAlignment="Stretch"
                                   Margin="-5 0 0 0" />
    
                            <ComboBox x:Name="textBox" 
                                         ItemsSource="{TemplateBinding ItemsSource}"
                                      SelectedItem="{TemplateBinding SelectedItem}"
                                       SelectedValuePath="{TemplateBinding SelectedValuePath}"
                                       SelectedValue="{TemplateBinding SelectedValue}" 
                                   DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
                                    
                                      HorizontalAlignment="Stretch"
                                      VerticalAlignment="Center"
                                      materialdesign:HintAssist.IsFloating="False"
                                      materialdesign:ColorZoneAssist.Mode="Dark"
                                      materialdesign:HintAssist.HintOpacity="0.10"
                                      materialdesign:HintAssist.FontFamily="Century Gothic"
                                      Foreground="{TemplateBinding Foreground}" />
                        </StackPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>
    
    

    FloatingLabelComboBox :

    
     public class FloatingLabelComboBox : ComboBox
      {
            static FloatingLabelComboBox()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(FloatingLabelComboBox), new FrameworkPropertyMetadata(typeof(FloatingLabelComboBox)));
            }
            public static readonly DependencyProperty LabelTextProperty =
               DependencyProperty.Register("LabelText", typeof(string), typeof(FloatingLabelComboBox), new PropertyMetadata("LabelText"));
    
            public string LabelText
            {
                get { return (string)GetValue(LabelTextProperty); }
                set { SetValue(LabelTextProperty, value); }
            }
    
            public static readonly DependencyProperty LabelForegroundProperty =
                DependencyProperty.Register("LabelForeground", typeof(Brush), typeof(FloatingLabelComboBox), new PropertyMetadata(Brushes.AliceBlue));
    
            public Brush LabelForeground
            {
                get { return (Brush)GetValue(LabelForegroundProperty); }
                set { SetValue(LabelForegroundProperty, value); }
            }
    
    
            public static readonly DependencyProperty LabelFontSizeProperty =
                DependencyProperty.Register("LabelFontSize", typeof(double), typeof(FloatingLabelComboBox), new PropertyMetadata(10.0));
    
            public double LabelFontSize
            {
                get { return (double)GetValue(LabelFontSizeProperty); }
                set { SetValue(LabelFontSizeProperty, value); }
            }
    }
    
    
    

    StudentModel:

    
     public class StudentModel : INotifyPropertyChanged
        {
            private int _studentId;
            public int StudentId
            {
                get => _studentId;
                set
                {
                    if (value == _studentId) return;
                    _studentId = value;
                    OnPropertyChanged(nameof(_studentId));
                }
            }
    
       
        private string _studentName;
        public string StudentName
        {
          get => _studentName;
          set
          {
            _studentName = value;
            OnPropertyChanged();
          }
        }
    
        private ICollection<string> _bloodgroup;
        public ICollection<string> BloodGroup
        {
          get => _bloodgroup;
          set
          {
            _bloodgroup = value;
            OnPropertyChanged();
          }
        }
    
    
        public event PropertyChangedEventHandler PropertyChanged;
    
            private void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
    
          }
    
    

    ViewModel:

    
      public class ViewModel : INotifyPropertyChanged
      {
        
        public ViewModel()
        {
          this.Students = new ObservableCollection<StudentModel>()
                { new StudentModel(){ StudentName = "Jane", BloodGroup = new ObservableCollection<string>() { "A", "B", "AB", "0"  }},
                  new StudentModel(){ StudentName = "Jone",  BloodGroup = new ObservableCollection<string>()  { "A", "B", "AB", "0" }},
                  new StudentModel(){ StudentName = "Tina", BloodGroup = new ObservableCollection<string>() {"A", "B", "AB", "0"}}
                };
        
    
          this.SelectedStudent = Students.FirstOrDefault();
        }
        public ObservableCollection<StudentModel> Students { get; private set; }
        public ObservableCollection<string> BloodGroups { get; private set; }
    
    
        public StudentModel _selectedStudent;
        public StudentModel SelectedStudent
        {
          get { return _selectedStudent; }
          set
          {
            _selectedStudent = value;
            if (_selectedStudent != null)
            {
              BloodGroups = _selectedStudent.BloodGroup;
              OnPropertyChanged(nameof(BloodGroups));
            }
    
            OnPropertyChanged();
          }
        }
    
        public string _selectedBloodGroup ;
        public string SelectedBloodGroup
        {
          get { return _selectedBloodGroup; }
          set
          {
            _selectedBloodGroup = value;
            OnPropertyChanged();
          }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        private void OnPropertyChanged([CallerMemberName] String propertyName = "")
        {
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
      }
    
    

    MainWindow:

    <Window x:Class="FloatingLabelComboBox.MainWindow"
    ...
            xmlns:local="clr-namespace:FloatingLabelComboBox"
            xmlns:custom="clr-namespace:FloatingLabelComboBox.CustomControls" 
            xmlns:materialdesign="http://materialdesigninxaml.net/winfx/xaml/themes"
          >
        <Window.DataContext>
            <local:ViewModel/>
        </Window.DataContext>
        <Grid>
          ...
            <StackPanel Margin="30" Orientation="Horizontal" Height="80">
                <custom:FloatingLabelComboBox Width="160" 
                                             LabelText="Student" 
                                             Foreground="AliceBlue" 
                                             LabelForeground="LimeGreen"
                                             materialdesign:HintAssist.Hint="Student"
                                             FontSize="20"   
                                             LabelFontSize="13"
                                               ItemsSource="{Binding Students}"  
                                              DisplayMemberPath="StudentName"    SelectedItem="{Binding SelectedStudent,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                           
                />
                <custom:FloatingLabelComboBox Width="160" 
                                             Margin="40 0 0 0"
                                             LabelText="Blood Group" 
                                             Foreground="AliceBlue" 
                                             LabelForeground="LimeGreen"
                                             materialdesign:HintAssist.Hint="Blood Group"
                                             FontSize="20"
                                             LabelFontSize="13"   ItemsSource="{Binding SelectedStudent.BloodGroup}"
                                         SelectedItem="{Binding SelectedBloodGroup,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"     />
                <StackPanel>
                    <TextBlock Text="Categories" Width="100"/>
                    <ComboBox ItemsSource="{Binding Students}" Foreground="White"  Width="100"  Height="50" DisplayMemberPath="StudentName"  SelectedItem="{Binding SelectedStudent}" />
                </StackPanel>
                <StackPanel>
                    <TextBlock Text="Proucts" Width="100"/>
                    <ComboBox ItemsSource="{Binding SelectedStudent.BloodGroup}" Width="100"  Height="50"  Foreground="White"   SelectedItem="{Binding SelectedBloodGroup}"   />
                </StackPanel>
            </StackPanel>
        </Grid>
    </Window>
    
    
    

    The result:

    2


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.