首页 > 解决方案 > 将两个 UserControl 组合成一个具有两种状态的正确方法是什么?

问题描述

我已经在 Internet 和 StackOverflow 上进行了一些搜索。为什么我需要这个:我有两个UserControl叫做PlusButtonand的 s MinusButton,它们都有通用的 XAML 和通用的代码隐藏。我想使用一个公共基类,理想情况下它也有 XAML 和代码隐藏。

我已经看到了此处发布的答案,但我在 XAML 方面的经验不足以应用该解决方案。我会很高兴有一个指向官方文档页面的链接,该页面可以让我了解我需要做什么。我习惯了代码隐藏,因为我也使用 WinForms,所以我不介意是否必须进行代码隐藏。

我比 XAML 更容易理解代码隐藏,所以我有很多可能在 XAML 中做得更好的代码隐藏。

如果我使用此处发布的解决方案,我会收到编译器错误:

  • cs_wpf_test_1.ArrowButton不能是 XAML 文件的根,因为它是使用 XAML 定义的。第 1 行位置 20。
  • 警告 CS0108PlusButton.InitializeComponent()隐藏继承的成员ArrowButton.InitializeComponent()。如果打算隐藏,请使用 new 关键字。

我得到上面列出的两个编译器错误,我不知道这些错误的最正确解决方法是什么。我希望它易于维护。

下面的代码是我遇到这些编译器错误之前的代码版本。

PlusButton.xaml

<UserControl x:Class="cs_wpf_test_1.PlusButton"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:cs_wpf_test_1"
             mc:Ignorable="d" 
             d:DesignHeight="120" d:DesignWidth="175"
             Loaded="UserControl_Loaded">
    <Button Name="MyButton" Focusable="False" Padding="0,0,0,0">
        <Button.Template>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border Background="{TemplateBinding Background}"
                         BorderBrush="{TemplateBinding BorderBrush}"
                         BorderThickness="{TemplateBinding BorderThickness}">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates" CurrentStateChanged="CommonStates_CurrentStateChanged">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="MouseOver" />
                            <VisualState x:Name="Pressed" />
                            <VisualState x:Name="Disabled" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                Name="MyCanvas">
                            <Polyline Stroke="Blue" Name="MyPolyline">
                                <Polyline.Points>
                                    <PointCollection>
                                        <Point X="5" Y="95" />
                                        <Point X="95" Y="95" />
                                        <Point X="50" Y="5" />
                                        <Point X="5" Y="95" />
                                    </PointCollection>
                                </Polyline.Points>
                            </Polyline>
                        </Canvas>
                        <Canvas HorizontalAlignment="Center" VerticalAlignment="Center"
                                Width="100" Height="100" Name="MySecondCanvas">
                            <Line Stroke="Black" X1="50" Y1="10"
                                  X2="50" Y2="90"
                  Name="MySignLine1" StrokeThickness="4"/>
                            <Line Stroke="Black" X1="10" Y1="50"
                                  X2="90" Y2="50"
                  Name="MySignLine2" StrokeThickness="4"/>
                        </Canvas>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Button.Template>
    </Button>
</UserControl>

PlusButton.xaml.cs

public partial class PlusButton : UserControl
{
    internal Polyline MyTemplatePolyline;
    internal Line MyTemplateSignLine1, MyTemplateSignLine2;
    internal Canvas MyTemplateCanvas,
        MyTemplateSecondCanvas;

    public PlusButton()
    {
        InitializeComponent();

        MyButton.Margin = new Thickness(
            -MyButton.BorderThickness.Left,
            -MyButton.BorderThickness.Top,
            -MyButton.BorderThickness.Right,
            -MyButton.BorderThickness.Bottom);
    }

    internal void SetPseudofocused(bool p)
    {
        if (p)
        {
            BorderBrush = Brushes.Blue;
        }
        else
        {
            BorderBrush = Brushes.Transparent;
        }
    }

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        MyButton.ApplyTemplate();

        MyTemplatePolyline = (Polyline)MyButton.Template.FindName("MyPolyline", MyButton);
        MyTemplateSignLine1 = (Line)MyButton.Template.FindName("MySignLine1", MyButton);
        MyTemplateSignLine2 = (Line)MyButton.Template.FindName("MySignLine2", MyButton);
        MyTemplateCanvas = (Canvas)MyButton.Template.FindName("MyCanvas", MyButton);
        MyTemplateSecondCanvas = (Canvas)MyButton.Template.FindName("MySecondCanvas", MyButton);

        UpdateMyLayout();
    }

    private void CommonStates_CurrentStateChanged(object sender, VisualStateChangedEventArgs e)
    {
        var btn = e.Control as Button;

        if (e.NewState.Name == "MouseOver")
        {
            btn.Background = Brushes.White;
        }
        else if (e.NewState.Name == "Pressed")
        {
            btn.Background = Brushes.LightBlue;
        }
        else if (e.NewState.Name == "Disabled")
        {
            btn.Background = Brushes.Gray;
        }
        else
        {
            btn.Background = (Brush)MyButton.FindResource(SystemColors.ControlBrushKey);
        }
    }

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);

        if (e.Property == WidthProperty || e.Property == HeightProperty || e.Property == BorderThicknessProperty)
        {
            UpdateMyLayout();
        }
    }

    internal void UpdateMyLayout()
    {
        if (MyTemplateCanvas == null)
        {
            return;
        }

        MyTemplateCanvas.Height = Height;
        MyTemplateCanvas.Width = Width;

        double h = ActualHeight - BorderThickness.Top -
            BorderThickness.Bottom;
        double w = ActualWidth - BorderThickness.Left -
            BorderThickness.Right;

        MyTemplatePolyline.Points.Clear();
        MyTemplatePolyline.Points.Add(new Point(5, h - 5));
        MyTemplatePolyline.Points.Add(new Point(w - 5, h - 5));
        MyTemplatePolyline.Points.Add(new Point(w / 2, 5));
        MyTemplatePolyline.Points.Add(new Point(5, h - 5));

        h = MyTemplateSecondCanvas.ActualHeight;
        w = MyTemplateSecondCanvas.ActualWidth;

        double ltrbPadding = h / 3;

        double l1 = h - 2 * ltrbPadding;
        double l2 = w - 2 * ltrbPadding;

        double l = Math.Min(l1, l2);

        // draw a cross with two lines:
        MyTemplateSignLine1.X1 = l / 2d + ltrbPadding;
        MyTemplateSignLine1.X2 = l / 2d + ltrbPadding;
        MyTemplateSignLine1.Y1 = ltrbPadding;
        MyTemplateSignLine1.Y2 = l + ltrbPadding;

        MyTemplateSignLine2.X1 = ltrbPadding;
        MyTemplateSignLine2.X2 = l + ltrbPadding;
        MyTemplateSignLine2.Y1 = ltrbPadding + l / 2d;
        MyTemplateSignLine2.Y2 = ltrbPadding + l / 2d;

        // update focus border size:
        double v = ActualHeight / 25d;
        BorderThickness = new Thickness(v, v, v, v);
    }
}

减号按钮.xaml

与第一个 XAML 文件相同,但MySecondCanvas的 XAML 仅包含一个Line.

减号按钮.xaml.cs

与第一个.cs文件相同,但:

  1. 它不创建或使用MyTemplateSignLine1and MyTemplateSignLine2,它只是使用MyTemplateSignLine.
  2. MyTemplatePolyline包含看起来像向下箭头的其他点(第一个箭头,倒置)。
  3. MyTemplateSignLine就像MyTemplateSignLine2来自其他 XAML 文件和代码隐藏一样。

标签: c#wpfxamlinheritanceuser-controls

解决方案


WPF 中的继承是一件棘手的事情,如果您只想修改代码,您可以继承 CS 文件中的控件,并随意覆盖方法,但如果您想修改 xaml,则可能会变得复杂。

但是我相信 WPF 方式更倾向于样式/模板。

添加资源字典相当容易(这里是如何)。如果你想改变按钮背景,你可以添加一个在触发时改变它的样式。您可能还希望删除按钮的默认行为,您可以通过覆盖模板来实现。

下面是一个按钮样式(负责背景颜色更改)和模板(没有默认按钮行为)的示例,您可以重用它们,也可以通过 ContentPresenter 更改内容:

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary>
                <Style x:Key="ColorfulButtonStyle" TargetType="Button">
                    <Style.Triggers>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter Property="Background" Value="Pink"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="False">
                            <Setter Property="Background" Value="Blue"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="Purple"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="False">
                            <Setter Property="Background" Value="green"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
                <ControlTemplate x:Key="EmptyButtonTemplate" TargetType="Button">
                    <Border Background="{TemplateBinding Background}" BorderThickness="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <ContentPresenter/>
                    </Border>
                </ControlTemplate>
            </ResourceDictionary>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

    <Button Template="{StaticResource EmptyButtonTemplate}" Style="{StaticResource ColorfulButtonStyle}" Width="30" Height="30" >
        <Ellipse HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="Black"/>
        <!--Replace the ellipse with the shape you like, defined as a control in code or in xaml-->
    </Button>

推荐阅读