0%

原文:

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
{ ///


/// Implemetation of a Split Button ///

[TemplatePart(Name = “PART_DropDown”, Type = typeof (Button))]
[ContentProperty(“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; ///
/// Static Constructor ///

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
* */
///
/// OnApplyTemplate override, set up the click event for the dropdown if present in the template ///

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;
} ///
/// Handles the Base Buttons OnClick event ///

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
{ ///


/// Class used for the ComponentResourceKey ///

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>

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
19
20
21
22 </Setter.Value>
23
24

    <Style x:Key=”easy5SplitButton” TargetType=“s:SplitButton”>

。。。。

                       
                           

                           
                       

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

 等价于:

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

                            </ContentPresenter/>

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

其他控件没实验过。

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

复制代码

///


        /// OnApplyTemplate override, set up the click event for the dropdown if present in the template
        ///

        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
19
20
21
22 </Setter.Value>
23
24

复制代码

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