WPF 自定义控件按钮内容因多个按钮而丢失
WPF Custom Control Button Content Goes Missing With More Than One Button
首先,这是在 .NET 4.0 中,因为它必须如此。我知道一些错误已在 .NET 的更高版本中修复,所以如果这是一个实际的 .NET 错误,我想我将不得不使用似乎没有这个问题的用户控件。
我在 WPF 中创建了一个自定义控件库来制作将在第 3 方软件中使用的自定义按钮。但是,我似乎遇到了一个问题,多个按钮导致除了其中一个按钮之外的所有内容都丢失了。我已经在SNOOP中确认了这个问题。只是内容不存在。 SNOOP 树一直到内容呈现器,然后它下面什么都没有,除了一个确实有内容的按钮。我创建了一个非常简单的问题示例。
我的图书馆Generic.xaml如下:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControlsLibrary.Controls">
<Style x:Key="CustomButtonStyle" TargetType="{x:Type controls:CustomButton}">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:CustomButton}">
<Border CornerRadius="{TemplateBinding CornerRadius}" BorderThickness="3" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" ContentSource="Content" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Button1Style" TargetType="{x:Type controls:Button1}" BasedOn="{StaticResource CustomButtonStyle}" >
<Setter Property="CornerRadius" Value="4" />
<Setter Property="BorderBrush" Value="White" />
<Setter Property="Height" Value="40" />
<Setter Property="Width" Value="100" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=controls:Button1}, Path=Text}" />
</Setter.Value>
</Setter>
</Style>
两个控件类如下:
自定义按钮:
public class CustomButton : Button
{
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(CustomButton), new FrameworkPropertyMetadata(new CornerRadius(0)));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
}
按钮 1:
public class Button1 : CustomButton
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Button1), new FrameworkPropertyMetadata(""));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
static Button1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Button1), new FrameworkPropertyMetadata(typeof(Button1)));
}
}
然后我创建了一个简单的 WPF 应用程序,其中只有一个主 window,所有逻辑都在 MainWindow.xaml:
<Window x:Class="CustomControlLibraryTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControlsLibrary.Controls;assembly=CustomControlsLibrary"
Title="MainWindow" Height="350" Width="525" Background="DarkGray">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/CustomControlsLibrary;component/Themes/Generic.xaml" />
</Window.Resources>
<StackPanel>
<controls:Button1 Style="{StaticResource Button1Style}" Background="Red" Text="Button 1" />
<controls:Button1 Style="{StaticResource Button1Style}" Background="Blue" Text="Button 2" />
</StackPanel>
当 运行 时,按钮 1 的内容丢失,而按钮 2 看起来很好。从 Window 中删除 Button 2 会导致 Button 1 看起来像预期的那样。
如前所述,SNOOP 表示当两个按钮都存在时按钮 1 的内容不存在。
有什么想法吗?
实际情况是该样式实际上只有一个 TextBlock 实例。当样式应用于第二个按钮时,TextBlock 实际上重新成为新控件的父级。您应该能够通过在 TextBlock 元素上设置 x:Shared="false" 来避免这种情况。
我要在这里提出不同意见,从 Matthew MacDonalds 的一句话开始 "Pro WPF in C#":
Custom controls are still a useful way to build custom widgets that
you can share between applications, but they’re no longer a
requirement when you want to enhance and customize core controls. (To
understand how remarkable this change is, it helps to point out that
this book’s predecessor, Pro .NET 2.0 Windows Forms and Custom
Controls in C#, had nine complete chapters about custom controls and
additional examples in other chapters. But in this book, you’ve made
it to Chapter 18 without a single custom control sighting!)
简单地说,不需要创建额外的按钮 classes 来控制模板中已经存在的属性。您可以使用数据绑定或附加属性等轻松地做到这一点,并且它将与 Blend 等工具更加兼容。
为了说明这一点,这里有一个帮助程序 class,用于您在示例代码中公开的两个属性:
public static class ButtonHelper
{
public static double GetCornerRadius(DependencyObject obj)
{
return (double)obj.GetValue(CornerRadiusProperty);
}
public static void SetCornerRadius(DependencyObject obj, double value)
{
obj.SetValue(CornerRadiusProperty, value);
}
// Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.RegisterAttached("CornerRadius", typeof(double), typeof(ButtonHelper), new PropertyMetadata(0.0));
public static string GetButtonText(DependencyObject obj)
{
return (string)obj.GetValue(ButtonTextProperty);
}
public static void SetButtonText(DependencyObject obj, string value)
{
obj.SetValue(ButtonTextProperty, value);
}
// Using a DependencyProperty as the backing store for ButtonText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.RegisterAttached("ButtonText", typeof(string), typeof(ButtonHelper), new PropertyMetadata(""));
}
现在我们可以立即创建两种样式,一种用于您的每种按钮类型,并在内部绑定到这些属性:
<Style x:Key="RoundedButtonStyle" TargetType="{x:Type Button}" >
<Setter Property="Margin" Value="10" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="Background" Value="Red" />
<Setter Property="controls:ButtonHelper.CornerRadius" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding Path=(controls:ButtonHelper.CornerRadius),
RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="3"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" ContentSource="Content" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TextButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource RoundedButtonStyle}">
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Background" Value="Blue" />
<Setter Property="controls:ButtonHelper.ButtonText" Value="TextButton" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding Path=(controls:ButtonHelper.CornerRadius),
RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="3"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<TextBlock Text="{Binding Path=(controls:ButtonHelper.ButtonText),
RelativeSource={RelativeSource TemplatedParent}}" Background="Transparent" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
就是这样!由于内容直接在样式中指定,因此不需要自定义控件也不需要 x:Shared,而且它更轻量级。这是使用它们的示例:
<UniformGrid Columns="2">
<Button Style="{StaticResource RoundedButtonStyle}" Content="RoundedButton" />
<Button Style="{StaticResource RoundedButtonStyle}" Content="RoundedButton big radius" controls:ButtonHelper.CornerRadius="20"/>
<Button Style="{StaticResource TextButtonStyle}" />
<Button Style="{StaticResource TextButtonStyle}" controls:ButtonHelper.ButtonText="TextButton new text"/>
<Button Style="{StaticResource TextButtonStyle}" BorderBrush="Green" Background="Green"
controls:ButtonHelper.ButtonText="Both text and radius"
controls:ButtonHelper.CornerRadius="20" />
</UniformGrid>
结果如下:
我当然意识到我已经在每个模板中指定了边框,但也可以通过在边框内放置一个内容控件并使用数据模板来设置内容来轻松删除边框。
首先,这是在 .NET 4.0 中,因为它必须如此。我知道一些错误已在 .NET 的更高版本中修复,所以如果这是一个实际的 .NET 错误,我想我将不得不使用似乎没有这个问题的用户控件。
我在 WPF 中创建了一个自定义控件库来制作将在第 3 方软件中使用的自定义按钮。但是,我似乎遇到了一个问题,多个按钮导致除了其中一个按钮之外的所有内容都丢失了。我已经在SNOOP中确认了这个问题。只是内容不存在。 SNOOP 树一直到内容呈现器,然后它下面什么都没有,除了一个确实有内容的按钮。我创建了一个非常简单的问题示例。
我的图书馆Generic.xaml如下:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControlsLibrary.Controls">
<Style x:Key="CustomButtonStyle" TargetType="{x:Type controls:CustomButton}">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:CustomButton}">
<Border CornerRadius="{TemplateBinding CornerRadius}" BorderThickness="3" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" ContentSource="Content" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Button1Style" TargetType="{x:Type controls:Button1}" BasedOn="{StaticResource CustomButtonStyle}" >
<Setter Property="CornerRadius" Value="4" />
<Setter Property="BorderBrush" Value="White" />
<Setter Property="Height" Value="40" />
<Setter Property="Width" Value="100" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=controls:Button1}, Path=Text}" />
</Setter.Value>
</Setter>
</Style>
两个控件类如下:
自定义按钮:
public class CustomButton : Button
{
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(CustomButton), new FrameworkPropertyMetadata(new CornerRadius(0)));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
}
按钮 1:
public class Button1 : CustomButton
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Button1), new FrameworkPropertyMetadata(""));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
static Button1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Button1), new FrameworkPropertyMetadata(typeof(Button1)));
}
}
然后我创建了一个简单的 WPF 应用程序,其中只有一个主 window,所有逻辑都在 MainWindow.xaml:
<Window x:Class="CustomControlLibraryTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CustomControlsLibrary.Controls;assembly=CustomControlsLibrary"
Title="MainWindow" Height="350" Width="525" Background="DarkGray">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/CustomControlsLibrary;component/Themes/Generic.xaml" />
</Window.Resources>
<StackPanel>
<controls:Button1 Style="{StaticResource Button1Style}" Background="Red" Text="Button 1" />
<controls:Button1 Style="{StaticResource Button1Style}" Background="Blue" Text="Button 2" />
</StackPanel>
当 运行 时,按钮 1 的内容丢失,而按钮 2 看起来很好。从 Window 中删除 Button 2 会导致 Button 1 看起来像预期的那样。
如前所述,SNOOP 表示当两个按钮都存在时按钮 1 的内容不存在。
有什么想法吗?
实际情况是该样式实际上只有一个 TextBlock 实例。当样式应用于第二个按钮时,TextBlock 实际上重新成为新控件的父级。您应该能够通过在 TextBlock 元素上设置 x:Shared="false" 来避免这种情况。
我要在这里提出不同意见,从 Matthew MacDonalds 的一句话开始 "Pro WPF in C#":
Custom controls are still a useful way to build custom widgets that you can share between applications, but they’re no longer a requirement when you want to enhance and customize core controls. (To understand how remarkable this change is, it helps to point out that this book’s predecessor, Pro .NET 2.0 Windows Forms and Custom Controls in C#, had nine complete chapters about custom controls and additional examples in other chapters. But in this book, you’ve made it to Chapter 18 without a single custom control sighting!)
简单地说,不需要创建额外的按钮 classes 来控制模板中已经存在的属性。您可以使用数据绑定或附加属性等轻松地做到这一点,并且它将与 Blend 等工具更加兼容。
为了说明这一点,这里有一个帮助程序 class,用于您在示例代码中公开的两个属性:
public static class ButtonHelper
{
public static double GetCornerRadius(DependencyObject obj)
{
return (double)obj.GetValue(CornerRadiusProperty);
}
public static void SetCornerRadius(DependencyObject obj, double value)
{
obj.SetValue(CornerRadiusProperty, value);
}
// Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.RegisterAttached("CornerRadius", typeof(double), typeof(ButtonHelper), new PropertyMetadata(0.0));
public static string GetButtonText(DependencyObject obj)
{
return (string)obj.GetValue(ButtonTextProperty);
}
public static void SetButtonText(DependencyObject obj, string value)
{
obj.SetValue(ButtonTextProperty, value);
}
// Using a DependencyProperty as the backing store for ButtonText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.RegisterAttached("ButtonText", typeof(string), typeof(ButtonHelper), new PropertyMetadata(""));
}
现在我们可以立即创建两种样式,一种用于您的每种按钮类型,并在内部绑定到这些属性:
<Style x:Key="RoundedButtonStyle" TargetType="{x:Type Button}" >
<Setter Property="Margin" Value="10" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="Background" Value="Red" />
<Setter Property="controls:ButtonHelper.CornerRadius" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding Path=(controls:ButtonHelper.CornerRadius),
RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="3"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" ContentSource="Content" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TextButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource RoundedButtonStyle}">
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Background" Value="Blue" />
<Setter Property="controls:ButtonHelper.ButtonText" Value="TextButton" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding Path=(controls:ButtonHelper.CornerRadius),
RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="3"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<TextBlock Text="{Binding Path=(controls:ButtonHelper.ButtonText),
RelativeSource={RelativeSource TemplatedParent}}" Background="Transparent" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
就是这样!由于内容直接在样式中指定,因此不需要自定义控件也不需要 x:Shared,而且它更轻量级。这是使用它们的示例:
<UniformGrid Columns="2">
<Button Style="{StaticResource RoundedButtonStyle}" Content="RoundedButton" />
<Button Style="{StaticResource RoundedButtonStyle}" Content="RoundedButton big radius" controls:ButtonHelper.CornerRadius="20"/>
<Button Style="{StaticResource TextButtonStyle}" />
<Button Style="{StaticResource TextButtonStyle}" controls:ButtonHelper.ButtonText="TextButton new text"/>
<Button Style="{StaticResource TextButtonStyle}" BorderBrush="Green" Background="Green"
controls:ButtonHelper.ButtonText="Both text and radius"
controls:ButtonHelper.CornerRadius="20" />
</UniformGrid>
结果如下:
我当然意识到我已经在每个模板中指定了边框,但也可以通过在边框内放置一个内容控件并使用数据模板来设置内容来轻松删除边框。