如何以通用方式更改按钮的圆度

问题描述

我目前正在努力使我的 WPF 应用程序更通用一些。 到目前为止,对于我想要创建的每个按钮,我都使用了不同的样式来修改圆度(这会产生很多无用的代码)。

使用以下代码我设法创建了一个可以从 XAML 文件更改的变量,但我无法将其链接到圆度本身。

谁能告诉我我做错了什么?我已经检查了很多论坛,但除了“不要以通用方式进行操作”之外,似乎没有人有答案。

我可以确定一切都在编译,并且样式以其他方式正确应用于按钮(没有 xaml 链接问题)。

我使用的样式:

<Style x:Key="AwakeButton" targettype="{x:Type customcontrols:AwakeButton}" BasedOn="{StaticResource {x:Type Button}}"
       xmlns:extensions="Awake.Services.Properties:Extensions">
    <Setter Property="customcontrols:AwakeButton.BorderRoundness" Value="4.0"/>
    <Style.Resources>
        <Style targettype="Border">
            <Setter Property="CornerRadius" Value="{Binding Path=BorderRoundness}" />
            <!--<Setter Property="CornerRadius" Value="10" />-->
        </Style>
    </Style.Resources>
</Style>

我为此创建的按钮的过载:

public class AwakeButton : Button
{
    public AwakeButton()
    {
        
    }

    public static DependencyProperty BorderRoundnessProperty =
         DependencyProperty.Registerattached("BorderRoundness",typeof(double),typeof(AwakeButton)); 
    public static void SetBorderRoundness(UIElement element,double value)
    {
        element.SetValue(BorderRoundnessProperty,value);
        
    }

    public static double GetBorderRoundness(UIElement element)
    {
        return (double)element.GetValue(BorderRoundnessProperty);
    }
}

我如何在页面中使用它:

<customcontrols:AwakeButton Style="{StaticResource AwakeButton}" Margin="142,115,0"  Width="136" Height="167" BorderRoundness="5">

解决方法

您必须将 BorderRoundness 绑定到父级 AwakeButton,否则会使用当前的 DataContext 进行解析,该 Button 不包含此属性。此外,如果您从 Register(...) 派生,则不必附加依赖属性,只需使用 static 方法注册一个普通属性即可。还要使 DP readonly <Setter Property="CornerRadius" Value="{Binding BorderRoundness,RelativeSource={RelativeSource AncestorType={x:Type local:AwakeButton}}}" />

BorderRoundness

如果您不更改按钮的任何特别之处,您还可以创建附加属性而不是仅用于公开 public static class ButtonProperties { public static readonly DependencyProperty BorderRoundnessProperty = DependencyProperty.RegisterAttached("BorderRoundness",typeof(double),typeof(ButtonProperties)); public static void SetBorderRoundness(UIElement element,double value) { element.SetValue(BorderRoundnessProperty,value); } public static double GetBorderRoundness(UIElement element) { return (double)element.GetValue(BorderRoundnessProperty); } } 属性的专用子类型。

BorderRoundness

您可以使用附加的属性绑定语法(括号)来引用 <Style x:Key="AwakeButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}"> <Setter Property="local:ButtonProperties.BorderRoundness" Value="4.0"/> <Style.Resources> <Style TargetType="Border"> <Setter Property="CornerRadius" Value="{Binding (local:ButtonProperties.BorderRoundness),RelativeSource={RelativeSource AncestorType={x:Type Button}}}" /> </Style> </Style.Resources> </Style>

<Button Grid.Row="0" Style="{StaticResource AwakeButton}" Margin="142,115,0"  Width="136" Height="167" local:ButtonProperties.BorderRoundness="5"/>

您现在使用带有新创建的附加边框圆度属性的常规按钮。

var numArray = [88,5,700];
,

圆度作为 CornerRadius 应用于按钮的边框。 Border 在 Button 的 ControlTemplate 中定义。 ControlTemplate 定义控件的外观。
换句话说,您需要将属性值委托给 ControlTemplate 中的相关元素。

要将值委托给 ControlTemplate,您必须覆盖此模板并将模板化的父属性绑定到模板元素:

在您的 AwakeButton 中,将 BorderRoundness 属性定义为简单的 DependencyProperty(未附加)并覆盖默认样式定义,以便 AwakeButton 将使用其自己的默认样式。这样 Button 就可以重复使用,而不必每次想要使用它时都重新定义 Style,这在将项目作为库发布时尤为重要:

AwakeButton.cs

public class AwakeButton : Button
{
  public static readonly DependencyProperty BorderRoundnessProperty = DependencyProperty.Register(
    "BorderRoundness",typeof(Thickness),typeof(AwakeButton),new PropertyMetadata(default(Thickness)));

  public Thickness DestinationPath
  {
    get => (Thickness) GetValue(AwakeButton.BorderRoundnessProperty);
    set => SetValue(AwakeButton.BorderRoundnessProperty,value);
  }

  static AwakeButton()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(AwakeButton),new FrameworkPropertyMetadata(typeof(AwakeButton)));
  }
}

Generic.xaml.cs
此文件位于 Themes 文件夹中,包含所有默认样式。 WPF 将自动检查此文件的默认样式,并在未找到其他样式覆盖时应用它。

<Style TargetType="AwakeButton">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="AwakeButton">
        <Border BorderBrush={TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                BorderBrush="{TemplateBinding BorderBrush}"
                CornerRadius="{TemplateBinding BorderRoundness}">
          <ContentPresenter />
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Stayle>

示例

<Grid>
  <AwakeButton BorderRoundness="8" />
</Grid>

但是如果你想让它真正通用,使用附加属性,你必须做出一个附加行为。以下代码适用于在其可视化树中包含 DependencyObject 作为子项的每个 Border

class Element : DependencyObject
{
  #region CornerRoundness attached property

  public static readonly DependencyProperty CornerRoundnessProperty = DependencyProperty.RegisterAttached(
    "CornerRoundness",typeof(CornerRadius),typeof(Element),new PropertyMetadata(default(CornerRadius),Element.OnCornerRoundnessChanged));

  public static void SetCornerRoundness(DependencyObject attachingElement,CornerRadius value) =>
    attachingElement.SetValue(Element.CornerRoundnessProperty,value);

  public static CornerRadius GetCornerRoundness(DependencyObject attachingElement) =>
    (CornerRadius) attachingElement.GetValue(Element.CornerRoundnessProperty);

  #endregion CornerRoundness attached property


  private static void OnCornerRoundnessChanged(DependencyObject attachingElement,DependencyPropertyChangedEventArgs e)
  {
    if (Element.TryFindVisualChildElement(attachingElement,out Border elementBorder))
    {
      elementBorder.CornerRadius = (CornerRadius) e.NewValue;
    }
  }

  public static bool TryFindVisualChildElement<TChild>(DependencyObject parent,out TChild resultElement)
    where TChild : DependencyObject
  {
    resultElement = null;

    if (parent is Popup popup)
    {
      parent = popup.Child;
      if (parent == null)
      {
        return false;
      }
    }

    for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
    {
      DependencyObject childElement = VisualTreeHelper.GetChild(parent,childIndex);

      if (childElement is TChild child)
      {
        resultElement = child;
        return true;
      }

      if (Element.TryFindVisualChildElement(childElement,out resultElement))
      {
        return true;
      }
    }

    return false;
  }
}

示例

<StackPanel>
  <Button Element.CornerRoundness="8" />
  <ToggleButton Element.CornerRoundness="8" />
</StackPanel>