如何覆盖自定义 WPF 用户控件的属性

HOWTO override properties from a custom WPF UserControl

我有以下 WPF 用户控件:

<UserControl x:Class="myComponents.UI.TextBoxWithPlaceholder"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:pEp.UI"
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800"
             Loaded="UserControl_Loaded">
    <Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:TextBoxWithPlaceholder}}"
          Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBox Name="myCustomTextBox"
                 Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
                 Padding="5"
                 IsReadOnly="{Binding IsReadOnly}"
                 HorizontalAlignment="Stretch"
                 TextChanged="CustomTextBox_TextChanged"
                 GotFocus="CustomTextBox_GotFocus"
                 LostFocus="CustomTextBox_LostFocus"
                 Margin="5"
                 HorizontalScrollBarVisibility="Auto"
                 VerticalScrollBarVisibility="Auto" />
        <TextBlock Name="myPlaceholderTextBlock"
                   IsHitTestVisible="False" 
                   Padding="5"
                   Text="{Binding Placeholder}" 
                   HorizontalAlignment="Left" 
                   Foreground="DarkGray"
                   Margin="5">
        </TextBlock>
    </Grid>
</UserControl>

基本上它是一个带有占位符的文本框。

现在,我通过 WPF 视图重用此组件:

xmlns:ui="clr-namespace:myComponents.UI"

然后将其作为普通对照:

<ui:TextBoxWithPlaceholder Name="myNewTextBox" IsReadOnly="{Binding IsReadOnly}"
                           Style="{StaticResource myTextBoxStyle}"
                           Placeholder="please, enter something here"/>

现在,正如您在上面看到的,我为其设置了自定义样式:

<Style x:Key="myTextBoxStyle" TargetType="{x:Type ui:TextBoxWithPlaceholder}">
    <Setter Property="Margin" Value="0" />
    <Setter Property="Padding" Value="0" />
    <Style.Triggers>
        <Trigger Property="IsFocused" Value="False">
            <Setter Property="Background" Value="{x:Null}"/>
            <Setter Property="Foreground" Value="{x:Null}"/>
            <Setter Property="BorderBrush" Value="{x:Null}"/>
        </Trigger>
    </Style.Triggers>
</Style>

现在,在我的“myNewTextBox”控件中,我试图覆盖名为 myCustomTextBox 和 myPlaceholderTextBlock 的控件的一些继承属性,例如 Margin、Padding、Background、Foreground、BorderBrush 等,但我已经尝试了上述样式,它是不工作。我也试过:

<Style x:Key="myTextBoxStyle" TargetType="{x:Type ui:TextBoxWithPlaceholder}">
    <Setter Property="{Binding Path=Margin, ElementName=myCustomTextBox}" Value="0" />
    <Setter Property="{Binding Path=Padding, ElementName=myCustomTextBox}" Value="0" />

    <Setter Property="{Binding Path=Margin, ElementName=myPlaceholderTextBlock }" Value="0" />
    <Setter Property="{Binding Path=Padding, ElementName=myPlaceholderTextBlock }" Value="0" />
    <Style.Triggers>
        <Trigger Property="IsFocused" Value="False">
            <Setter Property="{Binding Path=Background, ElementName=myCustomTextBox}" Value="{x:Null}"/>
            <Setter Property="{Binding Path=Foreground, ElementName=myCustomTextBox}" Value="{x:Null}"/>
            <Setter Property="{Binding Path=BorderBrush, ElementName=myCustomTextBox}" Value="{x:Null}"/>
        </Trigger>
    </Style.Triggers>
</Style>

这是一项需要自定义控件和视觉状态而不是 UserControl 触发器的任务。但是,如果您必须以 UserControl 的身份执行此操作(我不会责怪您,因为在这个阶段要学习很多东西),那么这里是:

首先,当您使用 ElementName 时,它应该指的是 XAML 处理器已经看到的元素,之前在当前 UI 中被布置。不是正在设置样式的控件内的元素。我认为这种方法行不通。

如果您希望 TextBoxWithPlaceholder 内的 TextBoxTextBlock 使用该外部控件的属性,您可以将它们绑定到它,在您的控件 XAML.比如改写一小部分那个绑定后台。

<TextBox Name="myCustomTextBox"
         Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
         Background={Binding RelativeSource={RelativeSource.FindAncestor, AncestorType={x:Type ui:TextBoxWithPlaceholder}, Path=Background}}"

但是,如果您真的希望嵌套的文本框(“myCustomTextBox”)使用带有触发器的样式及其自己专用的 属性 值,那么您可以尝试在其中创建一个 Resources 部分您的样式本身包含 TextBoxTextBlock 的隐式样式 Something like this

<Style x:Key="myTextBoxStyle" TargetType="{x:Type ui:TextBoxWithPlaceholder}">
    <Style.Resources>
        <!-- Implicit style for TextBox should only apply to TextBoxes inside a TextBoxWithPlaceholder -->
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="Margin" Value="0" />
            <Setter Property="Padding" Value="0" />

            <Style.Triggers>
                <Trigger Property="IsFocused" Value="False">
                    <Setter Property="Background Value="{x:Null}"/>
                    <Setter Property="Foreground" Value="{x:Null}"/>
                    <Setter Property="BorderBrush" Value="{x:Null}"/>
                </Trigger>
            <Style.Triggers>
        </Style>
    </Style.Resources>

</Style>

如果您希望能够从使用您的 TextBoxWithPlaceholder 控件的视图中设置 myCustomTextBox 的属性,您应该将 dependency properties 添加到后者并在 TextBoxWithPlaceholder.xaml 并在消费视图中设置它们,例如:

<ui:TextBoxWithPlaceholder ....PlaceHolderMargin="10" />

TextBoxWithPlaceholder.xaml:

<TextBlock Name="myPlaceholderTextBlock"
           ...
           Margin="{Binding PlaceHolderMargin,RelativeSource={RelativeSource AncestorType=UserControl}}">

恐怕您无法从 TextBoxWithPlaceholder 控件之外的 namescope 引用 ElementName=myPlaceholderTextBlock,因此尝试在消费视图中定义的 Style 中执行此操作不行。