-
Notifications
You must be signed in to change notification settings - Fork 27
/
CommandExtensions.cs
208 lines (186 loc) · 7.41 KB
/
CommandExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.System;
using Microsoft.Extensions.Logging;
using Uno.Extensions;
using Uno.Logging;
using System.Diagnostics.CodeAnalysis;
#if IS_WINUI
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using ItemsRepeater = Microsoft.UI.Xaml.Controls.ItemsRepeater;
#endif
namespace Uno.Toolkit.UI
{
public static class CommandExtensions
{
private static ILogger _logger = typeof(CommandExtensions).Log();
#region DependencyProperty: Command
/// <summary>
/// Backing property for the command to execute when <see cref="TextBox"/>/<see cref="PasswordBox"/> enter key is pressed,
/// <see cref="ListViewBase.ItemClick" />, <see cref="Selector.SelectionChanged" />, <see cref="NavigationView.ItemInvoked" />, <see cref="ItemsRepeater" /> item tapped, or <see cref="UIElement.Tapped" />.
/// </summary>
/// <remarks>
/// For Command, the relevant parameter is also provided for the <see cref="ICommand.CanExecute(object)"/> and <see cref="ICommand.Execute(object)"/> call:
/// <list type="bullet">
/// <item><see cref="TextBox.Text"/></item>
/// <item><see cref="PasswordBox.Password"/></item>
/// <item><see cref="ItemClickEventArgs.ClickedItem"/> from <see cref="ListViewBase.ItemClick"/></item>
/// <item><see cref="Selector.SelectedItem"/></item>
/// <item><see cref="NavigationViewItemInvokedEventArgs.InvokedItem"/> from <see cref="NavigationView.ItemInvoked"/></item>
/// <item><see cref="ItemsRepeater"/>'s item root's DataContext</item>
/// <item><see cref="UIElement"/> itself</item>
/// </list>
/// <see cref="CommandParameterProperty"/> can be set, on the item-container or item-template's root for collection-type controls, or control itself for other controls, to replace the above.
/// </remarks>
public static DependencyProperty CommandProperty { [DynamicDependency(nameof(GetCommand))] get; } = DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(CommandExtensions),
new PropertyMetadata(default(ICommand), OnCommandChanged));
[DynamicDependency(nameof(SetCommand))]
public static ICommand GetCommand(DependencyObject obj) => (ICommand)obj.GetValue(CommandProperty);
[DynamicDependency(nameof(GetCommand))]
public static void SetCommand(DependencyObject obj, ICommand value) => obj.SetValue(CommandProperty, value);
#endregion
#region DependencyProperty: CommandParameter
/// <summary>
/// Backing property for the parameter to pass to the <see cref="CommandProperty"/>.
/// </summary>
public static DependencyProperty CommandParameterProperty { [DynamicDependency(nameof(GetCommandParameter))] get; } = DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(CommandExtensions),
new PropertyMetadata(default(object?)));
[DynamicDependency(nameof(SetCommandParameter))]
public static object? GetCommandParameter(DependencyObject obj) => obj.GetValue(CommandParameterProperty);
[DynamicDependency(nameof(GetCommandParameter))]
public static void SetCommandParameter(DependencyObject obj, object? value) => obj.SetValue(CommandParameterProperty, value);
#endregion
private static void OnCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (InputExtensions.IsEnterCommandSupportedFor(sender))
{
// for input controls, this will be implemented in InputExtensions.
InputExtensions.OnEnterCommandChanged(sender, e);
}
else if (sender is ItemsRepeater ir)
{
ItemsRepeaterExtensions.OnItemCommandChanged(ir, e);
}
else if (sender is ListViewBase lvb)
{
if (!lvb.IsItemClickEnabled && _logger.IsEnabled(LogLevel.Warning))
{
_logger.Warn("IsItemClickEnabled is not enabled on the associated list. This must be enabled to make this behavior work");
}
lvb.ItemClick -= OnListViewItemClick;
if (GetCommand(sender) is { } command)
{
lvb.ItemClick += OnListViewItemClick;
}
}
else if (sender is Selector s)
{
s.SelectionChanged -= OnSelectorSelectionChanged;
if (GetCommand(sender) is { } command)
{
s.SelectionChanged += OnSelectorSelectionChanged;
}
}
else if (sender is NavigationView nv)
{
nv.ItemInvoked -= OnNavigationViewItemInvoked;
if (GetCommand(sender) is { } command)
{
nv.ItemInvoked += OnNavigationViewItemInvoked;
}
}
else if (sender is UIElement uie)
{
uie.Tapped -= OnUIElementTapped;
if (GetCommand(sender) is { } command)
{
uie.Tapped += OnUIElementTapped;
}
}
else
{
if (_logger.IsEnabled(LogLevel.Warning))
{
_logger.Warn($"CommandExtensions.Command is not supported on '{sender.GetType().FullName}'.");
}
}
}
internal static bool TryInvokeCommand(DependencyObject owner) => TryInvokeCommand(owner, GetCommandParameter(owner));
internal static bool TryInvokeCommand(DependencyObject owner, object? parameter)
{
if (GetCommand(owner) is { } command &&
command.CanExecute(parameter))
{
command.Execute(parameter);
return true;
}
return false;
}
internal static object? TryGetItemCommandParameter(DependencyObject? container)
{
if (container is ListViewItem)
{
// fixme: it doesn't work here, because we came from ListView::ItemClick,
// and when that happens the SelectedIndex not yet set.
return null;
}
if (container is { })
{
// we can have two scenarios here: // where the CommandParameter property can be assigned to
// 1. direct (IsItemItsOwnContainerOverride=true) container as items
if (GetCommandParameter(container) is { } parameter1)
{
return parameter1;
}
// 2. root element of item-template
if (container is ContentControl && // typically Selector's item-container are all of ContentControl descents: LVI, CBI, LBI...
container.GetFirstDescendant<ContentPresenter>(IsTemplateBoundToContent) is { } presenter &&
presenter.GetTemplateRoot() is { } root &&
GetCommandParameter(root) is { } parameter2)
{
return parameter2;
}
bool IsTemplateBoundToContent(ContentPresenter presenter) =>
presenter.GetBindingExpression(ContentPresenter.ContentProperty) is { ParentBinding.Path.Path: "Content" };
}
return null;
}
private static void OnListViewItemClick(object sender, ItemClickEventArgs e)
{
if (sender is not ListViewBase host) return;
TryInvokeCommand(host, /*TryGetItemCommandParameter(host.ContainerFromIndex(host.SelectedIndex)) ??*/ e.ClickedItem);
}
private static void OnSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is not Selector host) return;
TryInvokeCommand(host, TryGetItemCommandParameter(host.ContainerFromIndex(host.SelectedIndex)) ?? host.SelectedItem);
}
private static void OnNavigationViewItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs e)
{
TryInvokeCommand(sender, TryGetItemCommandParameter(e.InvokedItemContainer) ?? e.InvokedItem);
}
private static void OnUIElementTapped(object sender, TappedRoutedEventArgs e)
{
if (sender is not UIElement host) return;
TryInvokeCommand(host, GetCommandParameter(host) ?? host);
}
}
}