繁体   English   中英

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

[英]WPF: User Control access Style via Dependency Property

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

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

现在,我想创建一个用户控件,该控件仅包含具有这种样式的按钮和用于设置按钮图像的Dependency属性。 我的用户控件的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代码中没有命名元素。 相反,我有样式中的命名元素,我在用户控件中引用了该元素。 如何通过我的代码访问此代码?

如果我是我,我将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); }
        }
    }
}

这意味着您现在可以引用该属性。 否则,由于您的按钮只是一个常规按钮(仅具有常规按钮的属性;没有图像),因此您的样式不知道希望按钮具有的新图像属性。 不要忘记更新样式的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'myclass'来引用您的自定义按钮所在的名称空间!)

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

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

或者,也可以按照TernaryTopiary的建议将其直接放在UserControl.Resources中,如果在其他地方不需要它。

至于图像源属性,您可以按照Ternary的建议编写Button子类,也可以编写附加的属性(请参见下文)。 在XAML中,当您自定义控件时,首先尝试使用常规属性来完成此操作; 然后您尝试重新设置样式。 然后升级为替换控件模板,而这样做并不能完全解决问题,您需要考虑附加的属性/行为。 只有在所有其他方法都无法解决的情况下,您才可以使用子类化。 您应该知道如何做,但是您还应该学习其他做事方式。

在这种情况下,有一种快速而又肮脏的方法与正确的XAML处理方法一致:按钮的Content属性不再使用,其声明的类型为Object ,因此我们可以使用它。 由于我们使用绑定来传递图像源,因此您可以摆脱ButtonImageSource上的那个PropertyChanged处理程序。

<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被普遍理解为表示“任何任意内容”,而不是“任何ImageSource ”。

因此,使用附加属性稍微有些“正确”,而且工作量也不大。 这就是它的外观。

我们将在一个单独的静态类中定义附加属性,因为除了ButtonImageSource ,我想不出它的好名字,您已经在SmallButton使用了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
}

在XAML中,用户控件使用此附加属性而不是Content

<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) 更重要的是,对于一个应具有的属性,您有两个名称: ButtonImageButtonImageSource

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); }
}

顺便说一句,在您的Style中,最好将TemplateBinding用于BorderBrushBorderThickness ,并在Style setter中设置默认值,就像使用Background

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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