wpf 自定义按钮最佳方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13274956/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 06:06:49  来源:igfitidea点击:

wpf custom button best approach

wpfbuttonuser-controls

提问by Andrei

I want to create a custom Buttoninside WPF. Of course, the button will be an UserControl and it will contain many visual elements (like stroke, highlight, shadow, glow, images etc.).

我想Button在 WPF 中创建一个自定义。当然,按钮将是一个 UserControl,它将包含许多视觉元素(如笔触、高光、阴影、发光、图像等)。

The problem is that if I use DependencyProperties and bind them in XAML I won't be able to see the result at DesignTime (I've tried to implement IsInDesignMode method but for a certain reason that I can't understand my VS just crashes when I use this method on UserControls, otherwise it works just fine) and this is definitely not good.

问题是,如果我使用 DependencyProperties 并在 XAML 中绑定它们,我将无法在 DesignTime 看到结果(我已经尝试实现 IsInDesignMode 方法,但由于某种原因,我无法理解我的 VS 只是崩溃时我在 UserControls 上使用这种方法,否则它工作得很好),这绝对不好。

So I am thinking about not using XAML at all and do all my work in the code behind.

所以我在考虑根本不使用 XAML,而是在后面的代码中完成我的所有工作。

What do you guys think?

你们有什么感想?

回答by DRapp

Like you, when I was getting started and wanted to understand how / what was going on and working with templates, it took a lot of trial and error. Hopefully my research and some step-by-step components can help you customize to your liking and KNOWING where things are coming from.

像您一样,当我刚开始并想了解模板是如何/发生了什么以及如何使用模板时,需要进行大量的反复试验。希望我的研究和一些循序渐进的组件可以帮助您根据自己的喜好进行自定义并了解事物的来源。

First, when trying to understand how a new "template style" will work, I created a simple stand-alone WPF app ("AMS") for my Any Manipulating Styles. This way, I don't have to wait forever to see what something will look like during trial / error with the rest of my primary project and themes.

首先,在尝试了解新的“模板样式”将如何工作时,我为我的任何操作样式创建了一个简单的独立 WPF 应用程序(“AMS”)。这样,我不必永远等待,看看我的主要项目和主题的其余部分在试验/错误期间会是什么样子。

From that, I created a new WPF Window called "TestingStyles". Save/Compile, run, no problem.

从那以后,我创建了一个名为“TestingStyles”的新 WPF 窗口。保存/编译,运行,没问题。

Now, in the "VIEW CODE" of the TestingStyles window, I have put whatever I am playing with for a custom class... To help show the step-by-step, I've created the following:

现在,在 TestingStyles 窗口的“查看代码”中,我已经将我正在使用的任何内容放入自定义类......为了帮助逐步显示,我创建了以下内容:

namespace AMS
{
    /// <summary>
    /// Interaction logic for TestingStyles.xaml
    /// </summary>
    public partial class TestingStyles : Window
    {
        public TestingStyles()
        {
            InitializeComponent();
        }
    }

    // Enumerator for a custom property sample...
    public enum HowToShowStatus
    {
        ShowNothing,
        ShowImage1
    }


    public class YourCustomButtonClass : Button
    {
        public YourCustomButtonClass()
        {
            // auto-register any "click" will call our own custom "click" handler
            // which will change the status...  This could also be done to simplify
            // by only changing visibility, but shows how you could apply via other
            // custom properties too.
            Click += MyCustomClick;
        }

        protected void MyCustomClick(object sender, RoutedEventArgs e)
        {
            if( this.ShowStatus == HowToShowStatus.ShowImage1 )
                this.ShowStatus = HowToShowStatus.ShowNothing;
            else
                this.ShowStatus = HowToShowStatus.ShowImage1;
        }


        public static readonly DependencyProperty ShowStatusProperty =
              DependencyProperty.Register("ShowStatus", typeof(HowToShowStatus),
              typeof(YourCustomButtonClass), new UIPropertyMetadata(HowToShowStatus.ShowNothing));

        public HowToShowStatus ShowStatus
        {
            get { return (HowToShowStatus)GetValue(ShowStatusProperty); }
            set { SetValue(ShowStatusProperty, value); }
        }
    }

}

As you can see, the custom "Button" class, I have at the bottom outside the default TestingStyles : Window declaration... so its all in the same "Project".

正如您所看到的,自定义“按钮”类,我在默认 TestingStyles 之外的底部有:Window 声明......所以它都在同一个“项目”中。

In this XAML sample, I make reference to a "TaskComplete.png" graphic file (which should just for sample purposes, add directly to the project... Even if a simple smiley face for sample purposes). So, create such a simple .png file... even by using Microsoft Paint and drawing a circle with eyes and smile. Save into the project at the root (get into pathing stuff later, get it working first).

在这个 XAML 示例中,我引用了一个“TaskComplete.png”图形文件(它应该只是为了示例目的,直接添加到项目中......即使是一个简单的笑脸示例)。所以,创建一个如此简单的 .png 文件......即使使用 Microsoft Paint 并用眼睛和微笑画一个圆圈。在根目录下保存到项目中(稍后进入路径,先让它工作)。

Save and recompile the project, so the project knows publicly what the new "class" (button) is when you start to define the XAML template.

保存并重新编译项目,以便项目在您开始定义 XAML 模板时公开知道新的“类”(按钮)是什么。

Now, back to the TestingStyles designer and get it into split screen so you can see both the designer and the XAML markup... and Just replace with the following...

现在,回到 TestingStyles 设计器并将其置于分屏中,这样您就可以看到设计器和 XAML 标记...只需替换为以下...

<Window x:Class="AMS.TestingStyles"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:AMS"
        Title="TestingStyles" Height="300" Width="300" >

    <Window.Resources>
        <!-- Build a "Style" based on an anticpated target control type of YourCustomButtonClass.
            per the "my:" reference, the "my" is an "alias" to the xmlsn:my in the declaration above,
            so the XAML knows which library to find such control.  In this case, I've included within
            the actual forms's 'View Code' as a class at the bottom.  

            As soon as you assign an "x:Key" reference, its like its telling XAML to make this a PRIVATE
            style so you don't reference it explicitly (yet)
        -->
        <Style TargetType="my:YourCustomButtonClass" x:Key="keyYourCustomButtonClass">
            <!-- put whatever normal "settings" you want for your common look / feel, color -->
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="HorizontalContentAlignment" Value="Center"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Padding" Value="0,0,1,1"/>
            <Setter Property="Width" Value="100" />
            <Setter Property="Height" Value="30" />

            <!-- Now, for the template of the button.  Things can get really crazy here
              as you are now defining what you want the "button" to look like, borders, 
              content, etc. In this case, I have two borders to give the raise/sunken effect 
              of a button and it has its own colors -->
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button" >
                        <!-- The x:Name references used during triggers to know what it is "applying" changes to -->
                        <Border x:Name="BorderTopLeft"
                                BorderBrush="Gainsboro" 
                                BorderThickness="0,0,1.5,1.5">

                            <Border x:Name="BorderBottomRight"
                                BorderBrush="Gray" 
                                BorderThickness="1.5,1.5,0,0">
                                <!-- Now, what control  type do you want the button to have... 
                                    Ex: You could use a grid (as I have here), stack panels, etc -->
                                <Grid Background="LightBlue" >
                                    <!-- I'm defining as two columns wide, one row tall.  
                                        First column fixed width 20 pixels example for an image -->
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="20px" />
                                        <ColumnDefinition Width="*" />
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition />
                                    </Grid.RowDefinitions>

                                    <!-- Now, create the controls I want available within my "template".
                                        when assigned with "x:Name", thats like a property withing the template
                                        that triggers can associate and update to. -->
                                    <Image x:Name="btnImage" 
                                        Grid.Row="0" Grid.Column="0"
                                        Stretch="None"
                                        VerticalAlignment="Stretch" HorizontalAlignment="Stretch" 
                                        Source="TaskComplete.png"
                                        Visibility="Visible" />

                                    <!-- and also have the text for the button to show the user -->
                                    <TextBlock x:Name="txtNewBtn" 
                                        Grid.Row="0" Grid.Column="1"
                                        Padding="5"
                                        HorizontalAlignment="Left"
                                        VerticalAlignment="Center"
                                    Text="{TemplateBinding Content}" />
                                    <!-- The "{TemplateBinding Content}" means to set the text based on 
                                        the "CONTENT" property of the original button and not use a fixed value -->
                                </Grid>
                            </Border>
                        </Border>
                        <!-- Now, some triggers for the button itself... some can be property based, others data-based -->
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsPressed" Value="true">
                                <!-- What properties do we want to change when user CLICKS
                                    on the button, give the "EFFECT" of click down/up by
                                    changing the "Margin" and border thicknesses...  -->
                                <Setter Property="Margin" Value="1,1,0,0"/>
                                <!-- Notice the "TargetName" below referring to the x:Name I've applied in template above 
                                    so when the user clicks on the button, it changes the border thickness properties of
                                    each to give the effect of a normal button clicking.  I'm widening one border, shrinking other -->
                                <Setter TargetName="BorderTopLeft" Property="BorderThickness" Value="2.5,2.5,0,0"/>
                                <Setter TargetName="BorderBottomRight" Property="BorderThickness" Value="0,0,.5,.5"/>
                            </Trigger>

                            <!-- Here, I have a custome property on the class for "ShowStatus".  The binding is to itself
                                regardless of how many instances of this type of "button" are on a given form 
                                First trigger happens when the value is changed to "ShowNothing", but can also change 
                                when set to "ShowImage1" or other as you may need applicable
                            -->
                            <DataTrigger Binding="{Binding Path=ShowStatus, RelativeSource={RelativeSource Self}}" Value="ShowNothing">
                                <Setter TargetName="btnImage" Property="Visibility" Value="Hidden"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Path=ShowStatus, RelativeSource={RelativeSource Self}}" Value="ShowImage1">
                                <Setter TargetName="btnImage" Property="Visibility" Value="Visible"/>
                            </DataTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- NOW, we can expose any instance of "YourCustomButtonClass" button to use the style based on definition above 
            any instance of such YourCustomButtonClass will automatically reflect this style / look -->
        <Style TargetType="my:YourCustomButtonClass" BasedOn="{StaticResource keyYourCustomButtonClass}" />

    </Window.Resources>

    <Grid>
        <my:YourCustomButtonClass Content="Button" VerticalAlignment="Top" ShowStatus="ShowImage1" />
    </Grid>
</Window>

This should give you a great jump-start to defining your own templates and how the elements start to tie together. Once this sample is running, as you change any colors, margins, padding, etc to the template, you'll immediately see the visual impact that component has on the control.

这应该给你一个很好的开始来定义你自己的模板以及元素如何开始联系在一起。一旦此示例运行,当您更改模板的任何颜色、边距、填充等时,您将立即看到该组件对控件的视觉影响。

Have fun and don't bang your head too much against the wall...

玩得开心,不要把头撞到墙上太多...

BTW, once this is working, then you can take the style element stuff within the

顺便说一句,一旦这有效,那么您就可以在

<Window.Resources>
</Window.Resources> 

and put it into a Windows Resource Dictionary to make it global to your project instead of just this test form.

并将其放入 Windows 资源字典中,使其对您的项目具有全局性,而不仅仅是这个测试表单。