Accelerator keys for ContentDialog Buttons
source link: https://www.reflectionit.nl/blog/2022/accelerator-keys-for-contentdialog-buttons
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Accelerator keys for ContentDialog Buttons
By Fons Sonnemans,
25-feb-2022
Accelerator keys (or keyboard accelerators) are keyboard shortcuts that improve the usability and accessibility of your Windows applications by providing an intuitive way for users to invoke common actions or commands without navigating the app UI. You can add them to a Button using the KeyboardAccelerators property. See the example below.
<
Button
HorizontalAlignment
=
"Center"
Click
=
"Button_Click"
Content
=
"Test"
>
<
Button.KeyboardAccelerators
>
<
KeyboardAccelerator
Key
=
"T"
/>
</
Button.KeyboardAccelerators
>
</
Button
>
A keyboard is indispensable for users with certain disabilities (see Keyboard accessibility), and is also an important tool for users who prefer it as a more efficient way to interact with an app.
ContenDialog
ContenDialog controls in WinUI3 and UWP are modal UI overlays that provide contextual app information. They block interactions with the app window until being explicitly dismissed. They often request some kind of action from the user. They replace the MessageBox from Windows Forms and WPF.
You can create the above ContentDialog using code.
private
async
void
Button_Click(
object
sender, RoutedEventArgs e) {
var confirmDialog =
new
ContentDialog {
Title =
"Confirm"
,
PrimaryButtonText =
"Yes"
,
SecondaryButtonText =
"No"
,
CloseButtonText =
"Cancel"
,
Content =
"Are you sure you want this?"
,
};
var result =
await
confirmDialog.ShowAsync();
(sender
as
Button).Content = result.ToString();
}
You can also add a ConfirmDialog to your project which makes it easier create a more complex content. The following ConfirmContentDialog using the ContentDialog Item Template. This template is missing in WinUI. You can workaround this by coping a UWP ContentDialog.xaml into your WinUI project and then rename the namespace Windows.UI.* to Microsoft.UI.*.
<
ContentDialog
x:Class
=
"ContentDialogDemoUWP.ConfirmContentDialog"
Title
=
"Confirm"
CloseButtonText
=
"Cancel"
PrimaryButtonText
=
"Yes"
SecondaryButtonText
=
"No"
>
<
Grid
>
<
TextBlock
Text
=
"Are you sure you want this?"
/>
</
Grid
>
</
ContentDialog
>
You can show this ConfirmContentDialog using the ShowAsync() method. The result (Which action button was clicked: Primary, Secondary or Close (None)) is then placed in the Content of the Button which opened the content dialog.
private
async
void
Button_Click(
object
sender, RoutedEventArgs e) {
var confirmDialog =
new
ConfirmContentDialog();
var result =
await
confirmDialog.ShowAsync();
(sender
as
Button).Content = result.ToString();
}
A ContentDialog can have 3 buttons: Primary, Secondary and Close. The Close button has always the Escape as access key. The Primary and Secondary have none. There is no easy way to set them. There is only a Text and a Style property for each button in the ContentDialog. The PrimaryKeyboardAccelerators and SecondaryKeyboardAccelerators properties are just not there. I thought I could 'fix' this by setting the KeyboardAccelerators property using the Style the Button. Unfortunatly that didn't work because this KeyboardAccelerators property is not a DependencyProperty. Only DependencyProperty can be set in a Style. I already created this issue to get this fixed in WinUI3.
Solution
The solution I found is quite simple. In XAML you can create attachted properties. An attached property is a Extensible Application Markup Language (XAML) concept. Attached properties enable extra property/value pairs to be set on any XAML element that derives from DependencyObject, even though the element doesn't define those extra properties in its object model. The attached properties can be used in the Style which allows me to set the KeyboardAccelerators for the Buttons.
I created a KeyboardAccelerator and KeyboardAccelerators attached properties in my KeyboardAcceleratorsServices class. The first one is used if you only need one KeyboardAccelerator. The second one if you need more than one. The OnKeyboardAcceleratorChanged() and OnKeyboardAcceleratorsChanged() methods add the KeyboardAccelerator(s) to the Button.
using
System.Collections.Generic;
using
Windows.UI.Xaml;
using
Windows.UI.Xaml.Input;
namespace
ContentDialogDemoUWP.Helpers {
public
class
KeyboardAcceleratorsServices {
#region KeyboardAccelerator Attached Property
/// <summary>
/// Identifies the KeyboardAccelerator attachted property. This enables animation, styling, binding, etc...
/// </summary>
public
static
readonly
DependencyProperty KeyboardAcceleratorProperty =
DependencyProperty.RegisterAttached(
"KeyboardAccelerator"
,
typeof
(KeyboardAccelerator),
typeof
(KeyboardAcceleratorsServices),
new
PropertyMetadata(
default
(KeyboardAccelerator), OnKeyboardAcceleratorChanged));
/// <summary>
/// KeyboardAccelerator changed handler.
/// </summary>
/// <param name="d">UIElement that changed its KeyboardAccelerator attached property.</param>
/// <param name="e">DependencyPropertyChangedEventArgs with the new and old value.</param>
private
static
void
OnKeyboardAcceleratorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if
(d
is
UIElement source) {
if
(e.NewValue
is
KeyboardAccelerator value) {
source.KeyboardAccelerators.Add(value);
}
}
}
/// <summary>
/// Gets the value of the KeyboardAccelerator attached property from the specified FrameworkElement .
/// </summary>
public
static
KeyboardAccelerator GetKeyboardAccelerator(DependencyObject obj) {
return
(KeyboardAccelerator)obj.GetValue(KeyboardAcceleratorProperty);
}
/// <summary>
/// Sets the value of the KeyboardAccelerator attached property to the specified FrameworkElement .
/// </summary>
/// <param name="obj">The object on which to set the KeyboardAccelerator attached property.</param>
/// <param name="value">The property value to set.</param>
public
static
void
SetKeyboardAccelerator(DependencyObject obj, KeyboardAccelerator value) {
obj.SetValue(KeyboardAcceleratorProperty, value);
}
#endregion KeyboardAccelerator Attached Property
#region KeyboardAccelerators Attached Property
/// <summary>
/// Identifies the KeyboardAccelerators attachted property. This enables animation, styling, binding, etc...
/// </summary>
public
static
readonly
DependencyProperty KeyboardAcceleratorsProperty =
DependencyProperty.RegisterAttached(
"KeyboardAccelerators"
,
typeof
(IList<KeyboardAccelerator>),
typeof
(KeyboardAcceleratorsServices),
new
PropertyMetadata(
default
, OnKeyboardAcceleratorsChanged));
/// <summary>
/// KeyboardAccelerators changed handler.
/// </summary>
/// <param name="d">UIElement that changed its KeyboardAccelerators attached property.</param>
/// <param name="e">DependencyPropertyChangedEventArgs with the new and old value.</param>
private
static
void
OnKeyboardAcceleratorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if
(d
is
UIElement source) {
if
(e.NewValue
is
IList<KeyboardAccelerator> value) {
foreach
(var item
in
value) {
source.KeyboardAccelerators.Add(item);
}
}
}
}
/// <summary>
/// Gets the value of the KeyboardAccelerators attached property from the specified FrameworkElement.
/// </summary>
public
static
object
GetKeyboardAccelerators(DependencyObject obj) {
return
(
object
)obj.GetValue(KeyboardAcceleratorsProperty);
}
/// <summary>
/// Sets the value of the KeyboardAccelerators attached property to the specified FrameworkElement.
/// </summary>
/// <param name="obj">The object on which to set the KeyboardAccelerators attached property.</param>
/// <param name="value">The property value to set.</param>
public
static
void
SetKeyboardAccelerators(DependencyObject obj,
object
value) {
obj.SetValue(KeyboardAcceleratorsProperty, value);
}
#endregion KeyboardAccelerators Attached Property
}
public
class
KeyboardAcceleratorList : List<KeyboardAccelerator> {
}
}
I can now create a Style for the Primary and Secondary buttons in the ContentDialog. In this Style I can use the attached properties to set the KeyboardAccelerator. The PrimaryButton (Yes) has two KeyboardAccelerators (lines 11-18). One for the Y key and another one for the J key (just for demo purpose). The SecondaryButton (No) has only one for the N key (lines 23-27). I also had to add the xml namespace helpers to the ContentDialog element, line 4.
<
ContentDialog
x:Class
=
"ContentDialogDemoUWP.ConfirmContentDialog"
xmlns:helpers
=
"using:ContentDialogDemoUWP.Helpers"
Title
=
"Confirm"
CloseButtonText
=
"Cancel"
PrimaryButtonText
=
"Yes"
SecondaryButtonText
=
"No"
>
<
ContentDialog.PrimaryButtonStyle
>
<
Style
BasedOn
=
"{StaticResource AccentButtonStyle}"
TargetType
=
"Button"
>
<
Setter
Property
=
"helpers:KeyboardAcceleratorsServices.KeyboardAccelerators"
>
<
Setter.Value
>
<
helpers:KeyboardAcceleratorList
>
<
KeyboardAccelerator
Key
=
"Y"
/>
<
KeyboardAccelerator
Key
=
"J"
/>
</
helpers:KeyboardAcceleratorList
>
</
Setter.Value
>
</
Setter
>
</
Style
>
</
ContentDialog.PrimaryButtonStyle
>
<
ContentDialog.SecondaryButtonStyle
>
<
Style
TargetType
=
"Button"
>
<
Setter
Property
=
"helpers:KeyboardAcceleratorsServices.KeyboardAccelerator"
>
<
Setter.Value
>
<
KeyboardAccelerator
Key
=
"N"
/>
</
Setter.Value
>
</
Setter
>
</
Style
>
</
ContentDialog.SecondaryButtonStyle
>
<
Grid
>
<
TextBlock
Text
=
"Are you sure you want this?"
/>
</
Grid
>
</
ContentDialog
>
And that’s it – the user can now use the Y, J or N keys to select the correct action. The sample projects (UWP and WinUI3) are available on GitHub for full source code.
Leave a Comment
Leave a Comment0 Comments
All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK