using System.Windows.Controls; using System.Windows.Controls.Primitives; // ReSharper disable once CheckNamespace namespace WPFluent.Controls; /// /// Represents a button with two parts that can be invoked separately. One part behaves like a standard button and the /// other part invokes a flyout. /// [TemplatePart(Name = TemplateElementToggleButton, Type = typeof(ToggleButton))] public class SplitButton : Button { /// /// Template element represented by the ToggleButton name. /// private const string TemplateElementToggleButton = "PART_ToggleButton"; /// /// Identifies the dependency property. /// public static readonly DependencyProperty FlyoutProperty = DependencyProperty.Register( nameof(Flyout), typeof(object), typeof(SplitButton), new PropertyMetadata(null, OnFlyoutChanged)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register( nameof(IsDropDownOpen), typeof(bool), typeof(SplitButton), new PropertyMetadata(false, OnIsDropDownOpenChanged)); private ContextMenu? _contextMenu; public SplitButton() { Unloaded += static (sender, _) => { var self = (SplitButton)sender; self.ReleaseTemplateResources(); }; } private static void OnFlyoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is SplitButton dropDownButton) { dropDownButton.OnFlyoutChanged(e.NewValue); } } private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is SplitButton dropDownButton) { dropDownButton.OnIsDropDownOpenChanged(e.NewValue is bool boolVal && boolVal); } } private void OnSplitButtonToggleButtonOnClick(object sender, RoutedEventArgs e) { if (sender is not ToggleButton || _contextMenu is null) { return; } _contextMenu.SetCurrentValue(MinWidthProperty, ActualWidth); _contextMenu.SetCurrentValue(ContextMenu.PlacementTargetProperty, this); _contextMenu.SetCurrentValue(ContextMenu.PlacementProperty, PlacementMode.Bottom); _contextMenu.SetCurrentValue(ContextMenu.IsOpenProperty, true); } protected virtual void OnContextMenuClosed(object sender, RoutedEventArgs e) { SetCurrentValue(IsDropDownOpenProperty, false); } protected virtual void OnContextMenuOpened(object sender, RoutedEventArgs e) { SetCurrentValue(IsDropDownOpenProperty, true); } /// /// This method is invoked when the changes. /// /// The new value of . protected virtual void OnFlyoutChanged(object value) { if (value is ContextMenu contextMenu) { _contextMenu = contextMenu; _contextMenu.Opened += OnContextMenuOpened; _contextMenu.Closed += OnContextMenuClosed; } } /// /// This method is invoked when the changes. /// /// The new value of . protected virtual void OnIsDropDownOpenChanged(bool currentValue) { } /// /// Triggered when the control is unloaded. Releases resource bindings. /// protected virtual void ReleaseTemplateResources() { SplitButtonToggleButton.Click -= OnSplitButtonToggleButtonOnClick; } /// /// Gets or sets control responsible for toggling the drop-down button. /// protected ToggleButton SplitButtonToggleButton { get; set; } = null!; /// public override void OnApplyTemplate() { base.OnApplyTemplate(); if (GetTemplateChild(TemplateElementToggleButton) is ToggleButton toggleButton) { SplitButtonToggleButton = toggleButton; SplitButtonToggleButton.Click -= OnSplitButtonToggleButtonOnClick; SplitButtonToggleButton.Click += OnSplitButtonToggleButtonOnClick; } else { throw new NullReferenceException( $"Element {nameof(TemplateElementToggleButton)} of type {typeof(ToggleButton)} not found in {typeof(SplitButton)}"); } } /// /// Gets or sets the flyout associated with this button. /// [Bindable(true)] public object Flyout { get => GetValue(FlyoutProperty); set => SetValue(FlyoutProperty, value); } /// /// Gets or sets a value indicating whether the drop-down for a button is currently open. /// /// /// if the drop-down is open; otherwise, . The default is . /// [Bindable(true)] [Browsable(false)] [Category("Appearance")] public bool IsDropDownOpen { get => (bool)GetValue(IsDropDownOpenProperty); set => SetValue(IsDropDownOpenProperty, value); } }