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

How to change the roundness of a button in a generic way

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

使用以下代码 我已经设法创建了一个变量,我可以从 XAML 文件更改,但我不能 link 它到圆度本身。

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

我可以准确地说,一切都在编译,并且样式已正确应用于按钮(没有 xaml linking 问题)。

我使用的样式:

<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,0"  Width="136" Height="167" BorderRoundness="5">

您必须将 BorderRoundness 绑定到父 AwakeButton,否则将使用不包含此 属性 的当前 DataContext 进行解析。此外,如果您从 Button 派生,则不必附加依赖项 属性,您可以使用 Register(...) 方法注册一个普通的依赖项。也让 DPs static and 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,0"  Width="136" Height="167" local:ButtonProperties.BorderRoundness="5"/>

圆度作为 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>