3

I currently have a combobox bound to a dictionary. What I'm trying to do is have a default item in the combobox, such as "Please select an item...", which disappears when the user actually clicks on the combobox to select an item.

I've seen similar questions on this site, but can't seem to get any of the solutions to work properly for me. The only luck I've had is by putting this in my xaml combobox code:

<IsEditable="True" IsReadOnly="True" Text="Please select an item..."/>

But this of course changes the look of the combobox and I don't want it look editable.

My code behind:

private Dictionary<string, string> imageTypes = new Dictionary<string, string>();

public MainWindow()
{
    InitializeComponent();
    AddImage_Types();
}

public void AddImage_Types()
{
    imageTypes.Add("*.png", Png);
    imageTypes.Add("*.jpg *.jpeg *jfif", Jpg);
}

public Dictionary<string, string> ImageTypes
{
    get
    {
        return imageTypes;
    }
}

And here's my xaml for the combobox:

<ComboBox Name="imageCB"
          ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}, Path=ImageTypes}"
          SelectedValuePath="Value"
          DisplayMemberPath="Key" >
</ComboBox>

I've tried using triggers and styles as well like this answer: https://stackoverflow.com/a/16782339/2480598

I'm sure it's something simple, but I can't seem to get it.

Note: By default item, I mean that when the window is loaded, some text is already displayed in the combo box like "Please select an item...". This will go away when the user clicks the combo box to select an item from the dropdown.

Community
  • 1
  • 1
pfinferno
  • 1,779
  • 3
  • 34
  • 62
  • What do you mean by default Item? random item? – Amine May 13 '16 at 14:01
  • And if your ComboBox is not editable, why you set IsEditable to true? – Amine May 13 '16 at 14:03
  • By default I mean, when the window is loaded, the combobox has something like "Please select an item" displayed in it. Then when the user actually clicks it and sees the other items in the dropdown, that default message goes away. I only set the IsEditable because I got my needed functionality by doing that, but it's not how I want to do it. – pfinferno May 13 '16 at 14:05

4 Answers4

11

Look at this example for TextBox. you can do the same thing for ComboBox:

Watermark Behavior

1- Add a reference to the assembly System.Windows.Interactivity

2- Declare this in your xaml

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

3- Add this class to your Project

public class WatermarkBehavior : Behavior<ComboBox>
{
    private WaterMarkAdorner adorner;

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Text.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(WatermarkBehavior), new PropertyMetadata("Watermark"));


    public double FontSize
    {
        get { return (double)GetValue(FontSizeProperty); }
        set { SetValue(FontSizeProperty, value); }
    }

    // Using a DependencyProperty as the backing store for FontSize.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FontSizeProperty =
        DependencyProperty.Register("FontSize", typeof(double), typeof(WatermarkBehavior), new PropertyMetadata(12.0));


    public Brush Foreground
    {
        get { return (Brush)GetValue(ForegroundProperty); }
        set { SetValue(ForegroundProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Foreground.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ForegroundProperty =
        DependencyProperty.Register("Foreground", typeof(Brush), typeof(WatermarkBehavior), new PropertyMetadata(Brushes.Black));



    public string FontFamily
    {
        get { return (string)GetValue(FontFamilyProperty); }
        set { SetValue(FontFamilyProperty, value); }
    }

    // Using a DependencyProperty as the backing store for FontFamily.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FontFamilyProperty =
        DependencyProperty.Register("FontFamily", typeof(string), typeof(WatermarkBehavior), new PropertyMetadata("Segoe UI"));



    protected override void OnAttached()
    {
        adorner = new WaterMarkAdorner(this.AssociatedObject, this.Text, this.FontSize, this.FontFamily, this.Foreground);

        this.AssociatedObject.Loaded += this.OnLoaded;
        this.AssociatedObject.GotFocus += this.OnFocus;
        this.AssociatedObject.LostFocus += this.OnLostFocus;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        if (!this.AssociatedObject.IsFocused)
        {
            if (String.IsNullOrEmpty(this.AssociatedObject.Text))
            {
                var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);
                layer.Add(adorner);
            }
        }
    }

    private void OnLostFocus(object sender, RoutedEventArgs e)
    {
        if (String.IsNullOrEmpty(this.AssociatedObject.Text))
        {
            try
            {
                var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);
                layer.Add(adorner);
            }
            catch { }
        }
    }

    private void OnFocus(object sender, RoutedEventArgs e)
    {
        var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);
        layer.Remove(adorner);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
    }

    public class WaterMarkAdorner : Adorner
    {
        private string text;
        private double fontSize;
        private string fontFamily;
        private Brush foreground;

        public WaterMarkAdorner(UIElement element, string text, double fontsize, string font, Brush foreground)
            : base(element)
        {
            this.IsHitTestVisible = false;
            this.Opacity = 0.6;
            this.text = text;
            this.fontSize = fontsize;
            this.fontFamily = font;
            this.foreground = foreground;
        }

        protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            var text = new FormattedText(
                    this.text,
                    System.Globalization.CultureInfo.CurrentCulture,
                    System.Windows.FlowDirection.LeftToRight,
                    new System.Windows.Media.Typeface(fontFamily),
                    fontSize,
                    foreground);

            drawingContext.DrawText(text, new Point(3, 3));
        }
    }
}

4- And add your ComboBox:

<ComboBox Width="200" IsEditable="True" IsReadOnly="True">
    <i:Interaction.Behaviors>
        <local:WatermarkBehavior Text="Please select..." />
    </i:Interaction.Behaviors>
    <ComboBoxItem>Item1</ComboBoxItem>
    <ComboBoxItem>Item2</ComboBoxItem>
    <ComboBoxItem>Item3</ComboBoxItem>
</ComboBox>
Amine
  • 1,198
  • 11
  • 19
3

Ok, this is my version, almost identical to the one provided by Anime. Some changes I did: Added a Margin a dependency property, to be able to position the watermark where I needed it to be. Fully qualified some objects, because my VS was getting confused, and replaced a deprecated method with another override. I also changed the OnFocus event handler, so the text actually disappears when the control gets the focus. Might not be generic, but works for my scenarios.

Here it goes:

public class WatermarkBehavior : Behavior<ComboBox>
{
    private WaterMarkAdorner adorner;

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }


    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(WatermarkBehavior), new PropertyMetadata("Watermark"));


    public double FontSize
    {
        get { return (double)GetValue(FontSizeProperty); }
        set { SetValue(FontSizeProperty, value); }
    }


    public static readonly DependencyProperty FontSizeProperty =
        DependencyProperty.Register("FontSize", typeof(double), typeof(WatermarkBehavior), new PropertyMetadata(12.0));


    public System.Windows.Media.Brush Foreground
    {
        get { return (System.Windows.Media.Brush)GetValue(ForegroundProperty); }
        set { SetValue(ForegroundProperty, value); }
    }


    public static readonly DependencyProperty ForegroundProperty =
        DependencyProperty.Register("Foreground", typeof(System.Windows.Media.Brush), typeof(WatermarkBehavior), new PropertyMetadata(System.Windows.Media.Brushes.Black));

    
    public string FontFamily
    {
        get { return (string)GetValue(FontFamilyProperty); }
        set { SetValue(FontFamilyProperty, value); }
    }

 
    public static readonly DependencyProperty FontFamilyProperty =
        DependencyProperty.Register("FontFamily", typeof(string), typeof(WatermarkBehavior), new PropertyMetadata("Segoe UI"));


    public Thickness Margin
    {
        get { return (Thickness)GetValue(MarginProperty); }
        set { SetValue(MarginProperty, value); }
    }

    public static readonly DependencyProperty MarginProperty =
        DependencyProperty.Register("Margin", typeof(Thickness), typeof(WatermarkBehavior));


    protected override void OnAttached()
    {
        adorner = new WaterMarkAdorner(this.AssociatedObject, this.Text, this.FontSize, this.FontFamily, this.Margin, this.Foreground);

        this.AssociatedObject.Loaded += this.OnLoaded;
        this.AssociatedObject.GotFocus += this.OnFocus;
        this.AssociatedObject.LostFocus += this.OnLostFocus;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        if (!this.AssociatedObject.IsFocused)
        {
            if (String.IsNullOrEmpty(this.AssociatedObject.Text))
            {
                var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);
                layer.Add(adorner);
            }
        }
    }

    private void OnLostFocus(object sender, RoutedEventArgs e)
    {
        if (String.IsNullOrEmpty(this.AssociatedObject.Text))
        {
            try
            {
                var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);
                var adorners = layer.GetAdorners(AssociatedObject);
                if (adornerOflayer != null)
                {
                    foreach (var adorner in adorners)
                        layer.Remove(adorner);
                }
                layer.Add(adorner);
            }
            catch { }
        }
    }

    private void OnFocus(object sender, RoutedEventArgs e)
    {
        if (AssociatedObject.SelectedItem != null)
        {
            var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);
            layer.Remove(adorner);
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
    }

    public class WaterMarkAdorner : Adorner
    {
        private string text;
        private double fontSize;
        private string fontFamily;
        private System.Windows.Media.Brush foreground;

        public WaterMarkAdorner(UIElement element, string text, double fontsize, string font, Thickness margin, System.Windows.Media.Brush foreground)
            : base(element)
        {
            this.IsHitTestVisible = false;
            this.Opacity = 0.6;
            this.text = text;
            this.fontSize = fontsize;
            this.fontFamily = font;
            this.foreground = foreground;
            this.Margin = margin;
        }

        protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            Matrix m = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformToDevice;
            
            var text = new FormattedText(
                    this.text,
                    System.Globalization.CultureInfo.CurrentCulture,
                    System.Windows.FlowDirection.LeftToRight,
                    new Typeface(fontFamily),
                    fontSize,
                    foreground, m.M11);
            
            drawingContext.DrawText(text, new System.Windows.Point(3, 3));
        }
    }
}
TT7
  • 31
  • 10
Gonzalo Méndez
  • 548
  • 7
  • 18
1

The way you want to do this isn't really how WPF works. The best way to do this would be to use data validation / bindings, so user can't move on until something is selected; or a validation error is thrown (red lines around the combobox) if the user doesn't select something... or even just give a default value there in the first place.

But, if you want it to work the way you're asking, you could:

a) have the "Please select an item..." in your dictionary, then have an event handler remove it when the user selects something else

b) have the "Please select an item..." as the only item in your bound dictionary, then have an event handler change the binding when the user opens the combo box

c) put a label ontop of the combo box with a transparent background that, when clicked on, disappears

d) (untested), how about adjusting the code from this link?:

<ComboBox IsTextSearchEnabled="True" StaysOpenOnEdit="True" 
          Width="150" Height="24"  IsReadOnly="False" IsEditable="True">
  <ComboBox.Resources>
    <VisualBrush x:Key="HelpBrush" TileMode="None" Opacity="0.4" Stretch="None" AlignmentX="Left">
      <VisualBrush.Visual>
        <TextBlock FontStyle="Italic" Text="Type or select from list"/>
      </VisualBrush.Visual>
    </VisualBrush>
  </ComboBox.Resources>
  <ComboBox.Style>
    <Style TargetType="ComboBox">
      <Style.Triggers>
        <Trigger Property="Text" Value="{x:Null}">
          <Setter Property="Background" Value="{StaticResource HelpBrush}"/>
        </Trigger>
        <Trigger Property="Text" Value="">
          <Setter Property="Background" Value="{StaticResource HelpBrush}"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </ComboBox.Style>
  <ComboBoxItem>First item</ComboBoxItem>
  <ComboBoxItem>Another item</ComboBoxItem>
</ComboBox>
simonalexander2005
  • 4,338
  • 4
  • 48
  • 92
0

I "combed" the Gonzalez Méndez code once more. Unfortunately, TT7 made an error: the adornerOflayer variable does not exist. In addition, I replaced the comparison (String.IsNullOrEmpty(this.AssociatedObject.Text)) to (AssociatedObject.SelectedItem == null) and added processing of the OnSelectionChanged event, otherwise the WaterMark remained visible when the item was selected, which led to text overlap. As a result, it works without problems

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using Microsoft.Xaml.Behaviors;
// ReSharper disable IdentifierTypo

namespace mdmWPFControlLibrary.Common
{
    /// <summary>
    ///     https://stackoverflow.com/a/51450592/4884157
    /// </summary>
    public class WatermarkBehavior : Behavior<ComboBox>
    {
        #region Private Members

        private WaterMarkAdorner _adorner;

        private ComboBox ComboBox => AssociatedObject;

        private AdornerLayer Layer => AdornerLayer.GetAdornerLayer(ComboBox);

        private void SetAdorner()
        {
            if (ComboBox.SelectedItem == null)
            {
                try
                {
                    var adorners = Layer.GetAdorners(ComboBox);
                    if (adorners != null)
                        foreach (var adorner in adorners)
                            Layer.Remove(adorner);
                    Layer.Add(_adorner);
                }
                catch
                {
                    //
                }
            }
            else
            {
                Layer?.Remove(_adorner);
            }
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            if (!ComboBox.IsFocused)
            {
                if (ComboBox.SelectedItem == null)
                {
                    try
                    {
                        Layer?.Add(_adorner);
                    }
                    catch 
                    {
                        //
                    }
                }
            }
        }

        private void OnFocus(object sender, RoutedEventArgs e)
        {
            if (ComboBox.SelectedItem != null)
            {
                Layer?.Remove(_adorner);
            }
        }

        private void OnLostFocus(object sender, RoutedEventArgs e)
        {
            SetAdorner();
        }

        private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            SetAdorner();
        }

        #endregion

        #region Protected Members

        protected override void OnAttached()
        {
            _adorner = new WaterMarkAdorner(ComboBox, Text, FontSize, FontFamily, Margin, Foreground);

            ComboBox.Loaded += OnLoaded;
            ComboBox.GotFocus += OnFocus;
            ComboBox.LostFocus += OnLostFocus;
            ComboBox.SelectionChanged += OnSelectionChanged;
        }

        #endregion

        #region Public Members

        public string Text
        {
            get => (string)GetValue(TextProperty);
            set => SetValue(TextProperty, value);
        }

        public double FontSize
        {
            get => (double)GetValue(FontSizeProperty);
            set => SetValue(FontSizeProperty, value);
        }

        public Brush Foreground
        {
            get => (Brush)GetValue(ForegroundProperty);
            set => SetValue(ForegroundProperty, value);
        }


        public string FontFamily
        {
            get => (string)GetValue(FontFamilyProperty);
            set => SetValue(FontFamilyProperty, value);
        }

        public Thickness Margin
        {
            get => (Thickness)GetValue(MarginProperty);
            set => SetValue(MarginProperty, value);
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(WatermarkBehavior), new PropertyMetadata("Watermark"));


        public static readonly DependencyProperty FontSizeProperty =
            DependencyProperty.Register("FontSize", typeof(double), typeof(WatermarkBehavior), new PropertyMetadata(12.0));


        public static readonly DependencyProperty ForegroundProperty =
            DependencyProperty.Register("Foreground", typeof(Brush), typeof(WatermarkBehavior), new PropertyMetadata(Brushes.Black));


        public static readonly DependencyProperty FontFamilyProperty =
            DependencyProperty.Register("FontFamily", typeof(string), typeof(WatermarkBehavior), new PropertyMetadata("Segoe UI"));


        public static readonly DependencyProperty MarginProperty =
            DependencyProperty.Register("Margin", typeof(Thickness), typeof(WatermarkBehavior));

        public class WaterMarkAdorner : Adorner
        {
            #region Private Members

            private readonly string _text;
            private readonly double _fontSize;
            private readonly string _fontFamily;
            private readonly Brush _foreground;

            #endregion

            #region Protected Members

            protected override void OnRender(DrawingContext drawingContext)
            {
                base.OnRender(drawingContext);
                var mainWindow = Application.Current.MainWindow;
                if (mainWindow == null)
                    return;
                // ReSharper disable once InconsistentNaming
                Matrix? _matrix = PresentationSource.FromVisual(mainWindow)?.CompositionTarget?.TransformToDevice;
                if (_matrix == null)
                    return;
                Matrix matrix = (Matrix) _matrix;


                var text = new FormattedText(
                    _text,
                    System.Globalization.CultureInfo.CurrentCulture,
                    FlowDirection.LeftToRight,
                    new Typeface(_fontFamily),
                    _fontSize,
                    _foreground, matrix.M11);

                drawingContext.DrawText(text, new Point(3, 3));
            }

            #endregion

            #region Public Members

            public WaterMarkAdorner(UIElement element, string text, double fontSize, string font, Thickness margin, Brush foreground)
                : base(element)
            {
                IsHitTestVisible = false;
                Opacity = 0.6;
                _text = text;
                _fontSize = fontSize;
                _fontFamily = font;
                _foreground = foreground;
                Margin = margin;
            }

            #endregion
        }

        #endregion
    }
}
Alexander
  • 11
  • 2