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, notTask
and innerTask
must 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.