【问题标题】:WPF Custom Control Button Content Goes Missing With More Than One ButtonWPF 自定义控件按钮内容丢失多个按钮
【发布时间】:2016-03-03 06:58:11
【问题描述】:

首先,这是在 .NET 4.0 中,因为它必须如此。我知道一些错误已在更高版本的 .NET 中得到修复,所以如果这是一个实际的 .NET 错误,我想我将不得不使用似乎没有这个问题的用户控件。

我在 WPF 中创建了一个自定义控件库来制作将在 3rd 方软件中使用的可自定义按钮。但是,我似乎有一个问题,多个按钮导致除了一个按钮之外的所有内容都丢失了。我已经在 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 应用程序,其中只有一个主窗口,所有逻辑都在 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>

运行时,Button 1 的内容丢失,而 Button 2 看起来还不错。从 Window 中移除 Button 2 会使 Button 1 看起来像预期的那样。

如前所述,SNOOP 表示当两个按钮都存在时,按钮 1 的内容不存在。

有什么想法吗?

【问题讨论】:

    标签: c# .net wpf xaml custom-controls


    【解决方案1】:

    我将在这里提出不同意见,首先引用 Matthew MacDonalds “Pro WPF in C#”的一句话:

    自定义控件仍然是构建自定义小部件的有用方式 您可以在应用程序之间共享,但它们不再是 当您想要增强和自定义核心控件时的要求。 (至 了解这种变化是多么显着,它有助于指出 本书的前身 Pro .NET 2.0 Windows Forms and Custom C#中的控件,有九个完整的章节关于自定义控件和 其他章节中的其他示例。但在这本书中,你已经 到第 18 章,无需任何自定义控件瞄准!)

    简单地说,没有必要仅仅为了控制模板中已经存在的属性而创建额外的按钮类。您可以使用数据绑定或附加属性等轻松地做到这一点,并且它将与 Blend 等工具更加兼容。

    为了说明这一点,这里有一个帮助类,用于您在示例代码中公开的两个属性:

    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>
    

    结果如下:

    我当然意识到我已经在每个模板中指定了边框,但也可以通过在边框内放置内容控件并使用数据模板设置内容来轻松删除。

    【讨论】:

    • 终于开始实施您的建议。它工作得很好(特别是对于一些更复杂的样式控件)。谢谢!
    【解决方案2】:

    实际情况是该样式实际上有一个 TextBlock 实例。当样式应用于第二个按钮时,TextBlock 实际上是重新设置为新控件的父级。您应该可以通过在 TextBlock 元素上设置 x:Shared="false" 来避免这种情况。

    【讨论】:

    • 不错!您的修复工作,但是 x:Shared 属性只能应用于 Style 本身,而不是 TextBlock 元素。但是将它应用于 Button1Style 解决了这个问题。谢谢。
    • 啊,是的。 x:Shared 没有出现太多,所以我不记得确切的用法。当你没有预料到的时候,它肯定会让人抓狂。
    • 同意。我不记得我以前是否遇到过需要它的情况,这就是为什么我对发生的事情感到困惑,以及为什么我在网上找不到任何与我遇到的问题相似的东西。
    • 虽然看起来有点丑陋,但您正在模板化孩子,然后在派生类中设置内容,然后必须设置 x:Shared="False" 才能撤消问题。这是在创建两个实际上不需要首先创建的自定义控件的基础上。如果您不打算使用正确的数据绑定(您确实应该这样做),那么为什么不在派生类中重新模板呢?
    • @MarkFeldman,我不太明白你的意思。你是说我不应该创建一个基类,其唯一目的是暴露边框的圆角半径,以便我可以制作都需要圆角边缘但边框内的内容会有所不同的按钮?如果您认为样式继承太过分了,我想我可以为每个按钮样式中的基本内容重复 的内容。只是我需要至少两种按钮类型(可能更多),它们的内容截然不同,但都需要围绕它们的圆形边框。
    猜你喜欢
    • 1970-01-01
    • 2015-08-23
    • 1970-01-01
    • 2012-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多