显示提示文本的WPF TextBox样式-将ToolTip或Tag属性绑定到样式资源

xdnvmnnf  于 2023-05-30  发布在  其他
关注(0)|答案(1)|浏览(456)

我在这篇文章How can I add a hint text to WPF textbox?中发现了一个内联样式,我喜欢在文本框的背景中添加提示文本。我用它在Text 1节中创建了一个带有控件模板的样式<Window.Resources>:

<Window 
    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"
    Name="Window"  Title="MainWindow" Height="120" Width="1000">                                
<Window.Resources>
    <Style x:Key="Text1" TargetType="{x:Type TextBox}" >
      <Setter Property="SnapsToDevicePixels" Value="True"/>
      <Setter Property="OverridesDefaultStyle" Value="True"/>
      <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
      <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
      <Setter Property="AllowDrop" Value="true"/>
      <Setter Property="BorderThickness" Value="2,2,2,2" />
      <Setter Property="FontSize" Value="16" />
      <Setter Property="Margin" Value="1" />
      <Setter Property="Template">
        <Setter.Value>
         <ControlTemplate TargetType="{x:Type TextBox}" xmlns:sys="clr-namespace:System;assembly=mscorlib" >
            <ControlTemplate.Resources>
                <SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />
                <SolidColorBrush x:Key="DisabledBackgroundBrush" Color="#EEE" />
                <SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />
                <SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF" />
                <SolidColorBrush x:Key="Whity" Color="White" />
                <VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
                    <VisualBrush.Visual>
                        <Label Content="This is a hint text" Foreground="LightGray" />
                    </VisualBrush.Visual>
                </VisualBrush>
                <Storyboard x:Key="SrcWrongInput" TargetName="Src" >     
                    <ColorAnimation Storyboard.TargetProperty="(BorderBrush).(SolidColorBrush.Color)" To="Red" AutoReverse="True" RepeatBehavior="4x" Duration="0:0:0:0.3"/>        
                </Storyboard>
            </ControlTemplate.Resources> 
            <Border Name="Border"  CornerRadius="2"  Padding="2"  Background="{StaticResource WindowBackgroundBrush}"  BorderBrush="{StaticResource SolidBorderBrush}" BorderThickness="1" >
              <ScrollViewer Margin="0" x:Name="PART_ContentHost"/>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="Text" Value="{x:Static sys:String.Empty}">
                    <Setter TargetName="Border" Property="Background" Value="{StaticResource CueBannerBrush}"/>
                </Trigger>
                <Trigger Property="Text" Value="{x:Null}">
                    <Setter TargetName="Border" Property="Background" Value="{StaticResource CueBannerBrush}"/>
                </Trigger>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="{StaticResource Whity}"/>
                </Trigger>
            </ControlTemplate.Triggers> 
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
</Window.Resources>
<Grid>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="7*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="5*" />
            <RowDefinition Height="5*" />
        </Grid.RowDefinitions>
        <TextBox   Name="Src"  Grid.Column="0" Grid.Row="0" Style="{StaticResource Text1}" ToolTip="Enter path to exe or folder C:\Program Files\..." VerticalContentAlignment="Center" />
        <TextBox   Name="Name" Grid.Column="1" Grid.Row="0" Style="{StaticResource Text1}" ToolTip="Enter name" VerticalContentAlignment="Center"/>
        <TextBox   Name="Rslt" Grid.Column="0" Grid.Row="1" Style="{StaticResource Text1}" ToolTip="Result of procedure" VerticalContentAlignment="Center">
            <TextBox.Resources>
                <Storyboard x:Key="RsltWrongInput" TargetName="Rslt" >   
                    <ColorAnimation Storyboard.TargetProperty="(BorderBrush).(SolidColorBrush.Color)" To="Red" AutoReverse="True" RepeatBehavior="4x" Duration="0:0:0:0.3"/>        
                </Storyboard>   
            </TextBox.Resources>
        </TextBox>
        <Button    Name="Do"   Grid.Column="2" Grid.Row="0" />
    </Grid>
</Grid>

它加载了这个ps1脚本:

Add-Type -AssemblyName presentationframework
[xml]$XAML = Get-Content "MainWindowStack.xaml"
$reader=(New-Object System.Xml.XmlNodeReader $xaml) 
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader"; exit}
$xaml.SelectNodes("//*[@Name]") | ForEach-Object {Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)}
$DataContextRslt = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$do.Add_Click({
    $rsltWrongInputStory = $Rslt.Resources["RsltWrongInput"]
    $rsltWrongInputStory.begin()
})
$Form.ShowDialog() | out-null

它加载并显示提示文本,一旦文本不为空或文本框获得键盘焦点,提示文本就会消失,这正是我想要的行为。但是我无法将VisualBrush x:Key=“CueBannerBrush”中的标签内容绑定到文本框的另一个属性(例如,标记或工具提示)或DataContext以使提示文本可调整。我尝试了各种不同的Binding / TemplateBinding表达式,使用relativeSource,祖先,没有结果。
我还看了其他关于这个主题的文章:在Parameterized style to display hint text in WPF TextBox [duplicate]中,有一个关于如何以及为什么这不能工作的评论,后面是这个链接将占位符文本添加到文本框中,其中有一个不同方法的示例:

<Style x:Key="placeHolder" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <Grid>
                    <TextBox Text="{Binding Path=Text,
                                            RelativeSource={RelativeSource TemplatedParent}, 
                                            Mode=TwoWay,
                                            UpdateSourceTrigger=PropertyChanged}"
                             x:Name="textSource" 
                             Background="Transparent" 
                             Panel.ZIndex="2" />
                    <TextBox Text="{TemplateBinding Tag}" Background="{TemplateBinding Background}" Panel.ZIndex="1">
                        <TextBox.Style>
                            <Style TargetType="{x:Type TextBox}">
                                <Setter Property="Foreground" Value="Transparent"/>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding Path=Text, Source={x:Reference textSource}}" Value="">
                                        <Setter Property="Foreground" Value="LightGray"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </TextBox.Style>
                    </TextBox>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

它确实有效,使用这种风格,我可以使用文本框的Tag属性设置提示文本,但它有一个限制,即它不像第一个那样对键盘焦点做出React,提示文本只有在输入字母后才会消失。
我尝试使用此触发器添加该行为失败:

<DataTrigger Binding="{Binding Path=IsKeyboardFocused, Source={x:Reference textSource}}" Value="">
                                        <Setter Property="Foreground" Value="LightGray"/>
                                    </DataTrigger>

第一种样式还有一个问题,当尝试将VerticalContentAlignment=“Center”添加到setter时,Window不再加载,因此我将其单独添加到每个文本框。还有另外两个问题,其中执行的故事板“SrcWrongInput”,我添加到源的风格。首先,我不知道如何在样式资源中解决它,所以我还将其添加到名为“Rslt”的最后一个文本框的资源中,以至少确保它与样式兼容。它不是,我在ps1脚本中的按钮“Do”点击处理程序上触发了它,并得到了这个异常:
“BorderBrush-Eigenschaft wird nicht auf“DependencyObject”im Pfad“(BorderBrush).(0)”verwiesen.”
BorderBrush属性未链接到路径(BorderBrush)处的依赖项对象。(0)

ycggw6v2

ycggw6v21#

第一个示例使用VisualBrush呈现提示文本。相反,您应该使用TextBlock覆盖原始文本站点。出于性能原因,通常不建议使用Label并将Label.Content属性绑定到string。相反,您应该使用高度优化的TextBlock控件来显示简单的文本。
您的第二个解决方案实现了对TextBox非常糟糕的理解。使用内部TextBox实现TextBlockControltemplate没有意义。除了毫无意义之外,当你想处理模板化的TextBox的文本或输入事件时,你会遇到奇怪的行为:因为用户在内部的TextBox中输入,所以相关事件不会像预期的那样被引发(在模板化的TextBox上)。
推荐的解决方案是用TextBlock覆盖ControlTemnplate中的文本站点。然后可以将TextBlock.Text属性绑定到模板化的TextBox(例如TextBox.Tag或专用的自定义属性)。
使用CollectionView.IsEmpty属性作为触发器来切换提示TextBlock的可见性。这避免了两个单独的触发器来检查空的stringnull
一个MultiTrigger或在这种情况下一个MultiDataTrigger允许合并文本空和焦点条件,以进一步简化模板。为了避免丢失TextBox的自定义,您应该使用TemplateBinding扩展并将内部属性(如Border.Background)绑定到模板化的TextBox属性(例如TextBox.Background属性)。
最后,设置默认值,例如TextBox.Background使用Style设置器:这样你就可以自定义/覆盖控件,而不需要创建一个新的ControlTemplate

<Style x:Key="Text1"
       TargetType="{x:Type TextBox}">
  <Style.Resources>
    <SolidColorBrush x:Key="SolidBorderBrush"
                     Color="#888" />
    <SolidColorBrush x:Key="WindowBackgroundBrush"
                     Color="#FFF" />
  </Style.Resources>
  
  <Setter Property="Background"
          Value="{StaticResource WindowBackgroundBrush}" />
  <Setter Property="BorderBrush"
          Value="{StaticResource SolidBorderBrush}" />
  <Setter Property="BorderThickness"
          Value="1" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type TextBox}">

        <!-- Connect internals to the templated parent using '{TemplateBinding}' 
             to preserve customization -->
        <Border Name="Border"
                CornerRadius="2"
                Padding="2"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}">

          <!-- Use the Grid panel's implicit z-indexing of its children
               to overlay the text site with a hint text -->
          <Grid>
            <ScrollViewer x:Name="PART_ContentHost"
                          Margin="0" />
            <TextBlock x:Name="CueBannerPresenter"
                       Text="{TemplateBinding Tag}"
                       Visibility="Collapsed" />
          </Grid>
        </Border>

        <ControlTemplate.Triggers>
          <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
              <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text.IsEmpty}"
                         Value="True" />
              <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsKeyboardFocusWithin}"
                         Value="False" />
            </MultiDataTrigger.Conditions>

            <Setter TargetName="CueBannerPresenter"
                    Property="Visibility"
                    Value="Visible" />
          </MultiDataTrigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

相关问题