Skip to content

Commit ee9d0ce

Browse files
authored
Merge pull request AvaloniaUI#3427 from MarchingCube/improve-togglebutton
Implement ToggleButton events
2 parents 4c690d4 + 9953bdd commit ee9d0ce

File tree

2 files changed

+177
-3
lines changed

2 files changed

+177
-3
lines changed

src/Avalonia.Controls/Primitives/ToggleButton.cs

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@
77

88
namespace Avalonia.Controls.Primitives
99
{
10+
/// <summary>
11+
/// Represents a control that a user can select (check) or clear (uncheck). Base class for controls that can switch states.
12+
/// </summary>
1013
public class ToggleButton : Button
1114
{
15+
/// <summary>
16+
/// Defines the <see cref="IsChecked"/> property.
17+
/// </summary>
1218
public static readonly DirectProperty<ToggleButton, bool?> IsCheckedProperty =
1319
AvaloniaProperty.RegisterDirect<ToggleButton, bool?>(
1420
nameof(IsChecked),
@@ -17,24 +23,80 @@ public class ToggleButton : Button
1723
unsetValue: null,
1824
defaultBindingMode: BindingMode.TwoWay);
1925

26+
/// <summary>
27+
/// Defines the <see cref="IsThreeState"/> property.
28+
/// </summary>
2029
public static readonly StyledProperty<bool> IsThreeStateProperty =
2130
AvaloniaProperty.Register<ToggleButton, bool>(nameof(IsThreeState));
2231

32+
/// <summary>
33+
/// Defines the <see cref="Checked"/> event.
34+
/// </summary>
35+
public static readonly RoutedEvent<RoutedEventArgs> CheckedEvent =
36+
RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Checked), RoutingStrategies.Bubble);
37+
38+
/// <summary>
39+
/// Defines the <see cref="Unchecked"/> event.
40+
/// </summary>
41+
public static readonly RoutedEvent<RoutedEventArgs> UncheckedEvent =
42+
RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Unchecked), RoutingStrategies.Bubble);
43+
44+
/// <summary>
45+
/// Defines the <see cref="Unchecked"/> event.
46+
/// </summary>
47+
public static readonly RoutedEvent<RoutedEventArgs> IndeterminateEvent =
48+
RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Indeterminate), RoutingStrategies.Bubble);
49+
2350
private bool? _isChecked = false;
2451

2552
static ToggleButton()
2653
{
2754
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == true, ":checked");
2855
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == false, ":unchecked");
2956
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == null, ":indeterminate");
57+
58+
IsCheckedProperty.Changed.AddClassHandler<ToggleButton>((x, e) => x.OnIsCheckedChanged(e));
59+
}
60+
61+
/// <summary>
62+
/// Raised when a <see cref="ToggleButton"/> is checked.
63+
/// </summary>
64+
public event EventHandler<RoutedEventArgs> Checked
65+
{
66+
add => AddHandler(CheckedEvent, value);
67+
remove => RemoveHandler(CheckedEvent, value);
68+
}
69+
70+
/// <summary>
71+
/// Raised when a <see cref="ToggleButton"/> is unchecked.
72+
/// </summary>
73+
public event EventHandler<RoutedEventArgs> Unchecked
74+
{
75+
add => AddHandler(UncheckedEvent, value);
76+
remove => RemoveHandler(UncheckedEvent, value);
77+
}
78+
79+
/// <summary>
80+
/// Raised when a <see cref="ToggleButton"/> is neither checked nor unchecked.
81+
/// </summary>
82+
public event EventHandler<RoutedEventArgs> Indeterminate
83+
{
84+
add => AddHandler(IndeterminateEvent, value);
85+
remove => RemoveHandler(IndeterminateEvent, value);
3086
}
3187

88+
/// <summary>
89+
/// Gets or sets whether the <see cref="ToggleButton"/> is checked.
90+
/// </summary>
3291
public bool? IsChecked
3392
{
34-
get { return _isChecked; }
35-
set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
93+
get => _isChecked;
94+
set => SetAndRaise(IsCheckedProperty, ref _isChecked, value);
3695
}
3796

97+
/// <summary>
98+
/// Gets or sets a value that indicates whether the control supports three states.
99+
/// </summary>
38100
public bool IsThreeState
39101
{
40102
get => GetValue(IsThreeStateProperty);
@@ -47,18 +109,78 @@ protected override void OnClick()
47109
base.OnClick();
48110
}
49111

112+
/// <summary>
113+
/// Toggles the <see cref="IsChecked"/> property.
114+
/// </summary>
50115
protected virtual void Toggle()
51116
{
52117
if (IsChecked.HasValue)
118+
{
53119
if (IsChecked.Value)
120+
{
54121
if (IsThreeState)
122+
{
55123
IsChecked = null;
124+
}
56125
else
126+
{
57127
IsChecked = false;
128+
}
129+
}
58130
else
131+
{
59132
IsChecked = true;
133+
}
134+
}
60135
else
136+
{
61137
IsChecked = false;
138+
}
139+
}
140+
141+
/// <summary>
142+
/// Called when <see cref="IsChecked"/> becomes true.
143+
/// </summary>
144+
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
145+
protected virtual void OnChecked(RoutedEventArgs e)
146+
{
147+
RaiseEvent(e);
148+
}
149+
150+
/// <summary>
151+
/// Called when <see cref="IsChecked"/> becomes false.
152+
/// </summary>
153+
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
154+
protected virtual void OnUnchecked(RoutedEventArgs e)
155+
{
156+
RaiseEvent(e);
157+
}
158+
159+
/// <summary>
160+
/// Called when <see cref="IsChecked"/> becomes null.
161+
/// </summary>
162+
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
163+
protected virtual void OnIndeterminate(RoutedEventArgs e)
164+
{
165+
RaiseEvent(e);
166+
}
167+
168+
private void OnIsCheckedChanged(AvaloniaPropertyChangedEventArgs e)
169+
{
170+
var newValue = (bool?)e.NewValue;
171+
172+
switch (newValue)
173+
{
174+
case true:
175+
OnChecked(new RoutedEventArgs(CheckedEvent));
176+
break;
177+
case false:
178+
OnUnchecked(new RoutedEventArgs(UncheckedEvent));
179+
break;
180+
default:
181+
OnIndeterminate(new RoutedEventArgs(IndeterminateEvent));
182+
break;
183+
}
62184
}
63185
}
64186
}

tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Avalonia.Data;
2-
using Avalonia.Markup.Data;
32
using Avalonia.UnitTests;
43

54
using Xunit;
@@ -63,6 +62,54 @@ public void ToggleButton_ThreeState_Checked_Binds_To_Nullable_Bool()
6362
Assert.Null(threeStateButton.IsChecked);
6463
}
6564

65+
[Fact]
66+
public void ToggleButton_Events_Are_Raised_On_Is_Checked_Changes()
67+
{
68+
var threeStateButton = new ToggleButton();
69+
70+
bool checkedRaised = false;
71+
threeStateButton.Checked += (_, __) => checkedRaised = true;
72+
73+
threeStateButton.IsChecked = true;
74+
Assert.True(checkedRaised);
75+
76+
bool uncheckedRaised = false;
77+
threeStateButton.Unchecked += (_, __) => uncheckedRaised = true;
78+
79+
threeStateButton.IsChecked = false;
80+
Assert.True(uncheckedRaised);
81+
82+
bool indeterminateRaised = false;
83+
threeStateButton.Indeterminate += (_, __) => indeterminateRaised = true;
84+
85+
threeStateButton.IsChecked = null;
86+
Assert.True(indeterminateRaised);
87+
}
88+
89+
[Fact]
90+
public void ToggleButton_Events_Are_Raised_When_Toggling()
91+
{
92+
var threeStateButton = new TestToggleButton { IsThreeState = true };
93+
94+
bool checkedRaised = false;
95+
threeStateButton.Checked += (_, __) => checkedRaised = true;
96+
97+
threeStateButton.Toggle();
98+
Assert.True(checkedRaised);
99+
100+
bool indeterminateRaised = false;
101+
threeStateButton.Indeterminate += (_, __) => indeterminateRaised = true;
102+
103+
threeStateButton.Toggle();
104+
Assert.True(indeterminateRaised);
105+
106+
bool uncheckedRaised = false;
107+
threeStateButton.Unchecked += (_, __) => uncheckedRaised = true;
108+
109+
threeStateButton.Toggle();
110+
Assert.True(uncheckedRaised);
111+
}
112+
66113
private class Class1 : NotifyingBase
67114
{
68115
private bool _foo;
@@ -80,5 +127,10 @@ public bool? NullableFoo
80127
set { nullableFoo = value; RaisePropertyChanged(); }
81128
}
82129
}
130+
131+
private class TestToggleButton : ToggleButton
132+
{
133+
public new void Toggle() => base.Toggle();
134+
}
83135
}
84136
}

0 commit comments

Comments
 (0)