WPF:将UserControl属性映射到其子级

时间:2018-07-21 00:17:13

标签: wpf user-controls

我的问题的简短版本是:是否可以将UserControl的属性提供给UserControl的子级,而不必同时应用于UserControl?

长版本:我试图创建一个“ ButtonInput”,它是一个文本框,其右侧是一个位图按钮,位于该文本框的边框之内。这就是搜索框在许多网站上(或在Visual Studio中)的外观,右侧带有放大镜。

UserControl的定义是:

<UserControl x:Class="Test.Controls.ButtonInput"
             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:Test.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="Transparent">
        <Border 
                    Name="Border"
                    CornerRadius="6" 
                    Padding="4"
                    Margin="2 2 2 2"
                    Background="{Binding Path=Background, RelativeSource={RelativeSource FindAncestor, AncestorType=local:ButtonInput, AncestorLevel=1}}"
                    BorderBrush="{Binding Path=BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType=local:ButtonInput, AncestorLevel=1}}"
                    BorderThickness="1"
                >
                <Grid Background="Transparent">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <TextBox x:Name="tbInput" 
                             Grid.Column="0" 
                             MaxLines="1" 
                             Background="Transparent"
                             Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType=local:ButtonInput, AncestorLevel=1}}"
                             Text="{Binding Path=Text, RelativeSource={RelativeSource FindAncestor, AncestorType=local:ButtonInput, AncestorLevel=1}}"
                             BorderThickness="0"/>
                    <Button Width="24" Grid.Column="1" Click="Button_Click">
                        <Button.Template>
                            <ControlTemplate>
                                <Image x:Name="imgIcon"
                                        Source="{Binding Path=Source, RelativeSource={RelativeSource FindAncestor, AncestorType=local:ButtonInput, AncestorLevel=1}}" />
                            </ControlTemplate>
                        </Button.Template>
                    </Button>
                </Grid>
            </Border>

    </Grid>
</UserControl>

我将此控件放在测试窗口中。

<Window x:Class="Test.TestWindow"
        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:local="clr-namespace:Test"
        xmlns:controls="clr-namespace:Test.Controls"
        mc:Ignorable="d"
        Title="TestWindow" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Horizontal" Height="380" Width="402">
            <Label Content="Password" Width="75" VerticalAlignment="Center"/>
            <controls:ButtonInput x:Name="biTest" Source="Resources/img/password.png" Width="300" Height="35" Background="Orange" Foreground="Red" BorderBrush="Black" ButtonClick="ButtonInput_ButtonClick" />
        </StackPanel>

    </Grid>
</Window>

我遇到的问题是,我希望边框内部只有橙色,而橙色在边框外部流血。我将问题追溯到实时可视树的样子:

(ButtonInput)
   (Border)
      (ContentPresenter)
         (Grid)
            Border (Border)
               (Grid)
                  tbInput (TextBox)
                  (Button)

第一个Border不在我的控件定义中,但是它的背景是从ButtonInput继承的Orange。

我确实尝试了一种替代方法:我没有使用子控件作为UserControl的内容,而是使用了具有相同内容的ControlTemplate。在这种情况下,可执行文件看起来正常(带有黑色边框和橙色背景的圆角矩形,边框外没有渗色),但是Visual Studio中的设计器什么也不显示。从字面上看,ButtonInput应该存在一个空格。

那么,有没有办法防止在UserControl上设置的属性应用于第一个Border?背景是一个示例,但是我想利用其他方式使用其他属性。

1 个答案:

答案 0 :(得分:0)

这里发生的是您的“ ButtonInput”控件实际上不是按钮,而是一个用户控件,恰好碰巧上面有一个按钮。因此,当您在主窗口的<controls:ButtonInput>标签中设置背景时,您实际上是在说“忽略此用户控件关于整个背景颜色的所有内容,因为我现在要覆盖它”。

解决此问题的方法有多种,但是从UserControl的角度来看,最简单的方法是使用其工具库中的倒数第二个武器:模板。有效地覆盖控件中的模板表示:“我将不再像普通类型的控件那样显示我,因此,除非我明确使用它们,否则所有常规设置都将不适用。您的ButtonInput xaml:

<UserControl.Template>
    <ControlTemplate>

        <!-- all your old xaml code goes here -->
        <Grid Background="Transparent">
            <Border 
                    Name="Border"
                    CornerRadius="6" 
                    Padding="4"
        <!--             etc                  -->

    </ControlTemplate>
</UserControl.Template>

结果如下:

enter image description here

应该说的是,在WPF中很少有实际需要自定义控件的情况,这几乎肯定是其中之一。 WPF不仅能够通过样式和模板支持这样的功能。但是这个答案应该会在短期内满足您的需求。

编辑:如果您希望控件在设计器中可见,然后使用常规控件和模板代替它,则无论如何您可能应该这样做。现在,您的xaml应该如下所示:

<UserControl x:Class="WpfTestApp.Controls.ButtonInput"
             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:WpfTestApp.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             x:Name="_this">

    <UserControl.Resources>

        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">

            <Grid Background="Transparent">
                <Border 
                        Name="Border"
                        CornerRadius="6" 
                        Padding="4"
                        Margin="2 2 2 2"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="1"
                    >
                    <Grid Background="Transparent">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <TextBox x:Name="tbInput" 
                                 Grid.Column="0" 
                                 MaxLines="1" 
                                 Background="Transparent"
                                 Text="{Binding ElementName=_this, Path=Text, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
                                 Foreground="{TemplateBinding Foreground}"
                                 BorderThickness="0" Margin="0,-1,0,1"/>
                        <Button Width="24" Grid.Column="1">
                            <Button.Template>
                                <ControlTemplate>
                                    <Image x:Name="imgIcon" />
                                </ControlTemplate>
                            </Button.Template>
                        </Button>
                    </Grid>
                </Border>

            </Grid>

        </ControlTemplate>

    </UserControl.Resources>

    <Button x:Name="biTest" Width="300" Height="35" Background="Orange" Foreground="Red" BorderBrush="Black" Template="{StaticResource ButtonTemplate}" />

</UserControl>

您尚未解决问题中的文本绑定,上面的代码希望后面的UserControl代码具有依赖项属性:

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(ButtonInput), new PropertyMetadata(String.Empty));

现在您可以像这样使用它:

<controls:ButtonInput x:Name="biTest" Width="300" Height="35" Text="{Binding MyText, Mode=TwoWay}" />