原文:
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的控件,否则,该自定义控件部分功能将肯能有所丢失。