如何以通用方式更改按钮的圆度
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>
我目前正在努力使我的 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>