wpf ComboBox 的水印行为

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/37211869/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:48:45  来源:igfitidea点击:

Watermark Behavior for ComboBox

c#wpfcombobox

提问by pfinferno

I currently have a comboboxbound 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 comboboxto select an item.

我目前combobox绑定到dictionary. 我想要做的是在 中有一个默认项目combobox,例如“请选择一个项目...”,当用户实际单击combobox以选择一个项目时,它就会消失。

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:

我在这个网站上看到过类似的问题,但似乎无法找到任何适合我的解决方案。我唯一的运气是把它放在我的 xaml 组合框代码中:

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

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

但这当然会改变 的外观,combobox我不希望它看起来可编辑。

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:

这是我用于组合框的 xaml:

<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

我试过使用触发器和样式以及这个答案: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.

注意:默认项目,我的意思是当窗口加载时,一些文本已经显示在组合框中,比如“请选择一个项目...”。当用户单击组合框从下拉列表中选择一个项目时,这将消失。

采纳答案by simonalexander2005

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.

您想要执行此操作的方式并不是 WPF 的真正工作方式。最好的方法是使用数据验证/绑定,因此用户在选择某些内容之前无法继续;或者,如果用户没有选择某些内容,则会抛出验证错误(组合框周围的红线)……或者甚至只是首先在那里给出默认值。

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

a) 在您的字典中添加“请选择一个项目...”,然后在用户选择其他内容时让事件处理程序将其删除

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

b) 将“请选择一个项目...”作为绑定字典中的唯一项目,然后在用户打开组合框时让事件处理程序更改绑定

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

c) 在组合框的顶部放置一个带有透明背景的标签,点击后会消失

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

d)(未经测试),如何调整此链接中的代码?:

<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>

回答by Amine

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

看看这个 TextBox 的例子。你可以对 ComboBox 做同样的事情:

Watermark Behavior

水印行为

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

1- 添加对程序集 System.Windows.Interactivity 的引用

2- Declare this in your xaml

2- 在你的 xaml 中声明

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

3- Add this class to your Project

3- 将此类添加到您的项目中

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:

4- 并添加您的 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>

回答by Gonzalo Méndez

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.

好的,这是我的版本,几乎与动漫提供的版本相同。我做了一些更改:添加了一个 Margin 依赖属性,以便能够将水印定位在我需要的位置。完全限定某些对象,因为我的 VS 感到困惑,并用另一个覆盖替换了已弃用的方法。我还更改了 OnFocus 事件处理程序,因此当控件获得焦点时,文本实际上会消失。可能不是通用的,但适用于我的场景。

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);
                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));
        }
    }
}