csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
701 lines
27 KiB
701 lines
27 KiB
using System;
|
|
using System.Reactive.Disposables;
|
|
using System.Windows.Input;
|
|
using Avalonia.Controls.Metadata;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.Input;
|
|
using Avalonia.Interactivity;
|
|
using Avalonia.LogicalTree;
|
|
|
|
namespace Avalonia.Controls
|
|
{
|
|
/// <summary>
|
|
/// A button with primary and secondary parts that can each be pressed separately.
|
|
/// The primary part behaves like a <see cref="Button"/> and the secondary part opens a flyout.
|
|
/// </summary>
|
|
[PseudoClasses(
|
|
":disabled",
|
|
":secondary-button-right",
|
|
":secondary-button-span",
|
|
":checked-flyout-open",
|
|
":flyout-open",
|
|
":checked-touch-pressed",
|
|
":checked",
|
|
":checked-primary-pressed",
|
|
":checked-primary-pointerover",
|
|
":checked-secondary-pressed",
|
|
":checked-secondary-pointerover",
|
|
":touch-pressed",
|
|
":primary-pressed",
|
|
":primary-pointerover",
|
|
":secondary-pressed",
|
|
":secondary-pointerover")]
|
|
public class SplitButton : ContentControl, ICommandSource
|
|
{
|
|
/// <summary>
|
|
/// Raised when the user presses the primary part of the <see cref="SplitButton"/>.
|
|
/// </summary>
|
|
public event EventHandler<RoutedEventArgs> Click
|
|
{
|
|
add => AddHandler(ClickEvent, value);
|
|
remove => RemoveHandler(ClickEvent, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the <see cref="Click"/> event.
|
|
/// </summary>
|
|
public static readonly RoutedEvent<RoutedEventArgs> ClickEvent =
|
|
RoutedEvent.Register<SplitButton, RoutedEventArgs>(
|
|
nameof(Click),
|
|
RoutingStrategies.Bubble);
|
|
|
|
/// <summary>
|
|
/// Defines the <see cref="Command"/> property.
|
|
/// </summary>
|
|
public static readonly DirectProperty<SplitButton, ICommand> CommandProperty =
|
|
AvaloniaProperty.RegisterDirect<SplitButton, ICommand>(
|
|
nameof(Command),
|
|
splitButton => splitButton.Command,
|
|
(splitButton, command) => splitButton.Command = command,
|
|
enableDataValidation: true);
|
|
|
|
/// <summary>
|
|
/// Defines the <see cref="CommandParameter"/> property.
|
|
/// </summary>
|
|
public static readonly StyledProperty<object> CommandParameterProperty =
|
|
AvaloniaProperty.Register<SplitButton, object>(
|
|
nameof(CommandParameter));
|
|
|
|
/// <summary>
|
|
/// Defines the <see cref="Flyout"/> property
|
|
/// </summary>
|
|
public static readonly StyledProperty<FlyoutBase> FlyoutProperty =
|
|
AvaloniaProperty.Register<SplitButton, FlyoutBase>(
|
|
nameof(Flyout));
|
|
|
|
private ICommand _Command;
|
|
|
|
private Button _primaryButton = null;
|
|
private Button _secondaryButton = null;
|
|
|
|
private bool _commandCanExecute = true;
|
|
protected bool _hasLoaded = false;
|
|
private bool _isAttachedToLogicalTree = false;
|
|
private bool _isFlyoutOpen = false;
|
|
private bool _isKeyDown = false;
|
|
private PointerType _lastPointerType = PointerType.Mouse;
|
|
|
|
private CompositeDisposable _buttonPropertyChangedDisposable;
|
|
private IDisposable _flyoutPropertyChangedDisposable;
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Constructor / Destructors
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SplitButton"/> class.
|
|
/// </summary>
|
|
public SplitButton()
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Properties
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="ICommand"/> invoked when the primary part is pressed.
|
|
/// </summary>
|
|
public ICommand Command
|
|
{
|
|
get => _Command;
|
|
set => SetAndRaise(CommandProperty, ref _Command, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a parameter to be passed to the <see cref="Command"/>.
|
|
/// </summary>
|
|
public object CommandParameter
|
|
{
|
|
get => GetValue(CommandParameterProperty);
|
|
set => SetValue(CommandParameterProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="FlyoutBase"/> that is shown when the secondary part is pressed.
|
|
/// </summary>
|
|
public FlyoutBase Flyout
|
|
{
|
|
get => GetValue(FlyoutProperty);
|
|
set => SetValue(FlyoutProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the button is currently checked.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This property exists only for the derived <see cref="ToggleSplitButton"/> and is
|
|
/// unused (set to false) within <see cref="SplitButton"/>. Doing this allows the
|
|
/// two controls to share a default style.
|
|
/// </remarks>
|
|
internal virtual bool InternalIsChecked => false;
|
|
|
|
/// <inheritdoc/>
|
|
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Methods
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
/// <inheritdoc/>
|
|
public void CanExecuteChanged(object sender, EventArgs e)
|
|
{
|
|
var canExecute = Command == null || Command.CanExecute(CommandParameter);
|
|
|
|
if (canExecute != _commandCanExecute)
|
|
{
|
|
_commandCanExecute = canExecute;
|
|
UpdateIsEffectivelyEnabled();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the visual state of the control by applying latest PseudoClasses.
|
|
/// </summary>
|
|
protected void UpdatePseudoClasses()
|
|
{
|
|
string pcDisabled = ":disabled";
|
|
|
|
string pcSecondaryButtonRight = ":secondary-button-right";
|
|
string pcSecondaryButtonSpan = ":secondary-button-span";
|
|
|
|
string pcCheckedFlyoutOpen = ":checked-flyout-open";
|
|
string pcFlyoutOpen = ":flyout-open";
|
|
|
|
string pcCheckedTouchPressed = ":checked-touch-pressed";
|
|
string pcChecked = ":checked";
|
|
string pcCheckedPrimaryPressed = ":checked-primary-pressed";
|
|
string pcCheckedPrimaryPointerOver = ":checked-primary-pointerover";
|
|
string pcCheckedSecondaryPressed = ":checked-secondary-pressed";
|
|
string pcCheckedSecondaryPointerOver = ":checked-secondary-pointerover";
|
|
|
|
string pcTouchPressed = ":touch-pressed";
|
|
string pcPrimaryPressed = ":primary-pressed";
|
|
string pcPrimaryPointerOver = ":primary-pointerover";
|
|
string pcSecondaryPressed = ":secondary-pressed";
|
|
string pcSecondaryPointerOver = ":secondary-pointerover";
|
|
|
|
// Place the secondary button
|
|
// These are mutually exclusive PseudoClasses handled separately from SetExclusivePseudoClass().
|
|
// They must be applied in addition to the others.
|
|
if (_lastPointerType == PointerType.Touch || _isKeyDown)
|
|
{
|
|
PseudoClasses.Set(pcSecondaryButtonSpan, true);
|
|
PseudoClasses.Set(pcSecondaryButtonRight, false);
|
|
}
|
|
else
|
|
{
|
|
PseudoClasses.Set(pcSecondaryButtonSpan, false);
|
|
PseudoClasses.Set(pcSecondaryButtonRight, true);
|
|
}
|
|
|
|
// Change the visual state
|
|
if (!IsEnabled)
|
|
{
|
|
SetExclusivePseudoClass(pcDisabled);
|
|
}
|
|
else if (_primaryButton != null && _secondaryButton != null)
|
|
{
|
|
if (_isFlyoutOpen)
|
|
{
|
|
if (InternalIsChecked)
|
|
{
|
|
SetExclusivePseudoClass(pcCheckedFlyoutOpen);
|
|
}
|
|
else
|
|
{
|
|
SetExclusivePseudoClass(pcFlyoutOpen);
|
|
}
|
|
}
|
|
// SplitButton and ToggleSplitButton share a template -- this section is driving the checked states for ToggleSplitButton.
|
|
else if (InternalIsChecked)
|
|
{
|
|
if (_lastPointerType == PointerType.Touch || _isKeyDown)
|
|
{
|
|
if (_primaryButton.IsPressed || _secondaryButton.IsPressed || _isKeyDown)
|
|
{
|
|
SetExclusivePseudoClass(pcCheckedTouchPressed);
|
|
}
|
|
else
|
|
{
|
|
SetExclusivePseudoClass(pcChecked);
|
|
}
|
|
}
|
|
else if (_primaryButton.IsPressed)
|
|
{
|
|
SetExclusivePseudoClass(pcCheckedPrimaryPressed);
|
|
}
|
|
else if (_primaryButton.IsPointerOver)
|
|
{
|
|
SetExclusivePseudoClass(pcCheckedPrimaryPointerOver);
|
|
}
|
|
else if (_secondaryButton.IsPressed)
|
|
{
|
|
SetExclusivePseudoClass(pcCheckedSecondaryPressed);
|
|
}
|
|
else if (_secondaryButton.IsPointerOver)
|
|
{
|
|
SetExclusivePseudoClass(pcCheckedSecondaryPointerOver);
|
|
}
|
|
else
|
|
{
|
|
SetExclusivePseudoClass(pcChecked);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_lastPointerType == PointerType.Touch || _isKeyDown)
|
|
{
|
|
if (_primaryButton.IsPressed || _secondaryButton.IsPressed || _isKeyDown)
|
|
{
|
|
SetExclusivePseudoClass(pcTouchPressed);
|
|
}
|
|
else
|
|
{
|
|
// Calling without a parameter is treated as ':normal' and will clear all other
|
|
// PseudoClasses returning to the default state
|
|
SetExclusivePseudoClass();
|
|
}
|
|
}
|
|
else if (_primaryButton.IsPressed)
|
|
{
|
|
SetExclusivePseudoClass(pcPrimaryPressed);
|
|
}
|
|
else if (_primaryButton.IsPointerOver)
|
|
{
|
|
SetExclusivePseudoClass(pcPrimaryPointerOver);
|
|
}
|
|
else if (_secondaryButton.IsPressed)
|
|
{
|
|
SetExclusivePseudoClass(pcSecondaryPressed);
|
|
}
|
|
else if (_secondaryButton.IsPointerOver)
|
|
{
|
|
SetExclusivePseudoClass(pcSecondaryPointerOver);
|
|
}
|
|
else
|
|
{
|
|
// Calling without a parameter is treated as ':normal' and will clear all other
|
|
// PseudoClasses returning to the default state
|
|
SetExclusivePseudoClass();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Local function to enable the specified PseudoClass and disable all others
|
|
// This more closely matches the VisualStateManager of WinUI where the default style originated
|
|
void SetExclusivePseudoClass(string pseudoClass = "")
|
|
{
|
|
PseudoClasses.Set(pcDisabled, pseudoClass == pcDisabled);
|
|
|
|
PseudoClasses.Set(pcCheckedFlyoutOpen, pseudoClass == pcCheckedFlyoutOpen);
|
|
PseudoClasses.Set(pcFlyoutOpen, pseudoClass == pcFlyoutOpen);
|
|
|
|
PseudoClasses.Set(pcCheckedTouchPressed, pseudoClass == pcCheckedTouchPressed);
|
|
PseudoClasses.Set(pcChecked, pseudoClass == pcChecked);
|
|
PseudoClasses.Set(pcCheckedPrimaryPressed, pseudoClass == pcCheckedPrimaryPressed);
|
|
PseudoClasses.Set(pcCheckedPrimaryPointerOver, pseudoClass == pcCheckedPrimaryPointerOver);
|
|
PseudoClasses.Set(pcCheckedSecondaryPressed, pseudoClass == pcCheckedSecondaryPressed);
|
|
PseudoClasses.Set(pcCheckedSecondaryPointerOver, pseudoClass == pcCheckedSecondaryPointerOver);
|
|
|
|
PseudoClasses.Set(pcTouchPressed, pseudoClass == pcTouchPressed);
|
|
PseudoClasses.Set(pcPrimaryPressed, pseudoClass == pcPrimaryPressed);
|
|
PseudoClasses.Set(pcPrimaryPointerOver, pseudoClass == pcPrimaryPointerOver);
|
|
PseudoClasses.Set(pcSecondaryPressed, pseudoClass == pcSecondaryPressed);
|
|
PseudoClasses.Set(pcSecondaryPointerOver, pseudoClass == pcSecondaryPointerOver);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the secondary button's flyout.
|
|
/// </summary>
|
|
protected void OpenFlyout()
|
|
{
|
|
if (Flyout != null)
|
|
{
|
|
Flyout.ShowAt(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes the secondary button's flyout.
|
|
/// </summary>
|
|
protected void CloseFlyout()
|
|
{
|
|
if (Flyout != null)
|
|
{
|
|
Flyout.Hide();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers all flyout events.
|
|
/// </summary>
|
|
/// <param name="flyout">The flyout to connect events to.</param>
|
|
private void RegisterFlyoutEvents(FlyoutBase flyout)
|
|
{
|
|
if (flyout != null)
|
|
{
|
|
flyout.Opened += Flyout_Opened;
|
|
flyout.Closed += Flyout_Closed;
|
|
|
|
_flyoutPropertyChangedDisposable = flyout.GetPropertyChangedObservable(FlyoutBase.PlacementProperty).Subscribe(Flyout_PlacementPropertyChanged);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Explicitly unregisters all flyout events.
|
|
/// </summary>
|
|
/// <param name="flyout">The flyout to disconnect events from.</param>
|
|
private void UnregisterFlyoutEvents(FlyoutBase flyout)
|
|
{
|
|
if (flyout != null)
|
|
{
|
|
flyout.Opened -= Flyout_Opened;
|
|
flyout.Closed -= Flyout_Closed;
|
|
|
|
_flyoutPropertyChangedDisposable?.Dispose();
|
|
_flyoutPropertyChangedDisposable = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Explicitly unregisters all events related to the two buttons in OnApplyTemplate().
|
|
/// </summary>
|
|
private void UnregisterEvents()
|
|
{
|
|
_buttonPropertyChangedDisposable?.Dispose();
|
|
_buttonPropertyChangedDisposable = null;
|
|
|
|
if (_primaryButton != null)
|
|
{
|
|
_primaryButton.Click -= PrimaryButton_Click;
|
|
|
|
_primaryButton.PointerEnter -= Button_PointerEvent;
|
|
_primaryButton.PointerLeave -= Button_PointerEvent;
|
|
_primaryButton.PointerPressed -= Button_PointerEvent;
|
|
_primaryButton.PointerReleased -= Button_PointerEvent;
|
|
_primaryButton.PointerCaptureLost -= Button_PointerCaptureLost;
|
|
}
|
|
|
|
if (_secondaryButton != null)
|
|
{
|
|
_secondaryButton.Click -= SecondaryButton_Click;
|
|
|
|
_secondaryButton.PointerEnter -= Button_PointerEvent;
|
|
_secondaryButton.PointerLeave -= Button_PointerEvent;
|
|
_secondaryButton.PointerPressed -= Button_PointerEvent;
|
|
_secondaryButton.PointerReleased -= Button_PointerEvent;
|
|
_secondaryButton.PointerCaptureLost -= Button_PointerCaptureLost;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// OnEvent Overridable Methods
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
|
{
|
|
UnregisterEvents();
|
|
UnregisterFlyoutEvents(Flyout);
|
|
|
|
_primaryButton = e.NameScope.Find<Button>("PART_PrimaryButton");
|
|
_secondaryButton = e.NameScope.Find<Button>("PART_SecondaryButton");
|
|
|
|
_buttonPropertyChangedDisposable = new CompositeDisposable();
|
|
|
|
if (_primaryButton != null)
|
|
{
|
|
_primaryButton.Click += PrimaryButton_Click;
|
|
|
|
_buttonPropertyChangedDisposable.Add(_primaryButton.GetPropertyChangedObservable(Button.IsPressedProperty).Subscribe(Button_VisualPropertyChanged));
|
|
_buttonPropertyChangedDisposable.Add(_primaryButton.GetPropertyChangedObservable(Button.IsPointerOverProperty).Subscribe(Button_VisualPropertyChanged));
|
|
|
|
// Register for pointer events to keep track of the last used pointer type and update visual states
|
|
_primaryButton.PointerEnter += Button_PointerEvent;
|
|
_primaryButton.PointerLeave += Button_PointerEvent;
|
|
_primaryButton.PointerPressed += Button_PointerEvent;
|
|
_primaryButton.PointerReleased += Button_PointerEvent;
|
|
_primaryButton.PointerCaptureLost += Button_PointerCaptureLost;
|
|
}
|
|
|
|
if (_secondaryButton != null)
|
|
{
|
|
_secondaryButton.Click += SecondaryButton_Click;
|
|
|
|
_buttonPropertyChangedDisposable.Add(_secondaryButton.GetPropertyChangedObservable(Button.IsPressedProperty).Subscribe(Button_VisualPropertyChanged));
|
|
_buttonPropertyChangedDisposable.Add(_secondaryButton.GetPropertyChangedObservable(Button.IsPointerOverProperty).Subscribe(Button_VisualPropertyChanged));
|
|
|
|
// Register for pointer events to keep track of the last used pointer type and update visual states
|
|
_secondaryButton.PointerEnter += Button_PointerEvent;
|
|
_secondaryButton.PointerLeave += Button_PointerEvent;
|
|
_secondaryButton.PointerPressed += Button_PointerEvent;
|
|
_secondaryButton.PointerReleased += Button_PointerEvent;
|
|
_secondaryButton.PointerCaptureLost += Button_PointerCaptureLost;
|
|
}
|
|
|
|
RegisterFlyoutEvents(Flyout);
|
|
UpdatePseudoClasses();
|
|
|
|
_hasLoaded = true;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
{
|
|
base.OnAttachedToVisualTree(e);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
{
|
|
base.OnDetachedFromVisualTree(e);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
|
{
|
|
base.OnAttachedToLogicalTree(e);
|
|
|
|
if (Command != null)
|
|
{
|
|
Command.CanExecuteChanged += CanExecuteChanged;
|
|
CanExecuteChanged(this, EventArgs.Empty);
|
|
}
|
|
|
|
_isAttachedToLogicalTree = true;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
|
{
|
|
base.OnDetachedFromLogicalTree(e);
|
|
|
|
if (Command != null)
|
|
{
|
|
Command.CanExecuteChanged -= CanExecuteChanged;
|
|
}
|
|
|
|
_isAttachedToLogicalTree = false;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> e)
|
|
{
|
|
if (e.Property == CommandProperty)
|
|
{
|
|
if (_isAttachedToLogicalTree)
|
|
{
|
|
// Must unregister events here while a reference to the old command still exists
|
|
if (e.OldValue is ICommand oldCommand)
|
|
{
|
|
oldCommand.CanExecuteChanged -= CanExecuteChanged;
|
|
}
|
|
|
|
if (e.NewValue is ICommand newCommand)
|
|
{
|
|
newCommand.CanExecuteChanged += CanExecuteChanged;
|
|
}
|
|
}
|
|
|
|
CanExecuteChanged(this, EventArgs.Empty);
|
|
}
|
|
else if (e.Property == CommandParameterProperty)
|
|
{
|
|
CanExecuteChanged(this, EventArgs.Empty);
|
|
}
|
|
else if (e.Property == FlyoutProperty)
|
|
{
|
|
// Must unregister events here while a reference to the old flyout still exists
|
|
if (e.OldValue.GetValueOrDefault() is FlyoutBase oldFlyout)
|
|
{
|
|
UnregisterFlyoutEvents(oldFlyout);
|
|
}
|
|
|
|
if (e.NewValue.GetValueOrDefault() is FlyoutBase newFlyout)
|
|
{
|
|
RegisterFlyoutEvents(newFlyout);
|
|
}
|
|
|
|
UpdatePseudoClasses();
|
|
}
|
|
|
|
base.OnPropertyChanged(e);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnKeyDown(KeyEventArgs e)
|
|
{
|
|
var key = e.Key;
|
|
|
|
if (key == Key.Space || key == Key.Enter) // Key.GamepadA is not currently supported
|
|
{
|
|
_isKeyDown = true;
|
|
UpdatePseudoClasses();
|
|
}
|
|
|
|
base.OnKeyDown(e);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void OnKeyUp(KeyEventArgs e)
|
|
{
|
|
var key = e.Key;
|
|
|
|
if (key == Key.Space || key == Key.Enter) // Key.GamepadA is not currently supported
|
|
{
|
|
_isKeyDown = false;
|
|
UpdatePseudoClasses();
|
|
|
|
// Consider this a click on the primary button
|
|
if (IsEffectivelyEnabled)
|
|
{
|
|
OnClickPrimary(null);
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
else if (key == Key.Down && IsEffectivelyEnabled)
|
|
{
|
|
// WinUI requires the VirtualKey.Menu (alt) + VirtualKey.Down to open the flyout
|
|
// Avalonia will only require Key.Down which is better cross-platform
|
|
|
|
OpenFlyout();
|
|
e.Handled = true;
|
|
}
|
|
else if (key == Key.F4 && IsEffectivelyEnabled)
|
|
{
|
|
OpenFlyout();
|
|
e.Handled = true;
|
|
}
|
|
|
|
base.OnKeyUp(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invokes the <see cref="Click"/> event when the primary button part is clicked.
|
|
/// </summary>
|
|
/// <param name="e">The event args from the internal Click event.</param>
|
|
protected virtual void OnClickPrimary(RoutedEventArgs e)
|
|
{
|
|
// Note: It is not currently required to check enabled status; however, this is a failsafe
|
|
if (IsEffectivelyEnabled)
|
|
{
|
|
var eventArgs = new RoutedEventArgs(ClickEvent);
|
|
RaiseEvent(eventArgs);
|
|
|
|
if (!eventArgs.Handled && Command?.CanExecute(CommandParameter) == true)
|
|
{
|
|
Command.Execute(CommandParameter);
|
|
eventArgs.Handled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoked when the secondary button part is clicked.
|
|
/// </summary>
|
|
/// <param name="e">The event args from the internal Click event.</param>
|
|
protected virtual void OnClickSecondary(RoutedEventArgs e)
|
|
{
|
|
// Note: It is not currently required to check enabled status; however, this is a failsafe
|
|
if (IsEffectivelyEnabled)
|
|
{
|
|
OpenFlyout();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Event Handling
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
/// <summary>
|
|
/// Event handler for when the internal primary button part is pressed.
|
|
/// </summary>
|
|
private void PrimaryButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
OnClickPrimary(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event handler for when the internal secondary button part is pressed.
|
|
/// </summary>
|
|
private void SecondaryButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
OnClickSecondary(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event handler for when pointer events occur in the primary or secondary buttons.
|
|
/// </summary>
|
|
private void Button_PointerEvent(object sender, PointerEventArgs e)
|
|
{
|
|
// Warning: Code must match with Button_PointerCaptureLost
|
|
if (_lastPointerType != e.Pointer.Type)
|
|
{
|
|
_lastPointerType = e.Pointer.Type;
|
|
UpdatePseudoClasses();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event handler for when the pointer capture is lost in the primary or secondary buttons.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// In upstream WinUI this is not a separate event handler. However, Avalonia has different args.
|
|
/// </remarks>
|
|
private void Button_PointerCaptureLost(object sender, PointerCaptureLostEventArgs e)
|
|
{
|
|
// Warning: Code must match with Button_PointerEvent
|
|
if (_lastPointerType != e.Pointer.Type)
|
|
{
|
|
_lastPointerType = e.Pointer.Type;
|
|
UpdatePseudoClasses();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a primary or secondary button property changes that affects the visual states.
|
|
/// </summary>
|
|
private void Button_VisualPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
UpdatePseudoClasses();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the <see cref="FlyoutBase.Placement"/> property changes.
|
|
/// </summary>
|
|
private void Flyout_PlacementPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
UpdatePseudoClasses();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event handler for when the split button's flyout is opened.
|
|
/// </summary>
|
|
private void Flyout_Opened(object sender, EventArgs e)
|
|
{
|
|
_isFlyoutOpen = true;
|
|
UpdatePseudoClasses();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event handler for when the split button's flyout is closed.
|
|
/// </summary>
|
|
private void Flyout_Closed(object sender, EventArgs e)
|
|
{
|
|
_isFlyoutOpen = false;
|
|
UpdatePseudoClasses();
|
|
}
|
|
}
|
|
}
|
|
|