Files
Shrlalgo.RvKits/WPFluent/Controls/SplitButton/SplitButton.cs
2025-04-24 20:56:44 +08:00

161 lines
5.3 KiB
C#

using System.Windows.Controls;
using System.Windows.Controls.Primitives;
// ReSharper disable once CheckNamespace
namespace WPFluent.Controls;
/// <summary>
/// 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.
/// </summary>
[TemplatePart(Name = TemplateElementToggleButton, Type = typeof(ToggleButton))]
public class SplitButton : Button
{
/// <summary>
/// Template element represented by the <c>ToggleButton</c> name.
/// </summary>
private const string TemplateElementToggleButton = "PART_ToggleButton";
/// <summary>
/// Identifies the <see cref="Flyout"/> dependency property.
/// </summary>
public static readonly DependencyProperty FlyoutProperty = DependencyProperty.Register(
nameof(Flyout),
typeof(object),
typeof(SplitButton),
new PropertyMetadata(null, OnFlyoutChanged));
/// <summary>
/// Identifies the <see cref="IsDropDownOpen"/> dependency property.
/// </summary>
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); }
/// <summary>
/// This method is invoked when the <see cref="FlyoutProperty"/> changes.
/// </summary>
/// <param name="value">The new value of <see cref="FlyoutProperty"/>.</param>
protected virtual void OnFlyoutChanged(object value)
{
if (value is ContextMenu contextMenu)
{
_contextMenu = contextMenu;
_contextMenu.Opened += OnContextMenuOpened;
_contextMenu.Closed += OnContextMenuClosed;
}
}
/// <summary>
/// This method is invoked when the <see cref="IsDropDownOpenProperty"/> changes.
/// </summary>
/// <param name="currentValue">The new value of <see cref="IsDropDownOpenProperty"/>.</param>
protected virtual void OnIsDropDownOpenChanged(bool currentValue)
{
}
/// <summary>
/// Triggered when the control is unloaded. Releases resource bindings.
/// </summary>
protected virtual void ReleaseTemplateResources()
{ SplitButtonToggleButton.Click -= OnSplitButtonToggleButtonOnClick; }
/// <summary>
/// Gets or sets control responsible for toggling the drop-down button.
/// </summary>
protected ToggleButton SplitButtonToggleButton { get; set; } = null!;
/// <inheritdoc/>
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)}");
}
}
/// <summary>
/// Gets or sets the flyout associated with this button.
/// </summary>
[Bindable(true)]
public object Flyout { get => GetValue(FlyoutProperty); set => SetValue(FlyoutProperty, value); }
/// <summary>
/// Gets or sets a value indicating whether the drop-down for a button is currently open.
/// </summary>
/// <returns>
/// <see langword="true"/> if the drop-down is open; otherwise, <see langword="false"/>. The default is <see
/// langword="false"/>.
/// </returns>
[Bindable(true)]
[Browsable(false)]
[Category("Appearance")]
public bool IsDropDownOpen
{
get => (bool)GetValue(IsDropDownOpenProperty);
set => SetValue(IsDropDownOpenProperty, value);
}
}