Issue
Github repository to reproduce issue.
I am working on Xamarin Forms to create an app.
<StackLayout>
<Editor x:Name="Editor"
Text="{Binding Text, Mode=TwoWay}">
</Editor>
<Button x:Name="Button"
Text="Complete"
Command="{Binding CompleteCommand}" />
</StackLayout>
I am trying to lock focus on Editor when ContentView is Visible.
Therefore, I use Editor.Unfocused event to lock focus like below.
public partial class MyView : ContentView
{
private void OnEditorUnfocused(object sender, FocusEventArgs e)
{
if (IsVisible)
{
Editor.Focus();
}
}
}
I register and unregister OnEditorUnfocused method when ContentView.IsVisible property changed like below.
public partial class MyView : ContentView
{
protected override void OnPropertyChanging([CallerMemberName] string propertyName = null)
{
// IsVisible changes from "True" to "False"
if (propertyName.Equals("IsVisible") && IsVisible)
{
Editor.Unfocused -= OnEditorUnfocused;
Editor.Unfocus();
}
base.OnPropertyChanging(propertyName);
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// IsVisible changed from "False" to "True"
if (propertyName.Equals("IsVisible") && IsVisible)
{
Editor.Unfocused += OnEditorUnfocused;
Editor.Focus();
}
base.OnPropertyChanged(propertyName);
}
}
Here I encounter an issue. Editor.Focus() and Editor.Unfocus() does not apply immediately. And when ContentView is not visible, first Editor.Focus() does not work. Furthermore, when ContentView.Visible changes from True to False by Button, Keyboard remains on screen.
Therefore I change my code like below,
public partial class MyView : ContentView
{
private Task _focusEditorTask;
private Task _unfocusEditorTask;
public event EventHandler LostFocused;
public MyView()
{
InitializeComponent();
_focusEditorTask = _unfocusEditorTask = Task.CompletedTask;
}
private void OnEditorUnfocused(object sender, FocusEventArgs e)
{
if (IsVisible)
{
FocusEditor();
Console.WriteLine($"<Unfocused> Editor: Focused {Editor.IsFocused}");
}
}
private void FocusEditor()
{
_unfocusEditorTask.Wait();
_focusEditorTask.Wait();
_focusEditorTask = Task.Factory.StartNew(async () =>
{
Console.WriteLine($"<FocusEditor> START {Editor.IsFocused}");
while (!Editor.IsFocused)
{
Editor.Focus();
await Task.Delay(100);
Console.WriteLine($"<FocusEditor> PROGRESS {Editor.IsFocused}");
}
Console.WriteLine($"<FocusEditor> END {Editor.IsFocused}");
});
}
private void UnfocusEditor()
{
_focusEditorTask.Wait();
_unfocusEditorTask.Wait();
_unfocusEditorTask = Task.Factory.StartNew(async () =>
{
Console.WriteLine($"<!UnfocusEditor> START {Editor.IsFocused}");
while (Editor.IsFocused)
{
Editor.Unfocus();
await Task.Delay(100);
Console.WriteLine($"<!UnfocusEditor> PROGRESS {Editor.IsFocused}");
}
Console.WriteLine($"<!UnfocusEditor> END {Editor.IsFocused}");
return Task.CompletedTask;
});
}
protected override void OnPropertyChanging([CallerMemberName] string propertyName = null)
{
if (propertyName.Equals("IsVisible") && IsVisible)
{
Console.WriteLine($"<IsVisibleChanging> InputView: IsVisible ({IsVisible}) -> {!IsVisible}");
Editor.Unfocused -= OnEditorUnfocused;
UnfocusEditor();
}
base.OnPropertyChanging(propertyName);
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (propertyName.Equals("IsVisible") && IsVisible)
{
Console.WriteLine($"<IsVisibleChanged> InputView: IsVisible {!IsVisible} -> ({IsVisible})");
Editor.Unfocused += OnEditorUnfocused;
FocusEditor();
}
base.OnPropertyChanged(propertyName);
}
}
Now I have only one problem, Wait() does not effective.
Here is Application Output depends on ContnetView.IsVisible changes,
1. change ContnetView.IsVisible from false to true
<IsVisibleChanged> InputView: IsVisible False -> (True)
<FocusTextEditor> START False
<FocusTextEditor> PROGRESS False
<FocusTextEditor> PROGRESS True
<FocusTextEditor> END True
2. change ContentView.IsVisible from true to false by Button
<Unfocused> TextEditor: Focused False
<FocusTextEditor> START False
<IsVisibleChanging> InputView: IsVisible (True) -> False
// START should not fire...
<!UnfocusTextEditor> START False
<!UnfocusTextEditor> END False
// because of this, keyboard does not disappear even ContnetView is not visible.
<FocusTextEditor> PROGRESS True
<FocusTextEditor> END True
Solution
Task.Factory.StartNew()is not awaitable this way. Because it returnsTask<Task>here, notTaskand innerTaskmust be awaited then. But you may simply change it toTask.Run()and fix this completely. And finally you don't need a Threading here. The job can be done simply with asynchronous calls without invoking aTask.Run().You're interacting with UI from pooled Thread, that's not OK.
You locking UI Thread with
.Wait(), that's bad practice and may freeze UI or cause a deadlock in some cases.You may subscribe to the Event once.
I'm not familiar with Xamarin but know well the Asynchronous Programming.
Here's my try to rewrite the code.
public partial class MyView : ContentView
{
public MyView()
{
InitializeComponent();
}
private void OnEditorUnfocused(object sender, FocusEventArgs e)
{
if (Editor.IsVisible)
{
Editor.Focus();
Console.WriteLine($"<Unfocused> Editor: Focused {Editor.IsFocused}");
}
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(IsVisible))
{
if (IsVisible)
{
Editor.Focus();
Editor.Unfocused += OnEditorUnfocused;
}
else
{
Editor.Unfocused -= OnEditorUnfocused;
Editor.Unfocus();
}
Console.WriteLine($"<IsVisibleChanged> InputView: IsVisible {!IsVisible} -> ({IsVisible})");
}
}
}
Answered By - aepot

0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.