Browse Source

Merge pull request #1435 from DmitryZhelnin/ButtonSpinner-Control

ButtonSpinner control is ported from ExtendedWFPToolkit
pull/1439/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
80a0faf164
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      samples/ControlCatalog/ControlCatalog.csproj
  2. 1
      samples/ControlCatalog/MainView.xaml
  3. 24
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
  4. 54
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  5. 258
      src/Avalonia.Controls/ButtonSpinner.cs
  6. 174
      src/Avalonia.Controls/Spinner.cs
  7. 86
      src/Avalonia.Themes.Default/ButtonSpinner.xaml
  8. 1
      src/Avalonia.Themes.Default/DefaultTheme.xaml

6
samples/ControlCatalog/ControlCatalog.csproj

@ -35,6 +35,9 @@
<EmbeddedResource Include="DecoratedWindow.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ButtonSpinnerPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\DialogsPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
@ -164,6 +167,9 @@
<Compile Include="Pages\ToolTipPage.xaml.cs">
<DependentUpon>ToolTipPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ButtonSpinnerPage.xaml.cs">
<DependentUpon>ButtonSpinnerPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ScreenPage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

1
samples/ControlCatalog/MainView.xaml

@ -7,6 +7,7 @@
</TabControl.Transition>
<TabItem Header="Border"><pages:BorderPage/></TabItem>
<TabItem Header="Button"><pages:ButtonPage/></TabItem>
<TabItem Header="ButtonSpinner"><pages:ButtonSpinnerPage/></TabItem>
<TabItem Header="Calendar"><pages:CalendarPage/></TabItem>
<TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
<TabItem Header="Carousel"><pages:CarouselPage/></TabItem>

24
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml

@ -0,0 +1,24 @@
<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 Spin="OnSpin" Height="30"
AllowSpin="{Binding #allowSpinCheck.IsChecked}"
ShowButtonSpinner="{Binding #showSpinCheck.IsChecked}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Everest"/>
</ButtonSpinner>
<ButtonSpinner Spin="OnSpin" Height="30" ButtonSpinnerLocation="Left"
AllowSpin="{Binding #allowSpinCheck.IsChecked}"
ShowButtonSpinner="{Binding #showSpinCheck.IsChecked}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Everest"/>
</ButtonSpinner>
</StackPanel>
</StackPanel>
</UserControl>

54
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs

@ -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"
};
}
}

258
src/Avalonia.Controls/ButtonSpinner.cs

@ -0,0 +1,258 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
public enum Location
{
Left,
Right
}
/// <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);
/// <summary>
/// Defines the <see cref="ButtonSpinnerLocation"/> property.
/// </summary>
public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty =
AvaloniaProperty.Register<ButtonSpinner, Location>(nameof(ButtonSpinnerLocation), Location.Right);
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);
PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left");
PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right");
}
/// <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); }
}
/// <summary>
/// Gets or sets current location of the <see cref="ButtonSpinner"/>.
/// </summary>
public Location ButtonSpinnerLocation
{
get { return GetValue(ButtonSpinnerLocationProperty); }
set { SetValue(ButtonSpinnerLocationProperty, 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));
}
}
}
}

174
src/Avalonia.Controls/Spinner.cs

@ -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);
}
}
}
}

86
src/Avalonia.Themes.Default/ButtonSpinner.xaml

@ -0,0 +1,86 @@
<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"/>
</Style>
<Style Selector="ButtonSpinner /template/ RepeatButton">
<Setter Property="RepeatButton.Background" Value="Transparent"/>
<Setter Property="RepeatButton.BorderBrush" Value="Transparent"/>
</Style>
<Style Selector="ButtonSpinner /template/ RepeatButton:pointerover">
<Setter Property="RepeatButton.Background" Value="{DynamicResource ThemeControlMidBrush}"/>
<Setter Property="RepeatButton.BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
</Style>
<Style Selector="ButtonSpinner /template/ RepeatButton#PART_IncreaseButton">
<Setter Property="Content">
<Template>
<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"/>
</Template>
</Setter>
</Style>
<Style Selector="ButtonSpinner /template/ RepeatButton#PART_DecreaseButton">
<Setter Property="Content">
<Template>
<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"/>
</Template>
</Setter>
</Style>
<Style Selector="ButtonSpinner:right">
<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}">
<RepeatButton Grid.Row="0" Name="PART_IncreaseButton"/>
<RepeatButton Grid.Row="1" Name="PART_DecreaseButton"/>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ButtonSpinner:left">
<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,*">
<Grid Grid.Column="0" RowDefinitions="*,*" IsVisible="{TemplateBinding ShowButtonSpinner}">
<RepeatButton Grid.Row="0" Name="PART_IncreaseButton"/>
<RepeatButton Grid.Row="1" Name="PART_DecreaseButton"/>
</Grid>
<ContentPresenter Name="PART_ContentPresenter" Grid.Column="1"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Styles>

1
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -42,4 +42,5 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.CalendarItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Calendar.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.DatePicker.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ButtonSpinner.xaml?assembly=Avalonia.Themes.Default"/>
</Styles>

Loading…
Cancel
Save