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,675 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,268 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.
765 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,231 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,231 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,231 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,231 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 38,331 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.