【问题标题】:WPF: User Control access Style via Dependency PropertyWPF:通过依赖属性的用户控制访问样式
【发布时间】:2017-06-08 10:45:41
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# wpf xaml dependency-properties


    【解决方案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 'myclass' 以引用您的自定义按钮所在的命名空间!)

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

    【讨论】:

      【解决方案2】:

      如果样式在 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 的一种非常轻微的滥用:您不应该真的重新利用属性。 Content 在 WPF 中被普遍理解为“任何任意内容”,而不是“任何 ImageSource”。

      所以使用附加属性会更“正确”一点,而且工作量也不大。这就是它的样子。

      我们将在一个单独的静态类中定义附加属性,因为除了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
      }
      

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

      顺便说一句,在您的样式中,最好将TemplateBinding 用于BorderBrushBorderThickness,并在样式设置器中设置默认值,就像您对Background 所做的那样。

      【讨论】:

      • 感谢您的回复!我更新了我的问题,让我的问题更清楚
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多