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,793
  • 3
  • 25
  • 52
  • 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

3 Answers3

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,098
  • 11
  • 18
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
  • 21
  • 8
Gonzalo Méndez
  • 538
  • 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
  • 3,858
  • 4
  • 43
  • 82