WPF SplitButton 的杂七杂八

原文:

http://www.codeproject.com/Articles/20612/A-WPF-SplitButton

SplitButton.cs

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Markup;

namespace Wpf.Controls
{
    /// <summary>
    /// Implemetation of a Split Button
    /// </summary>
    [TemplatePart(Name = "PART_DropDown", Type = typeof (Button))]
    [ContentProperty("Items")] //不用显示写<Items/>标签
    [DefaultProperty("Items")]
    public class SplitButton : Button
    {
        // AddOwner Dependency properties
        public static readonly DependencyProperty HorizontalOffsetProperty;
        public static readonly DependencyProperty IsContextMenuOpenProperty;
        public static readonly DependencyProperty ModeProperty;
        public static readonly DependencyProperty PlacementProperty;
        public static readonly DependencyProperty PlacementRectangleProperty;
        public static readonly DependencyProperty VerticalOffsetProperty;

        /// <summary>
        /// Static Constructor
        /// </summary>
        static SplitButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof (SplitButton), new FrameworkPropertyMetadata(typeof (SplitButton)));
            IsContextMenuOpenProperty = DependencyProperty.Register("IsContextMenuOpen", typeof(bool), typeof(SplitButton), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsContextMenuOpenChanged)));
            ModeProperty = DependencyProperty.Register("Mode", typeof(SplitButtonMode), typeof(SplitButton), new FrameworkPropertyMetadata(SplitButtonMode.Split));

            // AddOwner properties from the ContextMenuService class, we need callbacks from these properties
            // to update the Buttons ContextMenu properties
            PlacementProperty = ContextMenuService.PlacementProperty.AddOwner(typeof (SplitButton), new FrameworkPropertyMetadata(PlacementMode.Bottom, new PropertyChangedCallback(OnPlacementChanged)));
            PlacementRectangleProperty = ContextMenuService.PlacementRectangleProperty.AddOwner(typeof (SplitButton), new FrameworkPropertyMetadata(Rect.Empty, new PropertyChangedCallback(OnPlacementRectangleChanged)));
            HorizontalOffsetProperty = ContextMenuService.HorizontalOffsetProperty.AddOwner(typeof (SplitButton), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnHorizontalOffsetChanged)));
            VerticalOffsetProperty = ContextMenuService.VerticalOffsetProperty.AddOwner(typeof (SplitButton), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnVerticalOffsetChanged)));
        }


        /*
         * Overrides
         * 
        */
        /// <summary>
        /// OnApplyTemplate override, set up the click event for the dropdown if present in the template
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // set up the click event handler for the dropdown button
            ButtonBase dropDown = this.Template.FindName("PART_DropDown", this) as ButtonBase;
            if (dropDown != null)
                dropDown.Click += Dropdown_Click;
        }

        /// <summary>
        ///     Handles the Base Buttons OnClick event
        /// </summary>
        protected override void OnClick()
        {
            switch (Mode)
            {
                case SplitButtonMode.Dropdown:
                    OnDropdown();
                    break;
                
                default:
                    base.OnClick(); // forward on the Click event to the user
                    break;
            }
        }

        /*
         * Properties
         * 
        */


        /// <summary>
        /// The Split Button's Items property maps to the base classes ContextMenu.Items property
        /// </summary>
        public ItemCollection Items
        {
            get
            {
                EnsureContextMenuIsValid();
                return this.ContextMenu.Items;
            }
        }

        /*
         * DependencyProperty CLR wrappers
         * 
        */

        /// <summary>
        /// Gets or sets the IsContextMenuOpen property. 
        /// </summary>
        public bool IsContextMenuOpen
        {
            get { return (bool) GetValue(IsContextMenuOpenProperty); }
            set { SetValue(IsContextMenuOpenProperty, value); }
        }


        /// <summary>
        /// Placement of the Context menu
        /// </summary>
        public PlacementMode Placement
        {
            get { return (PlacementMode) GetValue(PlacementProperty); }
            set { SetValue(PlacementProperty, value); }
        }


        /// <summary>
        /// PlacementRectangle of the Context menu
        /// </summary>
        public Rect PlacementRectangle
        {
            get { return (Rect) GetValue(PlacementRectangleProperty); }
            set { SetValue(PlacementRectangleProperty, value); }
        }


        /// <summary>
        /// HorizontalOffset of the Context menu
        /// </summary>
        public double HorizontalOffset
        {
            get { return (double) GetValue(HorizontalOffsetProperty); }
            set { SetValue(HorizontalOffsetProperty, value); }
        }


        /// <summary>
        /// VerticalOffset of the Context menu
        /// </summary>
        public double VerticalOffset
        {
            get { return (double) GetValue(VerticalOffsetProperty); }
            set { SetValue(VerticalOffsetProperty, value); }
        }

        /// <summary>
        /// Defines the Mode of operation of the Button
        /// </summary>
        /// <remarks>
        ///     The SplitButton two Modes are
        ///     Split (default),    - the button has two parts, a normal button and a dropdown which exposes the ContextMenu
        ///     Dropdown            - the button acts like a combobox, clicking anywhere on the button opens the Context Menu
        /// </remarks>
        public SplitButtonMode Mode
        {
            get { return (SplitButtonMode) GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        /*
         * DependencyPropertyChanged callbacks
         * 
        */

        private static void OnIsContextMenuOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SplitButton s = (SplitButton) d;
            s.EnsureContextMenuIsValid();

            if (!s.ContextMenu.HasItems)
                return;

            bool value = (bool) e.NewValue;

            if (value && !s.ContextMenu.IsOpen)
                s.ContextMenu.IsOpen = true;
            else if (!value && s.ContextMenu.IsOpen)
                s.ContextMenu.IsOpen = false;
        }


        /// <summary>
        /// Placement Property changed callback, pass the value through to the buttons context menu
        /// </summary>
        private static void OnPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SplitButton s = d as SplitButton;
            if (s == null) return;

            s.EnsureContextMenuIsValid();
            s.ContextMenu.Placement = (PlacementMode) e.NewValue;
        }

        /// <summary>
        /// PlacementRectangle Property changed callback, pass the value through to the buttons context menu
        /// </summary>
        private static void OnPlacementRectangleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SplitButton s = d as SplitButton;
            if (s == null) return;

            s.EnsureContextMenuIsValid();
            s.ContextMenu.PlacementRectangle = (Rect) e.NewValue;
        }

        /// <summary>
        /// HorizontalOffset Property changed callback, pass the value through to the buttons context menu
        /// </summary>
        private static void OnHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SplitButton s = d as SplitButton;
            if (s == null) return;

            s.EnsureContextMenuIsValid();
            s.ContextMenu.HorizontalOffset = (double) e.NewValue;
        }

        /// <summary>
        /// VerticalOffset Property changed callback, pass the value through to the buttons context menu
        /// </summary>
        private static void OnVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SplitButton s = d as SplitButton;
            if (s == null) return;

            s.EnsureContextMenuIsValid();
            s.ContextMenu.VerticalOffset = (double) e.NewValue;
        }

        /*
         * Helper Methods
         * 
        */

        /// <summary>
        /// Make sure the Context menu is not null
        /// </summary>
        private void EnsureContextMenuIsValid()
        {
            if (this.ContextMenu == null)
            {
                this.ContextMenu = new ContextMenu();
                this.ContextMenu.PlacementTarget = this;
                this.ContextMenu.Placement = Placement;

                this.ContextMenu.Opened += ((sender, routedEventArgs) => IsContextMenuOpen = true);
                this.ContextMenu.Closed += ((sender, routedEventArgs) => IsContextMenuOpen = false);
            }
        }

        private void OnDropdown()
        {
            EnsureContextMenuIsValid();
            if (!this.ContextMenu.HasItems)
                return;

            this.ContextMenu.IsOpen = !IsContextMenuOpen; // open it if closed, close it if open
        }

        /*
         * Events
         * 
        */

        /// <summary>
        /// Event Handler for the Drop Down Button's Click event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Dropdown_Click(object sender, RoutedEventArgs e)
        {
            OnDropdown();
            e.Handled = true;
        }
    }
}
View Code

SplitButtonMode.cs

namespace Wpf.Controls
{
    public enum SplitButtonMode
    {
        Split, Dropdown, Button
    }
}

SplitButtonResources.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;

namespace Wpf.Controls
{
    /// <summary>
    /// Class used for the ComponentResourceKey
    /// </summary>
    public class SplitButtonResources
    {
        public static ComponentResourceKey VistaSplitButtonStyleKey
        {
            get { return new ComponentResourceKey(typeof(SplitButtonResources), "vistaSplitButtonStyle"); }
        }
    }
}

自己做的style:

Easy5SplitButtonSplitButtonColor.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="clr-namespace:Wpf.Controls;assembly=Wpf.SplitButton"
    xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">

    <Style x:Key="easy5SplitButton"
               TargetType="s:SplitButton">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="s:SplitButton">
                    <Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="16"/>
                        </Grid.ColumnDefinitions>

                        <Border x:Name="Bd" Padding="2,3"
                                HorizontalAlignment="Stretch" 
                                VerticalAlignment="Stretch"
                                BorderBrush="Black"
                                Background="Red">
                            <ContentPresenter>

                            </ContentPresenter>
                        </Border>

                        <Path x:Name="path"
                            Data="M0,0L3,3 6,0z" 
                            Margin="0,1,0,0" 
                            Grid.Column="1"
                            Stroke="{TemplateBinding Foreground}" 
                            Fill="{TemplateBinding Foreground}" 
                            HorizontalAlignment="Center" 
                            VerticalAlignment="Center"/>

                        <Button x:Name="PART_DropDown" 
                                    Grid.Column="1"
                                    Margin="0">
                            <Path Data="M0,0L3,3 6,0z" 
                                      Margin="0,1,0,0" 
                                      Grid.Column="1"
                                      Stroke="{TemplateBinding Foreground}" 
                                      Fill="{TemplateBinding Foreground}" 
                                      HorizontalAlignment="Center" 
                                      VerticalAlignment="Center"/>
                        </Button>

                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
View Code

说明的问题:

1.用的CustomerControl自定义控件,而不是UserControl自定义控件。

CustomerControl自定义控件,这种灵活了,不依赖xaml,如果依赖xaml,依照惯例,得标出 :

CustomerControl自定义控件依赖样式中的控件,命名惯例是加上前缀PART_

并在定义控件的时使用标签属性注明:

    [TemplatePart(Name = "PART_DropDown", Type = typeof (Button))]
    public class SplitButton : Button

代码如下:
    /// <summary>
    /// Implemetation of a Split Button
    /// </summary>
    [TemplatePart(Name = "PART_DropDown", Type = typeof (Button))]
    public class SplitButton : Button
    {
...
}

xaml中的样式中必须提供名为:PART_DropDown的类型为Button的控件,否则,该自定义控件部分功能将肯能有所丢失。

例如:

 1     <Style x:Key="easy5SplitButton"
 2                TargetType="s:SplitButton">
 3         <Setter Property="Template">
 4             <Setter.Value>
 5                 <ControlTemplate TargetType="s:SplitButton">
 6 。。。。。。。。。
 7 
 8                         <Button x:Name="PART_DropDown" 
 9                                     Grid.Column="1"
10                                     Margin="0">
11                             <Path Data="M0,0L3,3 6,0z" 
12                                       Margin="0,1,0,0" 
13                                       Grid.Column="1"
14                                       Stroke="{TemplateBinding Foreground}" 
15                                       Fill="{TemplateBinding Foreground}" 
16                                       HorizontalAlignment="Center" 
17                                       VerticalAlignment="Center"/>
18                         </Button>
19 
20                     </Grid>
21                 </ControlTemplate>
22             </Setter.Value>
23         </Setter>
24     </Style>

2.

    <Style x:Key="easy5SplitButton"
               TargetType
="s:SplitButton">

。。。。

                        <Border x:Name="Bd" Padding="2,3"
                                HorizontalAlignment="Stretch"
                                VerticalAlignment="Stretch"
                                BorderBrush="Black"
                                Background="Red">
                            <ContentPresenter>

                            </ContentPresenter>
                        </Border>

样式    <Style x:Key="easy5SplitButton"
               TargetType
="s:SplitButton">中的

 <ContentPresenter/>等价于:<ContentPresenter Content="{TemplateBinding Content}">

(实验证明:Button类型中的ControlTemplate 包含

                            </ContentPresenter/>

ContentPresenter的Content属性自动绑定到模板所在的父Button的Content,

其他控件没实验过。

3.当单击到SplitButton中的PART_DropDown按钮时,由于:

        /// <summary>
        /// OnApplyTemplate override, set up the click event for the dropdown if present in the template
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // set up the click event handler for the dropdown button
            ButtonBase dropDown = this.Template.FindName("PART_DropDown", this) as ButtonBase;
            if (dropDown != null)
                dropDown.Click += Dropdown_Click;
        }

所以引发:

        private void Dropdown_Click(object sender, RoutedEventArgs e)
        {
            OnDropdown();
            e.Handled = true;
        }
        private void OnDropdown()
        {
            EnsureContextMenuIsValid();
            if (!this.ContextMenu.HasItems)
                return;

            this.ContextMenu.IsOpen = !IsContextMenuOpen; // open it if closed, close it if open
        }

如果没有,即dropDown == null

1             // set up the click event handler for the dropdown button
2             ButtonBase dropDown = this.Template.FindName("PART_DropDown", this) as ButtonBase;
3             if (dropDown != null)
4                 dropDown.Click += Dropdown_Click;

会由于路由冒泡到PART_DropDown按钮的父控件SplitButton,被捕捉从而引发Click事件。

但是不会弹出下拉菜单,功能会缺失,即上面已经提到的:

CustomerControl自定义控件,这种灵活了,不依赖xaml,如果依赖xaml,得标出:

    /// <summary>
    /// Implemetation of a Split Button
    /// </summary>
    [TemplatePart(Name = "PART_DropDown", Type = typeof (Button))]
    public class SplitButton : Button
    {
 1     <Style x:Key="easy5SplitButton"
 2                TargetType="s:SplitButton">
 3         <Setter Property="Template">
 4             <Setter.Value>
 5                 <ControlTemplate TargetType="s:SplitButton">
 6 。。。。。。。。。
 7 
 8                         <Button x:Name="PART_DropDown" 
 9                                     Grid.Column="1"
10                                     Margin="0">
11                             <Path Data="M0,0L3,3 6,0z" 
12                                       Margin="0,1,0,0" 
13                                       Grid.Column="1"
14                                       Stroke="{TemplateBinding Foreground}" 
15                                       Fill="{TemplateBinding Foreground}" 
16                                       HorizontalAlignment="Center" 
17                                       VerticalAlignment="Center"/>
18                         </Button>
19 
20                     </Grid>
21                 </ControlTemplate>
22             </Setter.Value>
23         </Setter>
24     </Style>

xaml中的样式中必须提供名为:PART_DropDown的类型为Button的控件,否则,该自定义控件部分功能将肯能有所丢失。

原文地址:https://www.cnblogs.com/easy5weikai/p/3143423.html