简体   繁体   中英

HOWTO override properties from a custom WPF UserControl

I have below WPF UserControl:

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

Basically it is a TextBox with a placeholder.

Now from a WPF view I reuse this component by doing:

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

and then place it as a normal control:

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

Now as you see above I set a custom style for it:

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

Now in my "myNewTextBox" control I am trying to override some inherited properties for the controls named myCustomTextBox and myPlaceholderTextBlock such as Margin, Padding, Background, Foreground, BorderBrush, etc. but I have tried above style and it is not working. Also I have tried:

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

If you want to be able to set properties of the myCustomTextBox from view that consumes your TextBoxWithPlaceholder control, you should add dependency properties to the latter and bind to them in TextBoxWithPlaceholder.xaml and set them in the consuming view, eg:

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

TextBoxWithPlaceholder.xaml:

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

I am afraid you cannot refer to ElementName=myPlaceholderTextBlock from a namescope outside the TextBoxWithPlaceholder control so trying to do this in a Style that's defined in a consuming view won't work.

This is a task that cries out for a Custom control and Visual States instead of a UserControl with Triggers. But if you must do this as a UserControl (and I don't blame you because that's a lot to learn at this stage) then here goes:

First of all, when you use ElementName it is supposed to refer to elements that the XAML processor has already seen, previously in the current UI being laid out. Not elements inside the control being styled. I don't see that approach working.

If you want the TextBox and TextBlock inside a TextBoxWithPlaceholder to use the properties of that outer control, you could bind them to it, inside your control's XAML. For example, to rewrite a small part of that binding the background.

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

But if you truly want that nested TextBox ("myCustomTextBox") to use a style with triggers and its own dedicated property values, then what you might try is creating a Resources section inside your style that itself contains implicit styles for the TextBox and TextBlock 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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM