// --------------------------------------------------------------------------------------------------------------------
//
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
//
//
// A Split Menu Button Control
//
// --------------------------------------------------------------------------------------------------------------------
namespace HandBrakeWPF.Controls.SplitButton
{
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
///
/// A Split Menu Button Control
///
[TemplatePart(Name = SplitElementName, Type = typeof(UIElement))]
public class SplitMenuButton : Button
{
#region Fields and Constants
///
/// The split element name.
///
private const string SplitElementName = "SplitElement";
///
/// The item source for the context menu
///
private readonly ObservableCollection itemSource = new ObservableCollection();
///
/// The is mouse over split element.
///
private bool IsMouseOverSplitElement;
///
/// The context menu.
///
private ContextMenu contextMenu;
///
/// The context menu initial offset.
///
private Point contextMenuInitialOffset;
///
/// The logical child.
///
private DependencyObject logicalChild;
///
/// The split element.
///
private UIElement splitElement;
#endregion
#region Constructors and Destructors
///
/// Initializes a new instance of the class.
///
public SplitMenuButton()
{
this.DefaultStyleKey = typeof(SplitMenuButton);
}
#endregion
#region Public Properties
///
/// Gets the ItemSource for the Context Menu
///
public Collection ItemSource
{
get
{
return this.itemSource;
}
}
#endregion
#region Public Methods and Operators
///
/// Called when the template is changed.
///
public override void OnApplyTemplate()
{
// Unhook existing handlers
if (this.splitElement != null)
{
this.splitElement.MouseEnter -= this.SplitElement_MouseEnter;
this.splitElement.MouseLeave -= this.SplitElement_MouseLeave;
this.splitElement = null;
}
if (this.contextMenu != null)
{
this.contextMenu.Opened -= this.ContextMenu_Opened;
this.contextMenu.Closed -= this.ContextMenu_Closed;
this.contextMenu = null;
}
if (this.logicalChild != null)
{
this.RemoveLogicalChild(this.logicalChild);
this.logicalChild = null;
}
// Apply new template
base.OnApplyTemplate();
// Hook new event handlers
this.splitElement = this.GetTemplateChild(SplitElementName) as UIElement;
if (this.splitElement != null)
{
this.splitElement.MouseEnter += this.SplitElement_MouseEnter;
this.splitElement.MouseLeave += this.SplitElement_MouseLeave;
this.contextMenu = ContextMenuService.GetContextMenu(this.splitElement);
if (this.contextMenu != null)
{
// Add the ContextMenu as a logical child (for DataContext and RoutedCommands)
this.contextMenu.Visibility = Visibility.Collapsed;
this.contextMenu.IsOpen = true;
DependencyObject current = this.contextMenu;
do
{
this.logicalChild = current;
current = LogicalTreeHelper.GetParent(current);
}
while (null != current);
this.contextMenu.IsOpen = false;
this.AddLogicalChild(this.logicalChild);
this.contextMenu.Opened += this.ContextMenu_Opened;
this.contextMenu.Closed += this.ContextMenu_Closed;
}
}
}
#endregion
#region Methods
///
/// Called when the Button is clicked.
///
protected override void OnClick()
{
if (this.IsMouseOverSplitElement)
{
this.OpenButtonMenu();
}
else
{
base.OnClick();
}
}
///
/// Called when a key is pressed.
///
///
/// The e.
///
protected override void OnKeyDown(KeyEventArgs e)
{
if (null == e)
{
throw new ArgumentNullException("e");
}
if (Key.Down == e.Key || Key.Up == e.Key)
{
this.Dispatcher.BeginInvoke((Action)this.OpenButtonMenu);
}
else
{
base.OnKeyDown(e);
}
}
///
/// The open button menu.
///
protected void OpenButtonMenu()
{
if ((0 < this.ItemSource.Count) && (null != this.contextMenu))
{
this.contextMenu.HorizontalOffset = 0;
this.contextMenu.VerticalOffset = 0;
this.contextMenu.Visibility = Visibility.Visible;
this.contextMenu.IsOpen = true;
}
}
///
/// The context menu closed.
///
///
/// The sender.
///
///
/// The RoutedEventArgs.
///
private void ContextMenu_Closed(object sender, RoutedEventArgs e)
{
this.LayoutUpdated -= this.SplitButton_LayoutUpdated;
this.Focus();
}
///
/// The context menu opened.
///
///
/// The sender.
///
///
/// The RoutedEventArgs.
///
private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
this.contextMenuInitialOffset = this.TranslatePoint(new Point(0, this.ActualHeight), this.contextMenu);
this.UpdateContextMenuOffsets();
this.LayoutUpdated += this.SplitButton_LayoutUpdated;
}
///
/// The split button layout updated.
///
///
/// The sender.
///
///
/// The EventArgs.
///
private void SplitButton_LayoutUpdated(object sender, EventArgs e)
{
this.UpdateContextMenuOffsets();
}
///
/// The split element_ mouse enter.
///
///
/// The sender.
///
///
/// The MouseEventArgs.
///
private void SplitElement_MouseEnter(object sender, MouseEventArgs e)
{
this.IsMouseOverSplitElement = true;
}
///
/// The split element mouse leave.
///
///
/// The sender.
///
///
/// The MouseEventArgs
///
private void SplitElement_MouseLeave(object sender, MouseEventArgs e)
{
this.IsMouseOverSplitElement = false;
}
///
/// The update context menu offsets.
///
private void UpdateContextMenuOffsets()
{
var currentOffset = new Point();
Point desiredOffset = this.contextMenuInitialOffset;
this.contextMenu.HorizontalOffset = desiredOffset.X - currentOffset.X;
this.contextMenu.VerticalOffset = desiredOffset.Y - currentOffset.Y;
if (FlowDirection.RightToLeft == this.FlowDirection)
{
this.contextMenu.HorizontalOffset *= -1;
}
}
#endregion
}
}