8 changed files with 544 additions and 0 deletions
@ -0,0 +1,19 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
|
||||
|
<StackPanel Orientation="Vertical" Gap="4"> |
||||
|
<TextBlock Classes="h1">ButtonSpinner</TextBlock> |
||||
|
<TextBlock Classes="h2">The ButtonSpinner control allows you to add button spinners to any element and then respond to the Spin event to manipulate that element.</TextBlock> |
||||
|
|
||||
|
<StackPanel Orientation="Vertical" Gap="8" Width="200" Margin="0,20,0,0"> |
||||
|
<CheckBox Name="allowSpinCheck" IsChecked="True">AllowSpin</CheckBox> |
||||
|
<CheckBox Name="showSpinCheck" IsChecked="True">ShowButtonSpinner</CheckBox> |
||||
|
<ButtonSpinner Name="spinner" Spin="OnSpin" Height="30" |
||||
|
AllowSpin="{Binding #allowSpinCheck.IsChecked}" |
||||
|
ShowButtonSpinner="{Binding #showSpinCheck.IsChecked}"> |
||||
|
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Everest"/> |
||||
|
</ButtonSpinner> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
|
||||
|
</UserControl> |
||||
@ -0,0 +1,54 @@ |
|||||
|
using System; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public class ButtonSpinnerPage : UserControl |
||||
|
{ |
||||
|
public ButtonSpinnerPage() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
|
||||
|
private void OnSpin(object sender, SpinEventArgs e) |
||||
|
{ |
||||
|
var spinner = (ButtonSpinner)sender; |
||||
|
var txtBox = (TextBlock)spinner.Content; |
||||
|
|
||||
|
int value = Array.IndexOf(_mountains, txtBox.Text); |
||||
|
if (e.Direction == SpinDirection.Increase) |
||||
|
value++; |
||||
|
else |
||||
|
value--; |
||||
|
|
||||
|
if (value < 0) |
||||
|
value = _mountains.Length - 1; |
||||
|
else if (value >= _mountains.Length) |
||||
|
value = 0; |
||||
|
|
||||
|
txtBox.Text = _mountains[value]; |
||||
|
} |
||||
|
|
||||
|
private readonly string[] _mountains = new[] |
||||
|
{ |
||||
|
"Everest", |
||||
|
"K2 (Mount Godwin Austen)", |
||||
|
"Kangchenjunga", |
||||
|
"Lhotse", |
||||
|
"Makalu", |
||||
|
"Cho Oyu", |
||||
|
"Dhaulagiri", |
||||
|
"Manaslu", |
||||
|
"Nanga Parbat", |
||||
|
"Annapurna" |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,235 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Interactivity; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a spinner control that includes two Buttons.
|
||||
|
/// </summary>
|
||||
|
public class ButtonSpinner : Spinner |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="AllowSpin"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<bool> AllowSpinProperty = |
||||
|
AvaloniaProperty.Register<ButtonSpinner, bool>(nameof(AllowSpin), true); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="ShowButtonSpinner"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<bool> ShowButtonSpinnerProperty = |
||||
|
AvaloniaProperty.Register<ButtonSpinner, bool>(nameof(ShowButtonSpinner), true); |
||||
|
|
||||
|
private Button _decreaseButton; |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the DecreaseButton template part.
|
||||
|
/// </summary>
|
||||
|
private Button DecreaseButton |
||||
|
{ |
||||
|
get { return _decreaseButton; } |
||||
|
set |
||||
|
{ |
||||
|
if (_decreaseButton != null) |
||||
|
{ |
||||
|
_decreaseButton.Click -= OnButtonClick; |
||||
|
} |
||||
|
_decreaseButton = value; |
||||
|
if (_decreaseButton != null) |
||||
|
{ |
||||
|
_decreaseButton.Click += OnButtonClick; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Button _increaseButton; |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the IncreaseButton template part.
|
||||
|
/// </summary>
|
||||
|
private Button IncreaseButton |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return _increaseButton; |
||||
|
} |
||||
|
set |
||||
|
{ |
||||
|
if (_increaseButton != null) |
||||
|
{ |
||||
|
_increaseButton.Click -= OnButtonClick; |
||||
|
} |
||||
|
_increaseButton = value; |
||||
|
if (_increaseButton != null) |
||||
|
{ |
||||
|
_increaseButton.Click += OnButtonClick; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes static members of the <see cref="ButtonSpinner"/> class.
|
||||
|
/// </summary>
|
||||
|
static ButtonSpinner() |
||||
|
{ |
||||
|
AllowSpinProperty.Changed.Subscribe(AllowSpinChanged); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether the <see cref="ButtonSpinner"/> should allow to spin.
|
||||
|
/// </summary>
|
||||
|
public bool AllowSpin |
||||
|
{ |
||||
|
get { return GetValue(AllowSpinProperty); } |
||||
|
set { SetValue(AllowSpinProperty, value); } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether the spin buttons should be shown.
|
||||
|
/// </summary>
|
||||
|
public bool ShowButtonSpinner |
||||
|
{ |
||||
|
get { return GetValue(ShowButtonSpinnerProperty); } |
||||
|
set { SetValue(ShowButtonSpinnerProperty, value); } |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
||||
|
{ |
||||
|
IncreaseButton = e.NameScope.Find<Button>("PART_IncreaseButton"); |
||||
|
DecreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton"); |
||||
|
SetButtonUsage(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected override void OnPointerReleased(PointerReleasedEventArgs e) |
||||
|
{ |
||||
|
base.OnPointerReleased(e); |
||||
|
Point mousePosition; |
||||
|
if (IncreaseButton != null && IncreaseButton.IsEnabled == false) |
||||
|
{ |
||||
|
mousePosition = e.GetPosition(IncreaseButton); |
||||
|
if (mousePosition.X > 0 && mousePosition.X < IncreaseButton.Width && |
||||
|
mousePosition.Y > 0 && mousePosition.Y < IncreaseButton.Height) |
||||
|
{ |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (DecreaseButton != null && DecreaseButton.IsEnabled == false) |
||||
|
{ |
||||
|
mousePosition = e.GetPosition(DecreaseButton); |
||||
|
if (mousePosition.X > 0 && mousePosition.X < DecreaseButton.Width && |
||||
|
mousePosition.Y > 0 && mousePosition.Y < DecreaseButton.Height) |
||||
|
{ |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected override void OnKeyDown(KeyEventArgs e) |
||||
|
{ |
||||
|
switch (e.Key) |
||||
|
{ |
||||
|
case Key.Up: |
||||
|
{ |
||||
|
if (AllowSpin) |
||||
|
{ |
||||
|
OnSpin(new SpinEventArgs(SpinEvent, SpinDirection.Increase)); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case Key.Down: |
||||
|
{ |
||||
|
if (AllowSpin) |
||||
|
{ |
||||
|
OnSpin(new SpinEventArgs(SpinEvent, SpinDirection.Decrease)); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case Key.Enter: |
||||
|
{ |
||||
|
//Do not Spin on enter Key when spinners have focus
|
||||
|
if (((IncreaseButton != null) && (IncreaseButton.IsFocused)) |
||||
|
|| ((DecreaseButton != null) && DecreaseButton.IsFocused)) |
||||
|
{ |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected override void OnPointerWheelChanged(PointerWheelEventArgs e) |
||||
|
{ |
||||
|
base.OnPointerWheelChanged(e); |
||||
|
if (!e.Handled && AllowSpin) |
||||
|
{ |
||||
|
if (e.Delta.Y != 0) |
||||
|
{ |
||||
|
var spinnerEventArgs = new SpinEventArgs(SpinEvent, (e.Delta.Y < 0) ? SpinDirection.Decrease : SpinDirection.Increase, true); |
||||
|
OnSpin(spinnerEventArgs); |
||||
|
e.Handled = spinnerEventArgs.Handled; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Called when the <see cref="AllowSpin"/> property value changed.
|
||||
|
/// </summary>
|
||||
|
/// <param name="oldValue">The old value.</param>
|
||||
|
/// <param name="newValue">The new value.</param>
|
||||
|
protected virtual void OnAllowSpinChanged(bool oldValue, bool newValue) |
||||
|
{ |
||||
|
SetButtonUsage(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Called when the <see cref="AllowSpin"/> property value changed.
|
||||
|
/// </summary>
|
||||
|
/// <param name="e">The event args.</param>
|
||||
|
private static void AllowSpinChanged(AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.Sender is ButtonSpinner spinner) |
||||
|
{ |
||||
|
var oldValue = (bool)e.OldValue; |
||||
|
var newValue = (bool)e.NewValue; |
||||
|
spinner.OnAllowSpinChanged(oldValue, newValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Disables or enables the buttons based on the valid spin direction.
|
||||
|
/// </summary>
|
||||
|
private void SetButtonUsage() |
||||
|
{ |
||||
|
if (IncreaseButton != null) |
||||
|
{ |
||||
|
IncreaseButton.IsEnabled = AllowSpin && ((ValidSpinDirection & ValidSpinDirections.Increase) == ValidSpinDirections.Increase); |
||||
|
} |
||||
|
|
||||
|
if (DecreaseButton != null) |
||||
|
{ |
||||
|
DecreaseButton.IsEnabled = AllowSpin && ((ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Called when user clicks one of the spin buttons.
|
||||
|
/// </summary>
|
||||
|
/// <param name="sender">The event sender.</param>
|
||||
|
/// <param name="e">The event args.</param>
|
||||
|
private void OnButtonClick(object sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
if (AllowSpin) |
||||
|
{ |
||||
|
var direction = sender == IncreaseButton ? SpinDirection.Increase : SpinDirection.Decrease; |
||||
|
OnSpin(new SpinEventArgs(SpinEvent, direction)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,174 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using Avalonia.Interactivity; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents spin directions that are valid.
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
public enum ValidSpinDirections |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Can not increase nor decrease.
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can increase.
|
||||
|
/// </summary>
|
||||
|
Increase = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can decrease.
|
||||
|
/// </summary>
|
||||
|
Decrease = 2 |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Represents spin directions that could be initiated by the end-user.
|
||||
|
/// </summary>
|
||||
|
public enum SpinDirection |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a spin initiated by the end-user in order to Increase a value.
|
||||
|
/// </summary>
|
||||
|
Increase = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Represents a spin initiated by the end-user in order to Decrease a value.
|
||||
|
/// </summary>
|
||||
|
Decrease = 1 |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides data for the Spinner.Spin event.
|
||||
|
/// </summary>
|
||||
|
public class SpinEventArgs : RoutedEventArgs |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the SpinDirection for the spin that has been initiated by the end-user.
|
||||
|
/// </summary>
|
||||
|
public SpinDirection Direction { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get or set whheter the spin event originated from a mouse wheel event.
|
||||
|
/// </summary>
|
||||
|
public bool UsingMouseWheel{ get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the SpinEventArgs class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="direction">Spin direction.</param>
|
||||
|
public SpinEventArgs(SpinDirection direction) |
||||
|
{ |
||||
|
Direction = direction; |
||||
|
} |
||||
|
|
||||
|
public SpinEventArgs(RoutedEvent routedEvent, SpinDirection direction) |
||||
|
: base(routedEvent) |
||||
|
{ |
||||
|
Direction = direction; |
||||
|
} |
||||
|
|
||||
|
public SpinEventArgs(SpinDirection direction, bool usingMouseWheel) |
||||
|
{ |
||||
|
Direction = direction; |
||||
|
UsingMouseWheel = usingMouseWheel; |
||||
|
} |
||||
|
|
||||
|
public SpinEventArgs(RoutedEvent routedEvent, SpinDirection direction, bool usingMouseWheel) |
||||
|
: base(routedEvent) |
||||
|
{ |
||||
|
Direction = direction; |
||||
|
UsingMouseWheel = usingMouseWheel; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Base class for controls that represents controls that can spin.
|
||||
|
/// </summary>
|
||||
|
public abstract class Spinner : ContentControl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="ValidSpinDirection"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<ValidSpinDirections> ValidSpinDirectionProperty = |
||||
|
AvaloniaProperty.Register<Spinner, ValidSpinDirections>(nameof(ValidSpinDirection), |
||||
|
ValidSpinDirections.Increase | ValidSpinDirections.Decrease); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Spin"/> event.
|
||||
|
/// </summary>
|
||||
|
public static readonly RoutedEvent<SpinEventArgs> SpinEvent = |
||||
|
RoutedEvent.Register<Spinner, SpinEventArgs>(nameof(Spin), RoutingStrategies.Bubble); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes static members of the <see cref="Spinner"/> class.
|
||||
|
/// </summary>
|
||||
|
static Spinner() |
||||
|
{ |
||||
|
ValidSpinDirectionProperty.Changed.Subscribe(OnValidSpinDirectionPropertyChanged); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Occurs when spinning is initiated by the end-user.
|
||||
|
/// </summary>
|
||||
|
public event EventHandler<SpinEventArgs> Spin |
||||
|
{ |
||||
|
add { AddHandler(SpinEvent, value); } |
||||
|
remove { RemoveHandler(SpinEvent, value); } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets <see cref="ValidSpinDirections"/> allowed for this control.
|
||||
|
/// </summary>
|
||||
|
public ValidSpinDirections ValidSpinDirection |
||||
|
{ |
||||
|
get { return GetValue(ValidSpinDirectionProperty); } |
||||
|
set { SetValue(ValidSpinDirectionProperty, value); } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Called when valid spin direction changed.
|
||||
|
/// </summary>
|
||||
|
/// <param name="oldValue">The old value.</param>
|
||||
|
/// <param name="newValue">The new value.</param>
|
||||
|
protected virtual void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Raises the OnSpin event when spinning is initiated by the end-user.
|
||||
|
/// </summary>
|
||||
|
/// <param name="e">Spin event args.</param>
|
||||
|
protected virtual void OnSpin(SpinEventArgs e) |
||||
|
{ |
||||
|
var valid = e.Direction == SpinDirection.Increase |
||||
|
? ValidSpinDirections.Increase |
||||
|
: ValidSpinDirections.Decrease; |
||||
|
|
||||
|
//Only raise the event if spin is allowed.
|
||||
|
if ((ValidSpinDirection & valid) == valid) |
||||
|
{ |
||||
|
RaiseEvent(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Called when the <see cref="ValidSpinDirection"/> property value changed.
|
||||
|
/// </summary>
|
||||
|
/// <param name="e">The event args.</param>
|
||||
|
private static void OnValidSpinDirectionPropertyChanged(AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.Sender is Spinner spinner) |
||||
|
{ |
||||
|
var oldValue = (ValidSpinDirections)e.OldValue; |
||||
|
var newValue = (ValidSpinDirections)e.NewValue; |
||||
|
spinner.OnValidSpinDirectionChanged(oldValue, newValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
<Styles xmlns="https://github.com/avaloniaui"> |
||||
|
<Style Selector="ButtonSpinner"> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderLightBrush}"/> |
||||
|
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/> |
||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch"/> |
||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Border Background="{TemplateBinding Background}" |
||||
|
BorderBrush="{TemplateBinding BorderBrush}" |
||||
|
BorderThickness="{TemplateBinding BorderThickness}" |
||||
|
Margin="{TemplateBinding Padding}" |
||||
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" |
||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"> |
||||
|
<Grid ColumnDefinitions="*,Auto"> |
||||
|
<ContentPresenter Name="PART_ContentPresenter" Grid.Column="0" |
||||
|
ContentTemplate="{TemplateBinding ContentTemplate}" |
||||
|
Content="{TemplateBinding Content}"/> |
||||
|
<Grid Grid.Column="1" RowDefinitions="*,*" IsVisible="{TemplateBinding ShowButtonSpinner}"> |
||||
|
<Grid.Styles> |
||||
|
<Style Selector="RepeatButton"> |
||||
|
<Setter Property="RepeatButton.Background" Value="Transparent"/> |
||||
|
<Setter Property="RepeatButton.BorderBrush" Value="Transparent"/> |
||||
|
</Style> |
||||
|
<Style Selector="RepeatButton:pointerover"> |
||||
|
<Setter Property="RepeatButton.Background" Value="{DynamicResource ThemeControlMidBrush}"/> |
||||
|
<Setter Property="RepeatButton.BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> |
||||
|
</Style> |
||||
|
</Grid.Styles> |
||||
|
<RepeatButton Grid.Row="0" Name="PART_IncreaseButton"> |
||||
|
<Path Fill="{DynamicResource ThemeForegroundBrush}" |
||||
|
Width="8" |
||||
|
Height="4" |
||||
|
Stretch="Uniform" |
||||
|
HorizontalAlignment="Center" |
||||
|
VerticalAlignment="Center" |
||||
|
Data="M0,5 L4.5,.5 9,5 6,5 4.5,3.5 3,5 z"/> |
||||
|
</RepeatButton> |
||||
|
<RepeatButton Grid.Row="1" Name="PART_DecreaseButton"> |
||||
|
<Path Fill="{DynamicResource ThemeForegroundBrush}" |
||||
|
Width="8" |
||||
|
Height="4" |
||||
|
Stretch="Uniform" |
||||
|
HorizontalAlignment="Center" |
||||
|
VerticalAlignment="Center" |
||||
|
Data="M0,0 L3,0 4.5,1.5 6,0 9,0 4.5,4.5 z"/> |
||||
|
</RepeatButton> |
||||
|
</Grid> |
||||
|
</Grid> |
||||
|
</Border> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
</Styles> |
||||
Loading…
Reference in new issue