如何将文本框样式转换为可重用的控件模板?

时间:2018-06-12 05:39:31

标签: c# .net wpf

我希望我的文本框具有水印样式。 我有下面的代码https://stackoverflow.com/a/21672408/9928363

admin.py

我有很多文本框,我只想要一些使用这种风格但不是全部。 我该怎么做?

2 个答案:

答案 0 :(得分:1)

有很多方法可以在文本框中添加水印,包括替换TextBox的ControlTemplate(正如您的问题所暗示的那样)。但是,替换TextBox的ControlTemplate可能并不理想,因为在这样做时,您将负责绘制整个控件,包括边框,样式化不同的状态等。这不是太困难(您可以使用Visual Studio或Espression Blend来复制来自当前主题的模板),但除非你做了很多工作,否则你将失去WPF功能,使常用控件的样式适应当前的Windows主题。

如果您想要一个不需要更改控件模板的简单,可重复使用的纯XAML方法,那么使用VisualBrush声明样式资源是一种有效的方法。

见下文,我们有3个文本框,水印样式应用于其中两个。通过在文本框具有输入焦点时删除水印,此样式比示例更进一步。

<Window ...>
    <Window.Resources>
        <Style TargetType="TextBox" x:Key="Watermark">
            <Style.Resources>
                <VisualBrush x:Key="WatermarkBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
                    <VisualBrush.Visual>
                        <Label Content="Enter Search Term Here" Foreground="LightGray" />
                    </VisualBrush.Visual>
                </VisualBrush>
            </Style.Resources>
            <Style.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
                    <Setter Property="Background" Value="{StaticResource WatermarkBrush}" />
                </DataTrigger>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter Property="Background" Value="{x:Null}" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel>
        <TextBox Width="250" VerticalAlignment="Center" FontSize="20"
            HorizontalAlignment="Left" Style="{StaticResource Watermark}"/>
        <TextBox Width="250" VerticalAlignment="Center" 
            HorizontalAlignment="Left"/>
        <TextBox Width="250" VerticalAlignment="Center" 
            HorizontalAlignment="Left" Style="{StaticResource Watermark}"/>
    </StackPanel>
</Window>

如果您需要水印来对不同的字体大小做出反应,您可以使用Stretch属性: -

<VisualBrush x:Key="WatermarkBrush" AlignmentX="Left" AlignmentY="Center" Stretch="Uniform">
    <VisualBrush.Visual>
        <Label Padding="2 1" Content="Enter Search Term Here" Foreground="LightGray" />
    </VisualBrush.Visual>
</VisualBrush>

答案 1 :(得分:0)

如先前的回答中所述,有时最好不要替换标准控件的ControlTemplate。因此,这是一种通过附加属性创建水印的方法,该方法可以使现有控件模板保持完整。这样做的好处是您可以将水印设置为任何文本,包括在运行时通过数据绑定进行设置。

为演示起见,XAML将创建3个文本框(请参见下文)。底部文本框具有一个水印,其Text属性绑定到顶部文本框的Text属性,从而演示了水印文本的动态性质。在第二个文本框中键入数字将更改底部文本框的文本(和水印)的字体大小。

enter image description here

以上图片取自Windows 10标准主题。将Windows主题设置为“高对比度”后,这里再次出现。如您所见,已经尊重了现有的ControlTemplate(和配色方案)。

High Contrast


XAML

<Window x:Class="WpfApp2.MainWindow"
        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:behaviors="clr-namespace:WpfApp2.Behaviors"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="525">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Label Content="Watermark" Grid.Row="0"/>
        <TextBox Grid.Row="0" Grid.Column="1" Margin="5"
            x:Name="watermark" Width="250" HorizontalAlignment="Left" Text="enter search term here"/>
        <Label Content="Font Size" Grid.Row="1"/>
        <TextBox Grid.Row="1" Grid.Column="1" Margin="5"
            x:Name="fontSize" Width="250"  VerticalAlignment="Center" HorizontalAlignment="Left" 
            behaviors:TextBoxExtensions.Watermark="Enter a font size"/>
        <Label Content="Result" Grid.Row="2"/>
        <TextBox Grid.Row="2" Grid.Column="2" Margin="5"
            Width="200"  VerticalAlignment="Center" HorizontalAlignment="Left" 
            FontSize="{Binding Text, ElementName=fontSize}"
            behaviors:TextBoxExtensions.Watermark="{Binding Path=Text, ElementName=watermark}" />
    </Grid>
</Window>

TextBoxExtensions.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace WpfApp2.Behaviors
{
    public class TextBoxExtensions
    {
        public static readonly DependencyProperty WatermarkProperty = DependencyProperty.RegisterAttached(
            "Watermark",
            typeof(string),
            typeof(TextBoxExtensions),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, OnWatermarkTextChanged)
        );

        private static void OnWatermarkTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tb = d as TextBox;

            if (tb != null)
            {
                var textChangedHandler = new TextChangedEventHandler((s, ea) => ShowOrHideWatermark(s as TextBox));
                var focusChangedHandler = new DependencyPropertyChangedEventHandler((s, ea) => ShowOrHideWatermark(s as TextBox));
                var sizeChangedHandler = new SizeChangedEventHandler((s, ea) => ShowOrHideWatermark(s as TextBox));

                if (string.IsNullOrEmpty(e.OldValue as string))
                {
                    tb.TextChanged += textChangedHandler;
                    tb.IsKeyboardFocusedChanged += focusChangedHandler;
                    // We need SizeChanged events because the Background brush is sized according to the control size
                    tb.SizeChanged += sizeChangedHandler;
                }

                if (string.IsNullOrEmpty(e.NewValue as string))
                {
                    tb.TextChanged -= textChangedHandler;
                    tb.IsKeyboardFocusedChanged -= focusChangedHandler;
                    tb.SizeChanged -= sizeChangedHandler;
                }

                ShowOrHideWatermark(tb);
            }
        }

        public static string GetWatermark(DependencyObject element)
        {
            return (string)element.GetValue(WatermarkProperty);
        }

        public static void SetWatermark(DependencyObject element, string value)
        {
            element.SetValue(WatermarkProperty, value);
        }

        private static void ShowOrHideWatermark(TextBox tb)
        {
            // Restore TextBox background to style/theme value
            tb.ClearValue(TextBox.BackgroundProperty);
            if (string.IsNullOrEmpty(tb.Text) && !tb.IsKeyboardFocused)
            {
                var wm = GetWatermark(tb);
                if (!string.IsNullOrEmpty(wm))
                {
                    tb.Background = CreateTextBrush(wm, tb);
                }
            }
        }

        private static Brush CreateTextBrush(string text, TextBox tb)
        {
            Grid g = new Grid
            {
                Background = tb.Background,
                Width = tb.ActualWidth,
                Height = tb.ActualHeight
            };

            g.Children.Add(new Label
            {
                Padding = new Thickness(2,1,1,1),
                FontSize = tb.FontSize,
                FontFamily = tb.FontFamily,
                Foreground = Brushes.LightGray,
                Content = text
            });

            VisualBrush vb = new VisualBrush
            {
                Visual = g,
                Stretch = Stretch.None,
                AlignmentX = AlignmentX.Left,
                AlignmentY = AlignmentY.Center,
            };

            return vb;
        }
    }
}

任何一种水印机制都不适合每种情况。此特定解决方案的优点是保留了默认主题,将使用TextBox的字体大小和系列,并且将通过样式或主题遵守TextBox控件上设置的背景颜色。但是,如果尝试直接在XAML中设置TextBox的Background属性,它将无法正常工作,因为该值将被水印清除。