NET MAUI - Keep keyboard focused on ListView update

Leon Schimmel 20 Reputation points
2023-03-25T02:46:35.54+00:00

I have a ListView with an ObservableCollection of Database Entries. When I update the Observable collection the focus of the Entry get lost and the keyboard disappears. Even when i set the focus afterwards manually the keyboard does not appear.

Thats my code.

Page.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:models="clr-namespace:LoginManager.Models"
             x:Class="LoginManager.OverviewPage"
             BackgroundColor="{AppThemeBinding Light={StaticResource LightBackground}, Dark={StaticResource DarkBackground}}">

    <Grid Margin="10">

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" ColumnSpacing="{OnPlatform WinUI=50, Default=0}" Margin="{OnPlatform WinUI='5,10,10,10', Default='0,-15,-10,-10'}">

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>

            <Border StrokeThickness="5"
                    StrokeShape="RoundRectangle 20"
                    Padding="0,2.5,0,5"
                    Style="{StaticResource Border}"
                    Grid.Column="0"
                    VerticalOptions="Center">

                <Entry x:Name="SearchEntry"
                       Style="{StaticResource Entry}"
                       BackgroundColor="Transparent"
                       FontSize="{OnPlatform WinUI='50', Default='35'}"
                       Placeholder="Suche"
                       HorizontalTextAlignment="Center"
                       VerticalOptions="Center"
                       Margin="15,0,15,0"
                       IsEnabled="{Binding IsNotBusy}"
                       TextChanged="SearchEntry_TextChanged"/>
            </Border>

            <ImageButton x:Name="SortByBtn"
                         Grid.Column="1"
                         Style="{StaticResource ImageButton}"
                         Source="sort_icon.png"
                         Aspect="AspectFit"
                         BackgroundColor="{AppThemeBinding Light={StaticResource LightAccent}, Dark={StaticResource DarkAccent}}"
                         Margin="0,5,0,5"
                         Padding="15"
                         HeightRequest="115"
                         WidthRequest="115"
                         HorizontalOptions="End"
                         VerticalOptions="Center"
                         IsEnabled="{Binding IsNotBusy}"
                         Pressed="SortByBtn_Pressed"
                         Released="SortByBtn_Released"
                         Clicked="SortByBtn_Clicked"/>
            
        </Grid>

        <Border Grid.Row="1"
                Style="{StaticResource Border}"
                BackgroundColor="{AppThemeBinding Light={StaticResource LightAccent}, Dark={StaticResource DarkAccent}}"
                StrokeThickness="2.5"
                StrokeShape="RoundRectangle 20"
                Padding="2.5"
                Margin="{OnPlatform WinUI='0,7.5,0,7.5', Default='0,0,0,7.5'}"/>

        <ActivityIndicator Grid.Row="2"
                           IsRunning="{Binding IsBusy}"
                           IsVisible="{Binding IsBusy}"
                           ZIndex="1"
                           Margin="100"
                           Color="{AppThemeBinding Light={StaticResource LightPrimary}, Dark={StaticResource DarkPrimary}}"/>

        <ListView x:Name="DatabaseEntriesListView"
                  Grid.Row="2"
                  Margin="20,0"
                  BackgroundColor="Transparent"
                  IsPullToRefreshEnabled="False"
                  HasUnevenRows="True"
                  SeparatorVisibility="None"
                  ItemsSource="{Binding DbEntries}"
                  HorizontalScrollBarVisibility="Never"
                  VerticalScrollBarVisibility="Never"
                  SelectionMode="None"
                  ItemTapped="DatabaseEntriesListView_ItemTapped">

            <ListView.ItemTemplate>
                <DataTemplate x:DataType="models:DbEntry">
                    <ViewCell>
                        <ViewCell.ContextActions>
                            <MenuItem Text="Bearbeiten"/>
                            <MenuItem Text="Löschen"/>
                        </ViewCell.ContextActions>

                        <Border Style="{StaticResource Border}"
                                Stroke="{AppThemeBinding Light={StaticResource LightPrimary}, Dark={StaticResource DarkPrimary}}"
                                BackgroundColor="{AppThemeBinding Light={StaticResource LightSecondary}, Dark={StaticResource DarkSecondary}}"
                                StrokeThickness="5"
                                StrokeShape="RoundRectangle 20"
                                Padding="20"
                                Margin="{OnPlatform WinUI='0,10,0,10', Default='0,2.5,0,2.5'}">
                            <Label Style="{StaticResource Label}"
                                   FontSize="{OnPlatform WinUI='35', Default='25'}"
                                   Text="{Binding Name}"
                                   HorizontalTextAlignment="Center"
                                   VerticalTextAlignment="Center"
                                   MaxLines="1"
                                   LineBreakMode="NoWrap"/>
                        </Border>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>

Page.xaml.cs:

namespace LoginManager;
using LoginManager.Common;
using LoginManager.ViewModels;
public sealed partial class OverviewPage : ContentPage
{
    private readonly OverviewViewModel _vm;
    public OverviewPage(OverviewViewModel vm)
    {
        InitializeComponent();
        BindingContext = _vm = vm;
    }
    protected override async void OnAppearing()
    {
        base.OnAppearing();
        SortByBtn.Scale = 1;
        var loadedSuccessfully = await _vm.GetWholeDb(_vm.CurrentUser);
        if (!loadedSuccessfully)
        {
            await DisplayAlert("Fehler", "Die Einträge konnten nicht geladen werden", "Okay");
            
            await Shell.Current.GoToAsync("..", true);
        }
    }
    private async void SearchEntry_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (!SearchEntry.IsFocused) SearchEntry.Focus();
        await _vm.SearchEntriesBy(e.NewTextValue);
        if (!SearchEntry.IsFocused)
        {
            SearchEntry.Focus();
        }
    }
    private async void SortByBtn_Pressed(object sender, EventArgs e)
    {
        await SortByBtn.ScaleTo(0.95, 100, Easing.Linear);
        await SortByBtn.ScaleTo(1, 100, Easing.Linear);
    }
    private async void SortByBtn_Released(object sender, EventArgs e) => await SortByBtn.ScaleTo(1, 100, Easing.Linear);
    private async void SortByBtn_Clicked(object sender, EventArgs e)
    {
        var chosen = await DisplayActionSheet("Sortieren nach...", null, "Abbrechen",
            "neueste zuerst", "älteste zuerst", "Alphabet aufsteigend", "Alphabet absteigend");
        var sortBy = chosen switch
        {
            "neueste zuerst" => SortBy.IdDESC,
            "älteste zuerst" => SortBy.IdASC,
            "Alphabet aufsteigend" => SortBy.NameASC,
            "Alphabet absteigend" => SortBy.NameDESC,
            _ => SortBy.None
        };
        await _vm.SortEntriesBy(sortBy);
    }
    private void DatabaseEntriesListView_ItemTapped(object sender, ItemTappedEventArgs e)
    {
    }
}

ViewModel.cs:

namespace LoginManager.ViewModels;
using Common;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LoginManager.Models;
using LoginManager.Services;
using System.Collections.ObjectModel;
[QueryProperty(nameof(CurrentUser), nameof(CurrentUser))]
public sealed partial class OverviewViewModel : ObservableObject
{
    private readonly IDatabase _db;
    public ObservableCollection<DbEntry> DbEntries { get; set; } = new();
    public OverviewViewModel(IDatabase db)
    {
        _db = db;
    }
    [ObservableProperty]
    User currentUser;
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(IsNotBusy))]
    bool isBusy;
    public bool IsNotBusy => !IsBusy;
    [RelayCommand]
    public async Task<bool> GetWholeDb(User currentUser)
    {
        await LoadDb(currentUser);
        return DbEntries.Count is not 0;
    }
    [RelayCommand]
    public Task SortEntriesBy(SortBy sortBy)
    {
        IsBusy = true;
        switch (sortBy)
        {
            case SortBy.NameASC:
                DbEntries.SortBy(x => x.Name);
                break;
            case SortBy.NameDESC:
                DbEntries.SortBy(x => x.Name, false);
                break;
            case SortBy.IdASC:
                DbEntries.SortBy(x => x.Id);
                break;
            case SortBy.IdDESC:
                DbEntries.SortBy(x => x.Id, false);
                break;
        }
        IsBusy = false;
        return Task.CompletedTask;
    }
    [RelayCommand]
    public Task SearchEntriesBy(string? query)
    {
        IsBusy = true;
        if (!string.IsNullOrWhiteSpace(query)) DbEntries.SortBy(x => x.Name.Count(x => query.Contains(x)));
        IsBusy = false;
        return Task.CompletedTask;
    }
    private async Task LoadDb(User currentUser)
    {
        IsBusy = true;
        await foreach (var entry in _db.GetWholeAsync(currentUser))
        {
            DbEntries.Add(entry);
        }
        IsBusy = false;
    }
}

I tried setting a Delay for the TextChanged Event but it doesn't work either. Also I don't want the Entry being unfocused and the keyboard going away at all.

.NET MAUI
.NET MAUI
A Microsoft open-source framework for building native device applications spanning mobile, tablet, and desktop.
3,411 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,842 questions
{count} votes

Accepted answer
  1. Wenyan Zhang (Shanghai Wicresoft Co,.Ltd.) 30,666 Reputation points Microsoft Vendor
    2023-03-25T07:49:21.4366667+00:00

    Hello,

    You set the IsEnabled property for the entry :<Entry x:Name="SearchEntry" ...IsEnabled="{Binding IsNotBusy}"/> It makes entry lost focus when IsEnable is False.

    In addition, the view model's IsNotBusy property changes every time the Entries are sorted:

    public bool IsNotBusy => !IsBusy;
    
    public Task SortEntriesBy(SortBy sortBy) { 
    IsBusy = true;// will make the entry lost focus 
    ... IsBusy = false;// will not make the entry focus
     return Task.CompletedTask; 
    }
    

    You can try to remove the binding : IsEnabled="{Binding IsNotBusy}" to check if it works.

    Another way is to pass the SearchEntry into your ViewModel, then set SearchEntry.Focus() after setting IsBusy = false.

    Besides, you can keep keyboard focused for Android platform.

    
    #if ANDROID
                Android.Views.InputMethods.InputMethodManager inputManager = (Android.Views.InputMethods.InputMethodManager)Android.App.Application.Context.ApplicationContext.GetSystemService(Android.Content.Context.InputMethodService);
                inputManager.ToggleSoftInput(Android.Views.InputMethods.InputMethodManager.ShowForced, Android.Views.InputMethods.HideSoftInputFlags.ImplicitOnly);
    #endif
    

    Best Regards,

    Wenyan Zhang


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.