Merge pull request #481 from irihitech/issue-479
Allow user to set Modal Dialog initial focus element
This commit is contained in:
@@ -17,7 +17,10 @@
|
|||||||
<GradientStop Offset="0.9" Color="{DynamicResource SemiLightBlue1Color}" />
|
<GradientStop Offset="0.9" Color="{DynamicResource SemiLightBlue1Color}" />
|
||||||
</LinearGradientBrush>
|
</LinearGradientBrush>
|
||||||
</UserControl.Background>
|
</UserControl.Background>
|
||||||
<Grid Margin="24" RowDefinitions="Auto, *, Auto" MinWidth="400">
|
<Grid
|
||||||
|
MinWidth="400"
|
||||||
|
Margin="24"
|
||||||
|
RowDefinitions="Auto, *, Auto">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Margin="8"
|
Margin="8"
|
||||||
@@ -45,7 +48,10 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="32,8,0,8"
|
Margin="32,8,0,8"
|
||||||
Label="Owner">
|
Label="Owner">
|
||||||
<TextBox u:FormItem.Label="Owner" Text="{Binding Owner}" />
|
<TextBox
|
||||||
|
u:FocusHelper.DialogFocusHint="True"
|
||||||
|
u:FormItem.Label="Owner"
|
||||||
|
Text="{Binding Owner}" />
|
||||||
</u:FormItem>
|
</u:FormItem>
|
||||||
<u:FormItem
|
<u:FormItem
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
@@ -69,9 +75,20 @@
|
|||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="8">
|
Spacing="8">
|
||||||
<Button Command="{Binding DialogCommand}" Content="Dialog" Theme="{DynamicResource SolidButton}" />
|
<Button
|
||||||
<Button Command="{Binding OKCommand}" Content="OK" Theme="{DynamicResource SolidButton}" Classes="Tertiary"/>
|
Command="{Binding DialogCommand}"
|
||||||
<Button Command="{Binding CancelCommand}" Content="Cancel" Theme="{DynamicResource SolidButton}" Classes="Tertiary"/>
|
Content="Dialog"
|
||||||
|
Theme="{DynamicResource SolidButton}" />
|
||||||
|
<Button
|
||||||
|
Classes="Tertiary"
|
||||||
|
Command="{Binding OKCommand}"
|
||||||
|
Content="OK"
|
||||||
|
Theme="{DynamicResource SolidButton}" />
|
||||||
|
<Button
|
||||||
|
Classes="Tertiary"
|
||||||
|
Command="{Binding CancelCommand}"
|
||||||
|
Content="Cancel"
|
||||||
|
Theme="{DynamicResource SolidButton}" />
|
||||||
<ComboBox>
|
<ComboBox>
|
||||||
<ComboBoxItem>A</ComboBoxItem>
|
<ComboBoxItem>A</ComboBoxItem>
|
||||||
<ComboBoxItem>B</ComboBoxItem>
|
<ComboBoxItem>B</ComboBoxItem>
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ using Avalonia.Metadata;
|
|||||||
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")]
|
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")]
|
||||||
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")]
|
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")]
|
||||||
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls.Shapes")]
|
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls.Shapes")]
|
||||||
|
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Helpers")]
|
||||||
@@ -4,6 +4,7 @@ using Avalonia.VisualTree;
|
|||||||
using Irihi.Avalonia.Shared.Helpers;
|
using Irihi.Avalonia.Shared.Helpers;
|
||||||
using Irihi.Avalonia.Shared.Shapes;
|
using Irihi.Avalonia.Shared.Shapes;
|
||||||
using Ursa.Controls.OverlayShared;
|
using Ursa.Controls.OverlayShared;
|
||||||
|
using Ursa.Helpers;
|
||||||
|
|
||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
@@ -111,14 +112,20 @@ public partial class OverlayDialogHost
|
|||||||
SetToPosition(control);
|
SetToPosition(control);
|
||||||
control.AddHandler(OverlayFeedbackElement.ClosedEvent, OnDialogControlClosing);
|
control.AddHandler(OverlayFeedbackElement.ClosedEvent, OnDialogControlClosing);
|
||||||
control.AddHandler(DialogControlBase.LayerChangedEvent, OnDialogLayerChanged);
|
control.AddHandler(DialogControlBase.LayerChangedEvent, OnDialogLayerChanged);
|
||||||
|
// Notice: mask animation here is not really awaited, because currently dialogs appears immediately.
|
||||||
if (!IsAnimationDisabled) MaskAppearAnimation.RunAsync(mask);
|
if (!IsAnimationDisabled) MaskAppearAnimation.RunAsync(mask);
|
||||||
|
|
||||||
var element = control.GetVisualDescendants().OfType<InputElement>().FirstOrDefault(a => a.Focusable);
|
var element = control.GetVisualDescendants().OfType<InputElement>()
|
||||||
|
.FirstOrDefault(FocusHelper.GetDialogFocusHint);
|
||||||
|
if (element is null)
|
||||||
|
{
|
||||||
|
element = control.GetVisualDescendants().OfType<InputElement>().FirstOrDefault(a => a.Focusable);
|
||||||
|
}
|
||||||
element?.Focus();
|
element?.Focus();
|
||||||
_modalCount++;
|
_modalCount++;
|
||||||
IsInModalStatus = _modalCount > 0;
|
IsInModalStatus = _modalCount > 0;
|
||||||
control.IsClosed = false;
|
control.IsClosed = false;
|
||||||
control.Focus();
|
// control.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle dialog layer change event
|
// Handle dialog layer change event
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Irihi.Avalonia.Shared.Shapes;
|
|||||||
using Ursa.Common;
|
using Ursa.Common;
|
||||||
using Ursa.Controls.OverlayShared;
|
using Ursa.Controls.OverlayShared;
|
||||||
using Ursa.EventArgs;
|
using Ursa.EventArgs;
|
||||||
|
using Ursa.Helpers;
|
||||||
|
|
||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
@@ -69,7 +70,13 @@ public partial class OverlayDialogHost
|
|||||||
{
|
{
|
||||||
await Task.WhenAll(animation.RunAsync(control), MaskAppearAnimation.RunAsync(mask));
|
await Task.WhenAll(animation.RunAsync(control), MaskAppearAnimation.RunAsync(mask));
|
||||||
}
|
}
|
||||||
var element = control.GetVisualDescendants().OfType<InputElement>().FirstOrDefault(a => a.Focusable);
|
|
||||||
|
var element = control.GetVisualDescendants().OfType<InputElement>()
|
||||||
|
.FirstOrDefault(FocusHelper.GetDialogFocusHint);
|
||||||
|
if (element is null)
|
||||||
|
{
|
||||||
|
element = control.GetVisualDescendants().OfType<InputElement>().FirstOrDefault(a => a.Focusable);
|
||||||
|
}
|
||||||
element?.Focus();
|
element?.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
src/Ursa/Helpers/FocusHelper.cs
Normal file
13
src/Ursa/Helpers/FocusHelper.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Input;
|
||||||
|
|
||||||
|
namespace Ursa.Helpers;
|
||||||
|
|
||||||
|
public class FocusHelper
|
||||||
|
{
|
||||||
|
public static readonly AttachedProperty<bool> DialogFocusHintProperty =
|
||||||
|
AvaloniaProperty.RegisterAttached<FocusHelper, InputElement, bool>("DialogFocusHint");
|
||||||
|
|
||||||
|
public static void SetDialogFocusHint(InputElement obj, bool value) => obj.SetValue(DialogFocusHintProperty, value);
|
||||||
|
public static bool GetDialogFocusHint(InputElement obj) => obj.GetValue(DialogFocusHintProperty);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:u="https://irihi.tech/ursa"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
x:Class="HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus.FocusDialog">
|
||||||
|
<TextBox Text="Hello" u:FocusHelper.DialogFocusHint="True"></TextBox>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus;
|
||||||
|
|
||||||
|
public partial class FocusDialog : UserControl
|
||||||
|
{
|
||||||
|
public FocusDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
x:Class="HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus.NormalDialog">
|
||||||
|
<TextBox Text="Hello"></TextBox>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus;
|
||||||
|
|
||||||
|
public partial class NormalDialog : UserControl
|
||||||
|
{
|
||||||
|
public NormalDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus;
|
||||||
|
|
||||||
|
public class Test
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public async Task Normal_Drawer_Focus_On_Border()
|
||||||
|
{
|
||||||
|
var ursaWindow = new TestWindow();
|
||||||
|
ursaWindow.Show();
|
||||||
|
ursaWindow.InvokeNormalDrawer();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
await Task.Delay(500);
|
||||||
|
var dialog = ursaWindow.GetVisualDescendants().OfType<DefaultDrawerControl>().FirstOrDefault();
|
||||||
|
Assert.NotNull(dialog);
|
||||||
|
var border = dialog.GetVisualDescendants().OfType<Border>().FirstOrDefault(a=>a.Name == "PART_Root");
|
||||||
|
var text = dialog.GetVisualDescendants().OfType<TextBox>().FirstOrDefault();
|
||||||
|
Assert.True(border?.IsFocused);
|
||||||
|
Assert.False(text?.IsFocused);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public async Task Focus_Drawer_Focus_On_Primary()
|
||||||
|
{
|
||||||
|
var ursaWindow = new TestWindow();
|
||||||
|
ursaWindow.Show();
|
||||||
|
ursaWindow.InvokeFocusDrawer();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
await Task.Delay(500);
|
||||||
|
var dialog = ursaWindow.GetVisualDescendants().OfType<DefaultDrawerControl>().FirstOrDefault();
|
||||||
|
Assert.NotNull(dialog);
|
||||||
|
var border = dialog.GetVisualDescendants().OfType<Border>().FirstOrDefault(a=>a.Name == "PART_Root");
|
||||||
|
var text = dialog.GetVisualDescendants().OfType<TextBox>().FirstOrDefault();
|
||||||
|
Assert.False(border?.IsFocused);
|
||||||
|
Assert.True(text?.IsFocused);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public async Task Normal_Dialog_Focus_On_Border()
|
||||||
|
{
|
||||||
|
var ursaWindow = new TestWindow();
|
||||||
|
ursaWindow.Show();
|
||||||
|
ursaWindow.InvokeNormalDialog();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
await Task.Delay(100);
|
||||||
|
var dialog = ursaWindow.GetVisualDescendants().OfType<DefaultDialogControl>().FirstOrDefault();
|
||||||
|
Assert.NotNull(dialog);
|
||||||
|
var border = dialog.GetVisualDescendants().OfType<Border>().FirstOrDefault(a=>a.Name == "PART_Border");
|
||||||
|
var text = dialog.GetVisualDescendants().OfType<TextBox>().FirstOrDefault();
|
||||||
|
Assert.True(border?.IsFocused);
|
||||||
|
Assert.False(text?.IsFocused);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public async Task Focus_Dialog_Focus_On_Primary()
|
||||||
|
{
|
||||||
|
var ursaWindow = new TestWindow();
|
||||||
|
ursaWindow.Show();
|
||||||
|
ursaWindow.InvokeFocusDialog();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
await Task.Delay(100);
|
||||||
|
var dialog = ursaWindow.GetVisualDescendants().OfType<DefaultDialogControl>().FirstOrDefault();
|
||||||
|
Assert.NotNull(dialog);
|
||||||
|
var border = dialog.GetVisualDescendants().OfType<Border>().FirstOrDefault(a=>a.Name == "PART_Border");
|
||||||
|
var text = dialog.GetVisualDescendants().OfType<TextBox>().FirstOrDefault();
|
||||||
|
Assert.False(border?.IsFocused);
|
||||||
|
Assert.True(text?.IsFocused);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<u:UrsaWindow xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:u="https://irihi.tech/ursa"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
x:Class="HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus.TestWindow"
|
||||||
|
Title="TestWindow">
|
||||||
|
Welcome to Avalonia!
|
||||||
|
</u:UrsaWindow>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Ursa.Controls;
|
||||||
|
using Ursa.Controls.Options;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus;
|
||||||
|
|
||||||
|
public partial class TestWindow : UrsaWindow
|
||||||
|
{
|
||||||
|
public TestWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvokeNormalDrawer()
|
||||||
|
{
|
||||||
|
Drawer.ShowModal<NormalDialog, object>("Hello World",
|
||||||
|
options: new DrawerOptions() { TopLevelHashCode = GetHashCode() });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvokeFocusDrawer()
|
||||||
|
{
|
||||||
|
Drawer.ShowModal<FocusDialog, object>("Hello World",
|
||||||
|
options: new DrawerOptions() { TopLevelHashCode = GetHashCode() });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvokeNormalDialog()
|
||||||
|
{
|
||||||
|
OverlayDialog.ShowModal<NormalDialog, object>("Hello World",
|
||||||
|
options: new OverlayDialogOptions() { TopLevelHashCode = GetHashCode() });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvokeFocusDialog()
|
||||||
|
{
|
||||||
|
OverlayDialog.ShowModal<FocusDialog, object>("Hello World",
|
||||||
|
options: new OverlayDialogOptions() { TopLevelHashCode = GetHashCode() });
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user