使用 MVVM 和 .NET MAUI 加载视图时如何将焦点设置到特定的条目控件

问题描述 投票:0回答:3

我在 .NET MAUI MVVM 项目中有一个带有输入控件和两个按钮的视图。当视图加载时,输入控件未获得焦点。如何将焦点设置到入口控件而不在后面的代码中添加功能? IE。 ContentPage 后面的代码只是将 ViewModel 链接到 BindingContext。

据我所知,表单控件不应该直接从 ViewModel 访问,那么在表单加载时是否可以通过其他方式将焦点设置到条目控件?

.net mvvm maui
3个回答
6
投票

我可以让它工作的唯一方法是放入该 xaml 来实现我想要的焦点控制:

Loaded="OnEntryLoaded"

然后在后面的代码中:

private void OnEntryLoaded(object sender, EventArgs e)
{
    Name.Focus();
}

1
投票

正如您在问题中提到的,如果没有 .cs 中的任何代码,我们就无法在 ViewModel 中设置 Entry 焦点。

最简单的方法是在 OnAppearing 方法中设置焦点(允许用户在页面可见之前立即自定义行为):

protected override void OnAppearing()
{
    base.OnAppearing();
    Task.Run(() =>
    {
        while (!myentry.IsVisible)
        {              
            Task.Delay(10).Wait();
        }
        Application.Current.Dispatcher.Dispatch(() =>
        {
            myentry.Focus();
        });
    });
}  

希望它对你有用。


0
投票

简介/背景:

我尝试将对视图的引用限制为仅在视图(xaml)本身内。理想情况下,这意味着 xaml 中没有任何 x:Name="Whatever" ,除非同一 xaml 中的视图引用 xaml 中的另一个视图,如下所示。这样做的一个好处是,例如,我可以将具有 20 个子视图的父视图减少为单个子视图,以便进行故障排除并将我的注意力集中在该单个视图上,然后任何属性更改都是由于该视图而不是其他 19 个视图中的任何一个。此外,如果我需要使视图在 Windows 和 iOS 中具有不同的行为,那么如果我决定进行较大的更改,那么没有这些引用会让事情变得更容易。

解决方案:

您提出的问题无需任何后端代码即可解决。以下是设置步骤的概述:

  1. 在 Visual Studio 2022 中创建一个新的 MAUI 项目。
  2. 将以下 nuget 包添加到项目中:“CommunityToolkit.Maui”版本 7.0.0。
  3. 修改MauiProgram.cs。
  4. 向项目添加仅 XAML 视图,即没有后端 CS 文件的 XAML 文件。
  5. 将视图模型添加到项目中。
  6. 修改AppShell.xaml文件。

我假设步骤 1 和 2 很简单。 (我一直在使用“CommunityToolkit.Maui”版本 7.0.0,在我的 .NET 8 项目中没有任何问题,但是当我尝试添加最新版本时,Visual Studio 抱怨 Android 上下文,所以这就是我回到版本 7 的原因工具包的内容。这对于本次讨论来说是微不足道的。)

第 3 步之后,MauiProgram.cs 应该是这样的(主要兴趣点是“.UseMauiCommunityToolkit()”及其关联的 using 指令):

using CommunityToolkit.Maui;
using Microsoft.Extensions.Logging;

namespace FocusExperiment
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                // Initialize the .NET MAUI Community Toolkit by adding the below line of code
                .UseMauiCommunityToolkit()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });

#if DEBUG
            builder.Logging.AddDebug();
#endif

            return builder.Build();
        }
    }
}

关于第4步,我选择删除MainPage并仅添加一个新的XAML文件,以强调不涉及View的后端代码。我只需向项目添加一个新的 XAML ContentPage 并接受其默认名称“NewPage1.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:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    xmlns:l="clr-namespace:FocusExperiment"
    x:Class="FocusExperiment.NewPage1"
    Title="NewPage1">

    <ContentPage.BindingContext>
        <l:MyViewModel />
    </ContentPage.BindingContext>

    <ContentPage.Behaviors>
        <toolkit:EventToCommandBehavior
            EventName="Loaded"
            Command="{Binding LoadedCommand}"
            CommandParameter="{x:Reference EntryControl}"
            />
    </ContentPage.Behaviors>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Button
            Grid.Row="0"
            Text="Hello"
            />
        <Button
            Grid.Row="1"
            Text="World"
            />
        <Entry
            Grid.Row="2"
            />
        <!--This one gets the focus:-->
        <Entry
            Grid.Row="3"
            x:Name="EntryControl"
            />
        <Entry
            Grid.Row="4"
            />
    </Grid>

</ContentPage>

我在那里放置了 3 个入口视图,并将焦点放在中间的一个上。这样,如果有任何默认行为将焦点放在第一个或最后一个输入控件上,我就不会被它愚弄。

记下“工具包:EventToCommandBehavior”节点。它将行为附加到 ContentPage。 EventName 告诉它监听 ContentPage 的 Loaded 事件。当该事件被广播时,附加的行为将调用 ICommand::Execute 方法(假设 ICommand::CanExecute 返回 true)。我正在传递我想要接收焦点的视图;我使用 CommandParameter 并给它“{x:Reference EntryControl}”来做到这一点。这是可以使用“x:Name=...”的地方,因为它既不耦合到后端也不耦合到视图模型。

另请注意,视图模型是通过“”隐式创建的。

这是第 5 步的视图模型:

using System.Windows.Input;

namespace FocusExperiment;

public class MyViewModel : BindableObject
{
    private class Loaded : ICommand
    {
        public event EventHandler? CanExecuteChanged;

        private bool _canExecute = true;

        public bool CanExecute(object? parameter)
        {
            if (parameter is bool canExecute && _canExecute != canExecute)
            {
                _canExecute = canExecute;
                CanExecuteChanged?.Invoke(this, EventArgs.Empty);
            }

            return _canExecute;
        }

        public void Execute(object? parameter)
        {
            if (parameter is View view)
            {
                view.Focus();
            }
        }
    }

    public ICommand LoadedCommand
    {
        get { return (ICommand)GetValue(LoadedCommandProperty); }
        set { SetValue(LoadedCommandProperty, value); }
    }

    public static readonly BindableProperty LoadedCommandProperty = BindableProperty.Create(nameof(LoadedCommand)
        , typeof(ICommand), typeof(MyViewModel), new Loaded());
}

注意:通过CommandParameter传递的View,其Focus方法是在ICommand::Execute方法中调用的。

最后(第 6 步),将 AppShell.xaml 修改为如下所示:

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="FocusExperiment.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:FocusExperiment"
    Shell.FlyoutBehavior="Disabled"
    Title="FocusExperiment">

    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:NewPage1}"
        Route="NewPage1"
        />

</Shell>
© www.soinside.com 2019 - 2024. All rights reserved.