How Can i add a "AddNew" button at the end of drop downs in a Custom ComboBox Control?

Mesh Ka 345 Reputation points
2023-12-28T11:28:17.14+00:00

I have a custom control with a ComboBox. I want to add a button at the end of the drop-down list that says "AddNew," such as "AddNewStudent," which, when clicked, opens a form to register a new student.

Since I expect to create many instances of the custom control, some of which may not need this feature, I want it to be activatable and vice versa. For instance, a ComboBox that lists students' grades doesn't need this feature because the grades are something known and unchanging. Additionally, I want the actions taken when the "AddNew" button is clicked to be a user-settable dependency property since the actions taken when the "AddNew" button is clicked will be different for each instance.

Here is the repos on github of the Example below.

customDateControl

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
{count} votes

3 answers

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-12-28T19:23:57.9166667+00:00

    Hi,
    you can add Button, bind to ICommand property and add new student object.

    XAML window:

    <Window x:Class="WpfApp1.Window131"
            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:WpfApp131"
            mc:Ignorable="d"
            Title="Mesh Ka_231228" Height="250" 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>
                  <Button Content="Add New" Margin="10" 
                          Command="{Binding Command, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
                          Visibility="{Binding CommandVisible, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
                </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"
                                     Command="{Binding CmdNew}"/>
    
        <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>
    

    And 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.Input;
    using System.Windows.Media;
    using System.Linq;
    
    namespace WpfApp131
    {
    	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 ICommand CmdNew
    		{ get => new RelayCommand(CmdNewStudent); }
    		private void CmdNewStudent(object obj)
    		{
    			int lastID = (from s in _studentList orderby s.StudentId descending select s.StudentId).FirstOrDefault();
    			_studentList.Add(new Student() { StudentId = lastID + 1, StudentName = $"Student {lastID + 1}" });
    			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 static readonly DependencyProperty CommandProperty =
    		DependencyProperty.RegisterAttached("Command", typeof(ICommand),
    			typeof(FloatingLabelComboBox),
    				new FrameworkPropertyMetadata(null));
    		public object Command
    		{
    			get { return GetValue(CommandProperty); }
    			set { SetValue(CommandProperty, value); }
    		}
    		public Visibility CommandVisible { get => (Command == null) ? Visibility.Collapsed : Visibility.Visible; }
    
    		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();
    		}
    	}
    	public class RelayCommand : ICommand
    	{
    		private readonly Predicate<object> _canExecute;
    		private readonly Action<object> _action;
    		public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }
    		public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }
    		public void Execute(object o) => _action(o);
    		public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);
    		public event EventHandler CanExecuteChanged
    		{
    			add { CommandManager.RequerySuggested += value; }
    			remove { CommandManager.RequerySuggested -= value; }
    		}
    	}
    }
    

    Result:

    x


  2. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-12-29T18:50:30.4866667+00:00

    Hi,
    to add New button at the end of drop downs you can use ID with value==0 and trigger like in following demo.

    XAML:

    <Window x:Class="WpfApp1.Window132"
            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:WpfApp132"
            mc:Ignorable="d"
            Title="Mesh Ka_231229" Height="400" Width="400">
      <Window.Resources>
        <local:ViewModel x:Key="vm"/>
      </Window.Resources>
      <StackPanel DataContext="{StaticResource vm}">
        <ComboBox ItemsSource="{Binding StudentsView}">
          <ComboBox.ItemTemplate>
            <DataTemplate>
              <Grid>
                <TextBlock Name="somePerson" Text="{Binding Path=StudentName}"/>
                <Button x:Name="cmdButton" Content="New" Visibility="Collapsed"
                        Command="{Binding Source={StaticResource vm}}"/>
              </Grid>
              <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding StudentId}" Value="0">
                  <DataTrigger.Setters>
                    <Setter Property="Visibility" Value="Visible" TargetName="cmdButton" />
                  </DataTrigger.Setters>
                </DataTrigger>
              </DataTemplate.Triggers>
            </DataTemplate>
          </ComboBox.ItemTemplate>
        </ComboBox>
      </StackPanel>
    </Window>
    

    Code:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Input;
    
    namespace WpfApp132
    {
    	public class ViewModel : INotifyPropertyChanged, ICommand
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    		public event EventHandler CanExecuteChanged;
    
    		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}" });
    			_studentList.Add(new Student() { StudentId = 0 });
    			_cvsStudentList.Source = _studentList;
    		}
    
    		internal ObservableCollection<Student> _studentList { get; set; } = new ObservableCollection<Student>();
    		private CollectionViewSource _cvsStudentList = new CollectionViewSource();
    		public ICollectionView StudentsView { get => _cvsStudentList.View; }
    
    		private Student _student;
    		public Student Student
    		{
    			get => _student;
    			set
    			{
    				_student = value;
    				OnPropertyChanged();
    				StudentsView.Refresh();
    			}
    		}
    		public bool CanExecute(object parameter) => true;
    		public void Execute(object parameter)
    		{
    			int lastID = (from s in _studentList orderby s.StudentId descending select s.StudentId).FirstOrDefault();
    			Student buttonItem = (from s in _studentList where s.StudentId == 0 select s).FirstOrDefault(); ;
    			_studentList.Remove(buttonItem);
    			_studentList.Add(new Student() { StudentId = lastID + 1, StudentName = $"Student {lastID + 1}" });
    			_studentList.Add(new Student() { StudentId = 0 }); 
    			StudentsView.Refresh();
    		}
    	}
    	public class Student 
    	{
    		public int StudentId { get; set; }
    		public string StudentName { get; set; }
    	}
    }
    

    Result:

    x


  3. Peter Fleischer (former MVP) 19,326 Reputation points
    2023-12-31T09:33:22.9533333+00:00

    Hi,
    you can use Command property for switching "AddNew" like in following demo:

    XAML:

    <Window x:Class="WpfApp1.Window133"
            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:WpfApp133"
            mc:Ignorable="d"
            Title="Mesh Ka_231229" Height="400" Width="400">
      <Window.Resources>
        <local:ViewModel x:Key="vm"/>
        <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="PART_MyComboBox"
                            ItemsSource="{TemplateBinding ItemsSource}"
                            SelectedItem="{Binding Path=SelectedItem, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                            SelectedValue="{Binding Path=SelectedValue, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                            DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Center"
                            Foreground="{TemplateBinding Foreground}">
                  </ComboBox>
                </StackPanel>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
        <local:BoolToColorConverter x:Key="BoolToColorConverter"/>
      </Window.Resources>
    
      <Grid DataContext="{StaticResource vm}">
        <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"
                                     Command="{Binding CmdNew}"/>
    
        <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 100 10 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.Input;
    using System.Windows.Media;
    using System.Linq;
    
    namespace WpfApp133
    {
    	public class ViewModel : INotifyPropertyChanged
    	{
    		public event PropertyChangedEventHandler PropertyChanged;
    		public event EventHandler CanExecuteChanged;
    
    		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}" });
    		}
    
    		internal ObservableCollection<Student> _studentList { get; set; } = new ObservableCollection<Student>();
    		private CollectionViewSource _cvsStudentList = new CollectionViewSource();
    		public ObservableCollection<string> BloodGroups { get; set; } = new ObservableCollection<string>() { "A", "B", "AB", "0" };
    
    		public ICollectionView StudentsView
    		{
    			get
    			{
    				ObservableCollection<Student> _viewList = new ObservableCollection<Student>(_studentList);
    				_viewList.Add(new Student() { StudentId = 0 });
    				_cvsStudentList.Source = _viewList;
    				return _cvsStudentList.View;
    			}
    		}
    
    		private Student _student;
    		public Student Student
    		{
    			get => _student;
    			set
    			{
    				_student = value;
    				OnPropertyChanged(nameof(StudentsView));
    				OnPropertyChanged();
    			}
    		}
    
    		public ICommand CmdNew
    		{ get => new RelayCommand(CmdNewStudent); }
    		private void CmdNewStudent(object obj)
    		{
    			int lastID = (from s in _studentList orderby s.StudentId descending select s.StudentId).FirstOrDefault();
    			_studentList.Add(new Student() { StudentId = lastID + 1, StudentName = $"Student {lastID + 1}" });
    			OnPropertyChanged(nameof(StudentsView));
    		}
    	}
    	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 static readonly DependencyProperty CommandProperty =
    		DependencyProperty.RegisterAttached("Command", typeof(ICommand),
    			typeof(FloatingLabelComboBox),
    				new FrameworkPropertyMetadata(null));
    		public object Command
    		{
    			get { return GetValue(CommandProperty); }
    			set { SetValue(CommandProperty, value); }
    		}
    
    		public static DependencyProperty
    			PropertyPathProperty = DependencyProperty.Register("PropertyPath", typeof(string),
    				typeof(FloatingLabelComboBox), null);
    		public string PropertyPath
    		{
    			get { return GetValue(PropertyPathProperty).ToString(); }
    			set { SetValue(PropertyPathProperty, value); }
    		}
    		public Visibility CommandVisible { get => (Command == null) ? Visibility.Collapsed : Visibility.Visible; }
    
    		#region ItemTemplate
    		private ComboBox myComboBox;
    		public override void OnApplyTemplate()
    		{
    			base.OnApplyTemplate();
    			//Find the combobox in the template once it's applied
    			myComboBox = base.Template.FindName("PART_MyComboBox", this) as ComboBox;
    			if (Command != null) ComboBoxItemTemplate();
    		}
    
    		private void ComboBoxItemTemplate()
    		{
    			//create the data template
    			DataTemplate _dt = new DataTemplate();
    			_dt.DataType = typeof(ComboBoxItem);
    
    			//set up the grid
    			FrameworkElementFactory _dtGrid = new FrameworkElementFactory(typeof(Grid));
    			_dtGrid.Name = "myGrid";
    			_dtGrid.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
    
    			//set up the textblock
    			FrameworkElementFactory _dtTb = new FrameworkElementFactory(typeof(TextBlock));
    			_dtTb.SetBinding(TextBlock.TextProperty, new Binding(DisplayMemberPath));
    			_dtGrid.AppendChild(_dtTb);
    
    			//set up the Button
    			FrameworkElementFactory _dtBtn = new FrameworkElementFactory(typeof(Button));
    			_dtBtn.Name = "cmdButton";
    			_dtBtn.SetValue(Button.ContentProperty, "AddNew");
    			_dtBtn.SetValue(Button.VisibilityProperty, Visibility.Collapsed);
    			_dtBtn.SetValue(Button.CommandProperty, Command); ;
    			_dtGrid.AppendChild(_dtBtn);
    
    			//set up the trigger
    			DataTrigger _dtTrigger = new DataTrigger() { Binding = new Binding("StudentId"), Value = 0 };
    			_dtTrigger.Setters.Add(new Setter() { Property = Control.VisibilityProperty, Value = Visibility.Visible, TargetName = "cmdButton" });
    			_dt.Triggers.Add(_dtTrigger);
    
    			//set the visual tree of the data template
    			_dt.VisualTree = _dtGrid;
    
    			//set the item template to be our shiny new data template
    			myComboBox.DisplayMemberPath = null;
    			myComboBox.ItemTemplate = _dt;
    		}
    		#endregion
    
    		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();
    		}
    	}
    	public class RelayCommand : ICommand
    	{
    		private readonly Predicate<object> _canExecute;
    		private readonly Action<object> _action;
    		public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }
    		public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }
    		public void Execute(object o) => _action(o);
    		public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);
    		public event EventHandler CanExecuteChanged
    		{
    			add { CommandManager.RequerySuggested += value; }
    			remove { CommandManager.RequerySuggested -= value; }
    		}
    	}
    }
    

    Result:

    x

    0 comments No comments

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.