Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

add SetFocusOnEntryCompleted (#841) #911

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<pages:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:pages="clr-namespace:Xamarin.CommunityToolkit.Sample.Pages"
x:Class="Xamarin.CommunityToolkit.Sample.Pages.Behaviors.SetFocusOnEntryCompletedBehaviorPage">

<StackLayout Padding="{StaticResource ContentPadding}"
Spacing="50"
VerticalOptions="CenterAndExpand">
<Label Text="Completing one entry will bring focus to the next entry." />
<StackLayout Spacing="3">
<Entry x:Name="Entry1"
Placeholder="Entry 1"
ReturnType="Next"
xct:SetFocusOnEntryCompletedBehavior.NextElement="{x:Reference Entry2}"
/>
<Entry x:Name="Entry2"
Placeholder="Entry 2"
ReturnType="Next"
xct:SetFocusOnEntryCompletedBehavior.NextElement="{x:Reference Entry3}"
/>
<Entry x:Name="Entry3"
Placeholder="Entry 3"
ReturnType="Next"
xct:SetFocusOnEntryCompletedBehavior.NextElement="{x:Reference Entry4}"
/>
<Entry x:Name="Entry4"
Placeholder="Entry 4 (no next entry this time)"
/>
</StackLayout>
</StackLayout>

</pages:BasePage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Xamarin.CommunityToolkit.Sample.Pages.Behaviors
{
public partial class SetFocusOnEntryCompletedBehaviorPage : BasePage
{
public SetFocusOnEntryCompletedBehaviorPage()
=> InitializeComponent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
new SectionModel(
typeof(ProgressBarAnimationBehaviorPage),
nameof(ProgressBarAnimationBehavior),
"Animate the progress for the ProgressBar")
"Animate the progress for the ProgressBar"),
new SectionModel(
typeof(SetFocusOnEntryCompletedBehaviorPage),
nameof(SetFocusOnEntryCompletedBehavior),
"Set focus to another element when an entry is completed"),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Xamarin.CommunityToolkit.Behaviors;
using Xamarin.Forms;
using Xunit;

namespace Xamarin.CommunityToolkit.UnitTests.Behaviors
{
public class SetFocusOnEntryCompleted_Tests
{
[Fact]
public void DoesNotSetFocusBeforeCompletion()
{
// arrange
var entry2 = CreateEntry();
var entry1 = CreateEntry(entry2);

// act
entry1.Focus();
entry1.Text = "text";

// assert
Assert.False(entry2.IsFocused);
}

[Fact]
public void SetsFocusWhenCompleted()
{
// arrange
var entry2 = CreateEntry();
var entry1 = CreateEntry(entry2);

// act
entry1.Focus();
entry1.SendCompleted();

// assert
Assert.True(entry2.IsFocused);
}

public Entry CreateEntry(VisualElement? nextElement = null)
{
var entry = new Entry();

// We simulate Focus/Unfocus behavior ourselves
// because unit tests doesn't have "platform-specific" part
// where IsFocused is controlled in the real app
entry.FocusChangeRequested += (s, e) => entry.SetValue(VisualElement.IsFocusedPropertyKey, e.Focus);

if (nextElement != null)
SetFocusOnEntryCompletedBehavior.SetNextElement(entry, nextElement);

return entry;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using Xamarin.CommunityToolkit.Behaviors.Internals;
using Xamarin.Forms;

namespace Xamarin.CommunityToolkit.Behaviors
{
/// <summary>
/// The <see cref="SetFocusOnEntryCompletedBehavior"/> is an attached property for entries that allows the user to specify what <see cref="VisualElement"/> should gain focus after the user completes that entry.
/// </summary>
public class SetFocusOnEntryCompletedBehavior : BaseBehavior<VisualElement>
{
/// <summary>
/// The <see cref="NextElementProperty"/> attached property.
/// </summary>
public static readonly BindableProperty NextElementProperty =
BindableProperty.CreateAttached(
"NextElement",
typeof(VisualElement),
typeof(SetFocusOnEntryCompletedBehavior),
default(VisualElement),
propertyChanged: OnNextElementChanged);

/// <summary>
/// Required <see cref="GetNextElement"/> accessor for <see cref="NextElementProperty"/> attached property.
/// </summary>
public static VisualElement GetNextElement(BindableObject view) =>
(VisualElement)view.GetValue(NextElementProperty);

/// <summary>
/// Required <see cref="SetNextElement"/> accessor for <see cref="NextElementProperty"/> attached property.
/// </summary>
public static void SetNextElement(BindableObject view, VisualElement value) =>
view.SetValue(NextElementProperty, value);

static void OnNextElementChanged(BindableObject bindable, object oldValue, object newValue)
{
var entry = (Entry)bindable;
var weakEntry = new WeakReference<Entry>(entry);
entry.Completed += completedHandler;

void completedHandler(object? sender, EventArgs e)
{
if (weakEntry.TryGetTarget(out var origEntry))
{
GetNextElement(origEntry)?.Focus();
}
}
}
}
}