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