WPF:用户控制访问样式通过依赖属性

时间:2017-06-08 10:45:42

标签: c# wpf xaml dependency-properties

我为带有图像的按钮创建了一个样式:

    <Style x:Key="StyleButtonBase" TargetType="Button">
    <Style.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../Sizes/Sizes.xaml" />
                <ResourceDictionary Source="../Colors/Brushes.xaml" />
                <ResourceDictionary Source="../Fonts/Fonts.xaml" />
                <ResourceDictionary Source="../Images/Images.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Style.Resources>
    <Setter Property="Background" Value="{StaticResource BrushButtonActive}" />
    <Setter Property="Foreground" Value="{StaticResource BrushForegroundLight}" />
    <Setter Property="FontFamily" Value="{StaticResource FontFamilyDefault}" />
    <Setter Property="FontSize" Value="{StaticResource DoubleFontSizeStandard}" />
    <Setter Property="Cursor" Value="Hand" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border
                    Background="{TemplateBinding Background}"
                    BorderBrush="{StaticResource BrushBorder}"
                    BorderThickness="{StaticResource ThicknessBorder}"
                    CornerRadius="{StaticResource CornerRadius}">
                    <Image Source="{StaticResource IconIcon}" Stretch="None" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsPressed" Value="True">
            <Setter Property="Background" Value="{StaticResource BrushButtonPressed}" />
        </Trigger>
    </Style.Triggers>
</Style>

现在我想创建一个User-Control,它只包含一个具有此样式的Button和一个Dependency属性来设置Button Image。我的用户控件的XAML部分如下所示:

<UserControl
x:Class="HH.HMI.ToolSuite.ResourceLib.Controls.ButtonSmall">

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="../Styles/Buttons.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>


<Button Style="{StaticResource StyleButtonBase}" Width="{StaticResource DoubleWidthButtonSmall}" Height="{StaticResource DoubleHeightControls}">
</Button></UserControl>

我的用户控件背后的代码如下所示:

public partial class ButtonSmall : UserControl, INotifyPropertyChanged
{
    public ButtonSmall()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty ButtonImageProperty
        = DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(TextOutput), new PropertyMetadata(null, OnButtonImagePropertyChanged));

    private static void OnButtonImagePropertyChanged(DependencyObject dependencyObject,
           DependencyPropertyChangedEventArgs e)
    {
        ButtonSmall temp = dependencyObject as ButtonSmall;
        temp.OnPropertyChanged("ButtonImage");
        temp.OnButtonImagePropertyChanged(e);
    }

    private void OnButtonImagePropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        ButtonSmallImage.Source = ButtonImageSource;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public ImageSource ButtonImageSource
    {
        get { return (ImageSource)GetValue(ButtonImageProperty); }
        set { SetValue(ButtonImageProperty, value); }
    }
}

在我的其他用户控件中,我通常访问用户控件本身的元素,如:

xamlname.text = text

现在我还没有用户控件的xaml代码中的命名元素。相反,我在样式中有命名元素,我在用户控件中引用它。如何通过我的代码访问这个?

2 个答案:

答案 0 :(得分:1)

如果我是你,我会将Button子类化并创建一个新类(只是一个.cs文件),如下所示:

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

namespace MyProject
{
    public class IconButton : Button
    {
        public static readonly DependencyProperty ButtonImageProperty = DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(IconButton),
                  new FrameworkPropertyMetadata(new BitmapImage(), FrameworkPropertyMetadataOptions.AffectsRender));

        public ImageSource ButtonImage
        {
            get { return (ImageSource)GetValue(ButtonImageProperty); }
            set { SetValue(ButtonImageProperty, value); }
        }
    }
}

这意味着您现在可以引用该属性。否则,因为你的按钮只是一个常规按钮(只有常规按钮的属性;没有图像),你的风格并不知道你期望Button有的新图像属性。不要忘记更新你的风格的TargetType指向IconButton。

如果您将样式放在用户控件的资源部分,可以像这样设置按钮样式:

<UserControl x:Class="MyProject.MyControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:myclass="clr-namespace:MyProject">
   <UserControl.Resources>
      <!-- your style here -->
   </UserControl.Resources>
   <myclass:IconButton Style="{StaticResource StyleButtonBase}/>
</UserControl>

(必须更换xmlns&#39; myclass&#39;以引用自定义按钮所在的命名空间!)

此外,如果从样式中删除x:Key属性,它将应用于范围中的所有按钮,这意味着您可以省略显式设置。如果您在共享的ResourceDictionary中找到它(如果您正在构建自定义控件库),这可能很方便(如果您这样做,则需要在App.xaml.cs文件中组合此资源字典) )。如果您最终这样做并且发现您的UserControl除了包装IconButton之外没有任何特殊功能,您当然可以完全省略它并且只在其他控件中直接使用IconButtons。你的样式声明你的IconButton看起来如何,你的IconButton类确保你的样式所期望的资源(你的图像)在运行时查找它们,所以只要你的风格在范围内,你就会好走。

答案 1 :(得分:0)

如果样式在App.xaml中的Application.Resources中定义,或者在App.xaml中合并到Application.Resources的资源字典中定义,则可以在用户控件中引用它通过StaticResource。如果它在另一个资源字典中,您必须将该合并到UserControl.Resources

或者您可以将其直接放在UserControl.Resources中,如TernaryTopiary所示,如果在其他地方不需要它。

对于图像源属性,您可以像Ternary建议的那样编写一个Button子类,或者您可以编写一个附加属性(见下文)。在XAML中,当您自定义控件时,首先尝试使用常规属性;然后你试着重新开始。然后你升级到替换控制模板,并且不能完成这项工作,你考虑附加的属性/行为。只有在所有其他方法都失败的情况下才会使用子类化。你应该知道如何去做,但你也应该学习其他的做事方式。

在这种情况下,有一种快速而肮脏的方式可以做到与正确的XAML操作方式一致:按钮的Content属性未使用,并且它的声明类型是Object,所以我们可以使用它。由于我们使用绑定传递图像源,因此您可以摆脱PropertyChanged上的ButtonImageSource处理程序。

<UserControl 
    ...>
    <!-- ... -->        

    <Button 
        Content="{Binding ButtonImageSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
        Style="{StaticResource StyleButtonBase}" 
        Width="{StaticResource DoubleWidthButtonSmall}" 
        Height="{StaticResource DoubleHeightControls}"
        />

StyleButtonBase中的控件模板中进行以下更改:

        <ControlTemplate TargetType="Button">
            <Border
                Background="{TemplateBinding Background}"
                BorderBrush="{StaticResource BrushBorder}"
                BorderThickness="{StaticResource ThicknessBorder}"
                CornerRadius="{StaticResource CornerRadius}"
                >
                <!-- Now we'll find the image source in the button's Content --->
                <Image 
                    Source="{TemplateBinding Content}" 
                    Stretch="None" 
                    />
            </Border>
        </ControlTemplate>

附属物

使用Content对此非常轻微地滥用WPF:您不应该真正重新利用属性。 WPF中普遍理解Content是指&#34;任意内容&#34;,而不是&#34;任何ImageSource&#34;。

所以它更多一点&#34;正确&#34;使用附属物,并没有太多的工作。这就是看起来的样子。

我们会在一个单独的静态类中定义附加属性,因为除了ButtonImageSource之外我无法想到它的好名称,您已经在SmallButton中使用了public static class ButtonHelper { #region ButtonHelper.ButtonImageSource Attached Property public static ImageSource GetButtonImageSource(Button obj) { return (ImageSource)obj.GetValue(ButtonImageSourceProperty); } public static void SetButtonImageSource(Button obj, ImageSource value) { obj.SetValue(ButtonImageSourceProperty, value); } public static readonly DependencyProperty ButtonImageSourceProperty = DependencyProperty.RegisterAttached("ButtonImageSource", typeof(ImageSource), typeof(ButtonHelper), new PropertyMetadata(null)); #endregion ButtonHelper.ButtonImageSource Attached Property } {1}}:

Content

在XAML中,用户控件使用此附加属性而不是<Button Style="{StaticResource StyleButtonBase}" local:ButtonHelper.ButtonImageSource="{Binding ButtonImageSource, RelativeSource={RelativeSource AncestorType=UserControl}}" />

<ControlTemplate TargetType="Button">
    <Border
        Background="{TemplateBinding Background}"
        BorderBrush="{TemplateBinding BorderBrush}"
        BorderThickness="{TemplateBinding BorderThickness}"
        CornerRadius="4"
        >
        <Image 
            Source="{TemplateBinding local:ButtonHelper.ButtonImageSource}" 
            Stretch="None" 
            />
    </Border>
</ControlTemplate>

控制模板同样如此:

Content

所有附加的属性确实为我们提供了一个名为ImageSource的属性,并且强类型为typeof(TextOutput),我们可以使用它来传递该图像源。

另一件事:也许这是一个错误,当你简化问题的代码时会悄悄进入,但是你将DependencyProperty.Register()传递给typeof(ButtonSmall)你应该传递的ButtonImage ButtonImageSource }。更重要的是,您应该为单个属性设置两个名称:public static readonly DependencyProperty ButtonImageSourceProperty = DependencyProperty.Register( "ButtonImageSource", typeof(ImageSource), // Should be ButtonSmall, not TextOutput typeof(ButtonSmall), new PropertyMetadata(null)); public ImageSource ButtonImageSource { get { return (ImageSource)GetValue(ButtonImageSourceProperty); } set { SetValue(ButtonImageSourceProperty, value); } } TemplateBinding

BorderBrush

顺便说一下,在你的风格中,最好将BorderThickness用于Background describe('your block', () => { let submitB; beforeEach(() => { submitB = fixture.debugElement.nativeElement.query(By.css('#submitNewPage')); submitB.dispatchEvent(new Event('click')) }); it('your expectation' () => {}) }) ,并设置样式设置器中的默认值,就像{{1 }}。