wpf自定义按钮最佳方法

时间:2012-11-07 17:28:00

标签: wpf button user-controls

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

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

因此,我正在考虑根本不使用XAML,并在后面的代码中完成所有工作。

你们觉得怎么样?

1 个答案:

答案 0 :(得分:11)

和我一样,当我开始并想要了解如何/正在进行和使用模板时,需要花费大量的试验和错误。希望我的研究和一些循序渐进的组件可以帮助您根据自己的喜好进行定制,并了解事物的来源。

首先,当试图了解一个新的"模板样式"我会为我的Any Manipulating Styles创建一个简单的独立WPF应用程序(" AMS")。通过这种方式,我不必永远等待,看看在我的主要项目和主题的其余部分的试用/错误期间会出现什么样的情况。

由此,我创建了一个名为" TestingStyles"的新WPF窗口。保存/编译,运行,没问题。

现在,在" VIEW CODE"在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); }
        }
    }

}

正如您所看到的,自定义&#34;按钮&#34; class,我在默认的TestingStyles:Window声明之外的底部...所以它全部在同一个&#34; Project&#34;。

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

保存并重新编译项目,以便项目公开知道新的&#34;类&#34; (按钮)是您开始定义XAML模板时。

现在,回到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>

这应该为您提供了一个很好的启动,可以定义自己的模板以及元素如何组合在一起。运行此示例后,当您将任何颜色,边距,填充等更改为模板时,您将立即看到该组件对控件的视觉影响。

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

顺便说一句,一旦这个工作,那么你可以在

中获取样式元素
<Window.Resources>
</Window.Resources> 

并将其放入Windows资源字典中,使其成为您项目的全局,而不仅仅是此测试表单。