[WPF] Styling The ComboBox with watermark

Victor King 21 Reputation points
2021-04-27T15:27:37.667+00:00

I am trying to implement a comboBox style. In this case , there is a watermark on it,and if you select an item , watermark will collapsed to the left top corner. If ComboboxItem with an image , it will change the main ComboBox button layout, Is there any way to achieve this effect?

I've found the implementation of textbox,
g.gif
How do I implement this effect on combobox. Any ideas?
91766-111.png

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,683 questions
0 comments No comments
{count} votes

Accepted answer
  1. DaisyTian-1203 11,616 Reputation points
    2021-04-28T04:17:21.287+00:00

    I made a demo as a workaround for you to refer to.
    Xaml code:

     <Grid>  
            <Grid Width="200" Height="200">  
                <ComboBox Name="myCom"  
                          Height="50"  
                          Width="200"  
                          HorizontalContentAlignment="Center"  
                          VerticalContentAlignment="Center"  
                 >  
                    <ComboBox.ItemTemplate>  
                        <ItemContainerTemplate>  
                            <WrapPanel>  
                                <Image Source="{Binding Icon}" Width="25" Height="25" Margin="0 0 30 0"></Image>  
                                <TextBlock  ><Run Text="{Binding Name}"/></TextBlock>  
                            </WrapPanel>  
                        </ItemContainerTemplate>  
                    </ComboBox.ItemTemplate>  
                </ComboBox>  
                <Label  Height="50"  
                        Width="180"   
                        HorizontalAlignment="Left"  
                        DataContext="{Binding ElementName=myCom,Path=SelectedItem}"  
                        Content="-- Select --"   
                        >  
                    <Label.Style>  
                        <Style TargetType="Label">  
                            <Setter Property="FontSize" Value="12"></Setter>  
                            <Setter Property="Background" Value="Transparent"></Setter>  
                            <Setter Property="HorizontalContentAlignment" Value="Left"/>  
                            <Setter Property="VerticalContentAlignment" Value="Top"/>  
                            <Setter Property="Foreground" Value="{x:Static SystemColors.GrayTextBrush}"/>  
                            <Style.Triggers>  
                                <DataTrigger Binding="{Binding ElementName=myCom,Path=SelectedItem}" Value="{x:Null}">  
                                    <Setter Property="HorizontalContentAlignment" Value="Center"/>  
                                    <Setter Property="VerticalContentAlignment" Value="Top"/>  
                                    <Setter Property="FontSize" Value="30"></Setter>  
                                </DataTrigger>  
      
                            </Style.Triggers>  
                        </Style>  
                    </Label.Style>  
                </Label>  
            </Grid>  
        </Grid>  
    

    C# code:

     public partial class MainWindow : Window  
        {  
            public MainWindow()  
            {  
                InitializeComponent();  
      
                List<Movie> lt = new List<Movie>()  
                {  
                    new Movie(){Name="Movie1" ,Icon= new BitmapImage(new Uri("Images/img_failed.png", UriKind.Relative))},  
                    new Movie(){Name="Movie1" ,Icon= new BitmapImage(new Uri("Images/Myico.ico", UriKind.Relative))},  
                    new Movie(){Name="Movie1" ,Icon= new BitmapImage(new Uri("Images/Todolist.ico", UriKind.Relative))}  
                };  
                myCom.ItemsSource = lt;  
            }   
        }  
        public class Movie  
        {  
            public string Name { get; set; }  
            public ImageSource Icon { get; set; }  
        }  
    

    The result picture is:
    91955-3.gif


    If the response is helpful, please click "Accept Answer" and upvote it.
    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.


1 additional answer

Sort by: Most helpful
  1. Emon Haque 3,176 Reputation points
    2021-04-27T16:21:20.497+00:00

    Previously, I'd done something like that by retemplating the ControlTemplate of Control BUT that's complicated, I've to extend the ComboBox/Control Class, style the ControlTemplate in Themes.xaml and have to have some Binding and TemplateBinding. To me, now, code behind approach seems easier for customization. Here's a sample of ComboBox:

    public class SelectItem : FrameworkElement  
    {  
        string iconData = "M11,6V14L7.5,10.5L6.08,11.92L12,17.84L17.92,11.92L16.5,10.5L13,14V6H11M12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22Z";  
        string closeData = "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";  
    
        Border border, selecteditem, rightIconBorder;  
        TextBox inputBox;  
        TextBlock hintBlock;  
        Grid container;  
        Path leftIcon, rightIcon, closeIcon;  
        Popup pop;  
        ListBox list;  
        ICollectionView view;  
        ContentControl selectedContent;  
        TranslateTransform translateHint;  
        ScaleTransform scaleHint;  
        DoubleAnimation translateHintAnim, scaleHintAnim, rightIconAnim;  
        ColorAnimation brushAnim;  
        SolidColorBrush brush;  
        Run requiredText;  
        bool isHintMoved;  
          
        DataTemplate itemTemplate;  
        public DataTemplate ItemTemplate {  
            get { return itemTemplate; }  
            set {  
                itemTemplate = value;  
                selectedContent.ContentTemplate = value;  
                list.ItemTemplate = value;  
            }  
        }  
        string hint;  
        public string Hint {  
            get { return hint; }  
            set { hint = value; hintBlock.Inlines.Add(value); }  
        }  
        string icon;  
        public string Icon {  
            get { return icon; }  
            set {   
                icon = value;  
                leftIcon.Data = Geometry.Parse(value);  
                leftIcon.Visibility = Visibility.Visible;  
            }  
        }  
        bool isRequired;  
        public bool IsRequired {  
            get { return isRequired; }  
            set {   
                isRequired = value;  
                if (isRequired) {  
                    requiredText = new Run() {  
                        Text = " (required)",  
                        FontStyle = FontStyles.Italic,  
                        Foreground = Brushes.Coral  
                    };  
                    hintBlock.Inlines.Add(requiredText);  
                }  
            }  
        }  
    
        public SelectItem() {  
            brush = new SolidColorBrush(Colors.SkyBlue);  
            Margin = new Thickness(5);  
              
            initializeControls();  
            initializeSelectedItemControls();  
              
            container = new Grid() {  
                ColumnDefinitions = {  
                    new ColumnDefinition() {Width = GridLength.Auto},  
                    new ColumnDefinition(),  
                    new ColumnDefinition() {Width = GridLength.Auto}  
                },  
                Children = { leftIcon, inputBox, hintBlock, selecteditem, rightIconBorder }  
            };  
    
            border = new Border() {  
                Padding = new Thickness(5),  
                CornerRadius = new CornerRadius(5),  
                BorderBrush = brush,  
                BorderThickness = new Thickness(1),  
                Child = container  
            };  
            AddVisualChild(border);  
    
            initializePopup();  
            initializeAnimations();  
            Loaded += (s,e) => rightIcon.RenderTransform = new RotateTransform(0, rightIcon.ActualWidth / 2, rightIcon.ActualHeight / 2);   
            IsEnabledChanged += makeGray;  
        }  
    
        void makeGray(object sender, DependencyPropertyChangedEventArgs e) {  
            brushAnim.To = IsEnabled ? Colors.SkyBlue : Colors.LightGray;  
            brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);  
        }  
    
        void initializeControls() {  
            translateHint = new TranslateTransform();  
            scaleHint = new ScaleTransform();  
    
            hintBlock = new TextBlock {  
                Padding = new Thickness(5, 0, 5, 0),  
                Foreground = Brushes.Gray,  
                Background = Brushes.White,  
                HorizontalAlignment = HorizontalAlignment.Left,  
                VerticalAlignment = VerticalAlignment.Center,  
                RenderTransform = new TransformGroup {  
                    Children = { scaleHint, translateHint }  
                }  
            };  
            inputBox = new TextBox() {  
                Margin = new Thickness(5),  
                BorderThickness = new Thickness(0)  
            };  
            leftIcon = new Path {  
                Fill = brush,  
                Width = 24,  
                Height = 24,  
                Stretch = Stretch.Uniform,  
                HorizontalAlignment = HorizontalAlignment.Right,  
                Visibility = Visibility.Collapsed  
            };  
            rightIcon = new Path() {  
                Fill = brush,  
                Data = Geometry.Parse(iconData),  
                Width = 24,  
                Height = 24,  
                Stretch = Stretch.Uniform,  
                VerticalAlignment = VerticalAlignment.Center  
            };  
            rightIconBorder = new Border() {  
                Background = Brushes.Transparent,  
                Child = rightIcon  
            };  
            Grid.SetColumn(rightIconBorder, 2);  
            Grid.SetColumn(hintBlock, 1);  
            Grid.SetColumn(inputBox, 1);  
             
            rightIconBorder.MouseUp += showPopup;  
            inputBox.TextChanged += refreshList;  
            inputBox.KeyUp += focusList;  
        }  
    
        void initializeSelectedItemControls() {  
            selectedContent = new ContentControl() {  
                VerticalAlignment = VerticalAlignment.Center,  
                Margin = new Thickness(0, 0, 5, 0),  
                FocusVisualStyle = null  
            };  
            closeIcon = new Path() {  
                Fill = Brushes.Coral,  
                Width = 16,  
                Height = 16,  
                Data = Geometry.Parse(closeData),  
                Stretch = Stretch.Uniform,  
    
            };  
            var closeIconBorder = new Border() {  
                Background = Brushes.Transparent,  
                Child = closeIcon  
            };  
            Grid.SetColumn(closeIconBorder, 1);  
            selecteditem = new Border() {  
                Background = Brushes.LightGray,  
                Margin = new Thickness(5, 0, 5, 0),  
                CornerRadius = new CornerRadius(5),  
                Padding = new Thickness(5, 0, 5, 0),  
                Visibility = Visibility.Hidden,  
                Child = new Grid() {  
                    ColumnDefinitions = {  
                        new ColumnDefinition(),  
                        new ColumnDefinition(){Width = GridLength.Auto}  
                    },  
                    Children = { selectedContent, closeIconBorder }  
                }  
            };  
            Grid.SetColumn(selecteditem, 1);  
            closeIconBorder.MouseLeftButtonUp += removeSelected;  
        }  
    
        void initializePopup() {  
            list = new ListBox() {  
                IsSynchronizedWithCurrentItem = true,  
                HorizontalContentAlignment = HorizontalAlignment.Stretch  
            };  
            pop = new Popup() {  
                AllowsTransparency = true,  
                Child = list,  
                MaxHeight = 200,  
                StaysOpen = false,  
                Placement = PlacementMode.Bottom,  
                PlacementTarget = border  
            };  
            pop.Opened += animateRightIcon;  
            pop.Closed += animateRightIcon;  
            list.KeyUp += setSelected;  
        }  
    
        void removeSelected(object sender, MouseButtonEventArgs e) {  
            selecteditem.Visibility = Visibility.Hidden;  
            inputBox.Text = string.Empty;  
            inputBox.IsEnabled = true;  
            Selectedvalue = null;  
            if (IsRequired) hintBlock.Inlines.Add(requiredText);  
        }  
    
        void setSelected(object sender, KeyEventArgs e) {  
            if (e.Key == Key.Enter) {  
                inputBox.IsEnabled = false;  
                selectedContent.Content = list.SelectedItem;  
                Selectedvalue =  (int?)list.SelectedItem.GetType().GetProperty(SelectedvaluePath).GetValue(list.SelectedItem);  
                selecteditem.Visibility = Visibility.Visible;  
                pop.IsOpen = false;  
                if (IsRequired) hintBlock.Inlines.Remove(requiredText);  
                Keyboard.Focus(selectedContent);  
            }  
        }  
    
        void focusList(object sender, KeyEventArgs e) {  
            if (e.Key == Key.Down) {  
                if (pop.IsOpen)  
                    (list.ItemContainerGenerator.ContainerFromItem(list.SelectedItem) as ListBoxItem).Focus();  
            }  
        }  
    
        void refreshList(object sender, TextChangedEventArgs e) {  
            view.Refresh();  
            pop.IsOpen = true;  
        }  
    
        bool filterList(object o) => o.GetType().GetProperty(SearchPath).GetValue(o).ToString().Contains(inputBox.Text);  
    
        void showPopup(object sender, MouseButtonEventArgs e) => pop.IsOpen = !pop.IsOpen;  
    
        void initializeAnimations() {  
            var duration = TimeSpan.FromMilliseconds(250);  
            var ease = new CubicEase { EasingMode = EasingMode.EaseInOut };  
            scaleHintAnim = new DoubleAnimation {  
                Duration = duration,  
                EasingFunction = ease  
            };  
            translateHintAnim = new DoubleAnimation {  
                Duration = duration,  
                EasingFunction = ease  
            };  
            brushAnim = new ColorAnimation {  
                Duration = duration,  
                EasingFunction = ease  
            };  
            rightIconAnim = new DoubleAnimation() {  
                Duration = duration,  
                EasingFunction = ease  
            };  
        }  
        void animateHint() {  
            translateHint.BeginAnimation(TranslateTransform.YProperty, translateHintAnim);  
            scaleHint.BeginAnimation(ScaleTransform.ScaleYProperty, scaleHintAnim);  
            scaleHint.BeginAnimation(ScaleTransform.ScaleXProperty, scaleHintAnim);  
        }  
        void animateRightIcon(object sender, EventArgs e) {  
            rightIconAnim.To = pop.IsOpen ? 180 : 0;  
            rightIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, rightIconAnim);  
        }  
        protected override void OnMouseEnter(MouseEventArgs e) {  
            if (string.IsNullOrWhiteSpace(inputBox.Text)) {  
                inputBox.Focus();  
                brushAnim.To = Colors.CornflowerBlue;  
                brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);  
            }  
        }  
        protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) {  
            if (Selectedvalue == null && !isHintMoved) {  
                isHintMoved = true;  
                scaleHintAnim.To = 0.9;  
                translateHintAnim.To = -20;  
                animateHint();  
            }  
            brushAnim.To = Colors.CornflowerBlue;  
            brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);  
        }  
        protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e) {  
            if (Selectedvalue == null && inputBox.Text.Length == 0 && !pop.IsOpen && isHintMoved) {  
                isHintMoved = false;  
                scaleHintAnim.To = 1;  
                translateHintAnim.To = 0;  
                animateHint();  
            }  
            if (!pop.IsOpen) {  
                brushAnim.To = Colors.SkyBlue;  
                brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);  
            }  
        }  
        protected override Size MeasureOverride(Size availableSize) {  
            border.Width = availableSize.Width;  
            pop.Width = availableSize.Width;  
            border.Measure(availableSize);  
            return border.DesiredSize;  
        }  
        protected override Size ArrangeOverride(Size finalSize) {  
            border.Arrange(new Rect(border.DesiredSize));  
            return finalSize;  
        }  
        protected override Visual GetVisualChild(int index) => border;  
        protected override int VisualChildrenCount => 1;  
    
        #region DependencyProperty  
        public IEnumerable ItemsSource {  
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }  
            set { SetValue(ItemsSourceProperty, value); }  
        }  
    
        public int? Selectedvalue {  
            get { return (int?)GetValue(SelectedvalueProperty); }  
            set { SetValue(SelectedvalueProperty, value); }  
        }  
    
        public string SelectedvaluePath {  
            get { return (string)GetValue(SelectedvaluePathProperty); }  
            set { SetValue(SelectedvaluePathProperty, value); }  
        }  
    
    
        public string SearchPath {  
            get { return (string)GetValue(SearchPathProperty); }  
            set { SetValue(SearchPathProperty, value); }  
        }  
    
        public static readonly DependencyProperty SearchPathProperty =  
            DependencyProperty.Register("SearchPath", typeof(string), typeof(SelectItem), new PropertyMetadata(null));  
    
        public static readonly DependencyProperty SelectedvaluePathProperty =  
            DependencyProperty.Register("SelectedvaluePath", typeof(string), typeof(SelectItem), new PropertyMetadata(null));  
    
        public static readonly DependencyProperty SelectedvalueProperty =  
            DependencyProperty.Register("Selectedvalue", typeof(int?), typeof(SelectItem), new PropertyMetadata(null));  
    
        public static readonly DependencyProperty ItemsSourceProperty =  
            DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(SelectItem), new PropertyMetadata(null, onSourceChanged));  
    
        static void onSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {  
            var o = d as SelectItem;  
            o.view = CollectionViewSource.GetDefaultView(o.ItemsSource);  
            o.view.Filter = o.filterList;  
            o.list.ItemsSource = o.view;  
        }  
        #endregion  
    }  
    

    in MainWindow.xaml, I;ve used it this way:

    <cc:SelectItem Grid.Row="1"   
                    Icon="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"  
                    Hint="Choose an Item"  
                    SearchPath="Text"  
                    SelectedvaluePath="Index"  
                    IsRequired="True"  
                    ItemsSource="{Binding Source}">  
        <cc:SelectItem.ItemTemplate>  
            <DataTemplate>  
                <Grid>  
                    <Grid.ColumnDefinitions>  
                        <ColumnDefinition/>  
                        <ColumnDefinition Width="Auto"/>  
                    </Grid.ColumnDefinitions>  
                    <TextBlock Text="{Binding Text}"/>  
                    <TextBlock Grid.Column="1" Text="{Binding Index}"/>  
                </Grid>  
            </DataTemplate>  
        </cc:SelectItem.ItemTemplate>  
    </cc:SelectItem>  
    

    and in MainWindow.xaml.cs I've these for that:

    public partial class MainWindow : Window  
    {  
        public List<Model.ListItem> Source { get; set; }  
        public MainWindow()  
        {  
            InitializeComponent();  
            var rand = new Random();  
            Source = new List<Model.ListItem>();  
            for (int i = 0; i < 20; i++) {  
                Source.Add(new Model.ListItem() {  
                    Text = rand.Next(65,91) + "Test",  
                    Index = i  
                });  
            }  
            DataContext = this;  
        }  
    }  
    

    Here in the comment section you could see how it looks. To have image, set it in the DataTemplate and hopefully it'll be shown in the box. It still is complicated, isn't it? It doesn't have any support for two way binding of SelectedValue right now.