原文:

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;
}
}
}

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=""/>
</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=""
Stroke="{TemplateBinding Foreground}"
Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/> <Button x:Name="PART_DropDown"
Grid.Column=""
Margin="">
<Path Data="M0,0L3,3 6,0z"
Margin="0,1,0,0"
Grid.Column=""
Stroke="{TemplateBinding Foreground}"
Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button> </Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

说明的问题:

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

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

会由于路由冒泡到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
{
     <Style x:Key="easy5SplitButton"
TargetType="s:SplitButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="s:SplitButton">
。。。。。。。。。 <Button x:Name="PART_DropDown"
Grid.Column=""
Margin="">
<Path Data="M0,0L3,3 6,0z"
Margin="0,1,0,0"
Grid.Column=""
Stroke="{TemplateBinding Foreground}"
Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button> </Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

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

WPF SplitButton 的杂七杂八的更多相关文章

  1. wpf SplitButton

     SplitButton该控件除了本身Button 的功能外,还具有下拉菜单的功能,能够在按键右側加入下拉菜单控件: <SplitButton Content="..." ...

  2. 用WPF实现查找结果高亮显示

    概述 我们经常会遇到这样的需求:到数据库里查找一些关键字,把带这些关键字的记录返回显示在客户端上.但如果仅仅是单纯地把文本显示出来,那很不直观,用户不能很轻易地看到他们想找的内容,所以通常我们还要做到 ...

  3. 安装Extended WPF Toolkit

    Extended WPF Toolkit 可以说是WPF Toolkit 的一个补充,也包含了许多WPF 控件供开发者使用.本篇将介绍Extended WPF Toolkit 1.4.0 中新增的一些 ...

  4. (转)WPF学习资源整理

    由于笔者正在学习WPF,所以整理出网络中部分WPF的学习资源,希望对同样在学习WPF的朋友们有所帮助. 首推刘铁猛的<深入浅出WPF>系列博文 1.深入浅出WPF(1)——什么是WPFht ...

  5. 浅谈Fluent Ribbon 中的SplitButton

    Fluent Ribbon Control Suite 就不做介绍了,网上的例子比较多,类似Office2007及以后版本的图形界面(菜单栏).官网地址:https://github.com/flue ...

  6. WPF学习资源整理

    WPF(WindowsPresentation Foundation)是微软推出的基于Windows Vista的用户界面框架,属于.NET Framework 3.0的一部分.它提供了统一的编程模型 ...

  7. 《Dotnet9》系列-开源C# WPF控件库3《HandyControl》强力推荐

    大家好,我是Dotnet9小编,一个从事dotnet开发8年+的程序员.我最近开始写dotnet分享文章,希望能让更多人看到dotnet的发展,了解更多dotnet技术,帮助dotnet程序员应用do ...

  8. WinUI 3学习笔记(3)—— ComboBox & DropDownButton & SplitButton

    本篇想介绍相对小众但颇具使用价值的控件SplitButton,提到SplitButton难免会拿来与ComboBox进行比较,同时在WinUI 3的控件库中,还有一个默默无闻的DropDownButt ...

  9. WPF优秀组件推荐之MahApps

    概述 MahApps是一套基于WPF的界面组件,通过该组件,可以使用较小的开发成本实现一个相对很好的界面效果. 官方网站:MahApps.Metro - Home 开源代码:MahApps · Git ...

随机推荐

  1. Emmet的高级功能与使用技巧

    Emmet系列教程 前端开发利器Emmet的介绍 Emmet快速编写HTML代码 Emmet快速编写CSS样式 Emmet快速编写CSS样式 编写好HTML和CSS代码时,我们也需要修改或添加一些内容 ...

  2. 基于AspectJ自定义注解

    package com.aspectj.demo.aspect; import java.lang.annotation.ElementType; import java.lang.annotatio ...

  3. uva439 - Knight Moves(BFS求最短路)

    题意:8*8国际象棋棋盘,求马从起点到终点的最少步数. 编写时犯的错误:1.结构体内没构造.2.bfs函数里返回条件误写成起点.3.主函数里取行标时未注意书中的图. #include<iostr ...

  4. [GeekBand] 面向对象的设计模式(C++)(1)

    一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心. 这样,你就能一次又一次 地使用该方案而不必做重复劳动. 0. 从面向对象谈起 底层思维与抽象思维: 底层思维要求程序员&q ...

  5. 关于RadAsm中GetEnvironmentStrings的BUG。

    今天在asm中不通过msvcrt.inc调用c库. 所以.第一时间就在vc的lib中拷贝了libc.lib问价.加入工程后. 声明.调用如下: 然后.链接报错. libc.lib(crt0.obj) ...

  6. linux FTP 批量下载文件

    wget是一个从网络上自动下载文件的自由工具,支持通过HTTP.HTTPS.FTP三个最常见的TCP/IP协议下载,并可以使用HTTP代理.wget名称的由来是“World Wide Web”与“ge ...

  7. xamarin android——数据绑定到控件(四)

    本文为通过自定义列表适配器定义ListView,以上文为基础,基于ListActivity. 定义列表项布局,包含一个图片显示,标题和描述 <LinearLayout xmlns:android ...

  8. grails的插件

    今天来歪理邪说一下grails的插件. 有个问题让本人困惑了一段时间,插件是属于grails的,还是属于某个工程的?为什么会有这个问题呢,这涉及到grails插件的安装方式. grails的插件像是一 ...

  9. 解决Strict Standards: Only variables should be passed by reference

    这个错误发生在大家php调试程序用到一段代码里,那就是格式化显示出变量的函数functionrdump($arr)的第5行, 这段代码出自ecmall团队之手,但是ecmall已经很古董了,在php5 ...

  10. linux设备驱动模型(kobject与kset)

    Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述.换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要 ...