Xamarin表单中没有条目的页面上的焦点问题

ecbunoof  于 2023-04-18  发布在  其他
关注(0)|答案(1)|浏览(143)

我正在努力解决Xamarin Forms Android的焦点问题。
我们通常在带有外接键盘的手持设备上运行这个应用程序。我的应用程序在应用程序的顶部有一个导航栏,条目通常在底部。
在导航栏中有一个“退出”图像按钮,用于返回上一页,还有一些其他按钮用于执行特殊功能。
每个页面的页脚都有条目,但很少有页面没有条目。虽然有条目的页面可以正确聚焦,当我按回车键时,与条目的“ReturnCommand”相关联的EnterCommand会正确触发,当没有条目时,第一个元素会聚焦,回车键会触发“Escape”图像按钮的“Click/Press”,返回到上一页。
如何防止图像按钮聚焦或如何聚焦页面上的其他控件?
这是每个页面的基本样式:

<Grid HorizontalOptions="FillAndExpand"
      VerticalOptions="FillAndExpand"
      BackgroundColor="{StaticResource NavigationBarColor}"
      Padding="0,5">
    <Grid.Resources>
        <Style TargetType="ImageButton">
            <Style.Setters>
                <Setter Property="Padding"
                        Value="0" />
                <Setter Property="BackgroundColor"
                        Value="Transparent" />
                <Setter Property="VerticalOptions"
                        Value="CenterAndExpand" />
            </Style.Setters>
        </Style>
    </Grid.Resources>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="33" />
        <ColumnDefinition />
        <ColumnDefinition Width="33" />
        <ColumnDefinition Width="33" />
    </Grid.ColumnDefinitions>

    <ImageButton Command="{TemplateBinding EscapeCommand}"
                 Source="{helpers:ImageResource Previous}"
                 IsVisible="{Binding IsEnabled, Source={RelativeSource Mode=Self}}" />

    <ImageButton Command="{TemplateBinding OpenMenuCommand}"
                 Source="{helpers:ImageResource Menu}"
                 IsVisible="{Binding IsEnabled, Source={RelativeSource Mode=Self}}" />

    <Label Text="{TemplateBinding ViewModel.Title}"
           Grid.Column="1"
           FontSize="Medium"
           Padding="0,2"
           FontAttributes="Bold"
           MaxLines="2"
           LineBreakMode="TailTruncation"
           VerticalOptions="FillAndExpand"
           VerticalTextAlignment="Center" />

    <ImageButton Command="{TemplateBinding ApriOpzioniCommand}"
                 Source="{helpers:ImageResource More}"
                 IsVisible="{Binding IsEnabled, Source={RelativeSource Mode=Self}}"
                 Grid.Column="2" />

    <ImageButton Command="{TemplateBinding ViewModel.OkCommand}"
                 Source="{helpers:ImageResource Next}"
                 IsVisible="{Binding IsEnabled, Source={RelativeSource Mode=Self}}"
                 IsEnabled="False"
                 Grid.Column="3" />
</Grid>
<!-- End NavigationBar -->

<ScrollView x:Name="scrollViewContent"
            Style="{StaticResource ScrollViewBaseStyle}"
            Grid.Row="1">
    <StackLayout VerticalOptions="FillAndExpand"
                 HorizontalOptions="FillAndExpand"
                 Spacing="0">

        <!-- Begin Header -->
        <Frame Style="{StaticResource FrameBaseStyle}"
               CornerRadius="6">
            <ContentView Content="{TemplateBinding Header}"
                         Style="{StaticResource ContentViewStyle}"
                         TabIndex="2"
                         IsTabStop="False" />
        </Frame>
        <!-- End Header -->

        <!-- Begin Content -->
        <ContentPresenter Style="{StaticResource ContentPresenterBaseStyle}"
                          TabIndex="1" />
        <!-- End Content -->

    </StackLayout>
</ScrollView>

<!-- Begin Footer -->
<ContentView Content="{TemplateBinding Footer}"
             Grid.Row="2"
             Margin="5"
             TabIndex="0"
             Style="{StaticResource ContentViewStyle}" />

每个页面的视图模型库:

public abstract class ContentPageBase<TViewModel> : ReactiveContentPage<TViewModel>, IAlertPage, ITemplatedPage, IDisposable
    where TViewModel : NavigationViewModelBase
{
    private bool _initialized;
    private readonly Subject<int> _pagesCount;
    private readonly Subject<bool> _isOpzioniDisponibili;

    protected ContentPageBase(IKeyboardService keyboardListener)
    {
        KeyboardListener = keyboardListener ?? throw new ArgumentNullException(nameof(keyboardListener));

        _pagesCount = new Subject<int>();
        _isOpzioniDisponibili = new Subject<bool>();

        ApriOpzioniCommand = ReactiveCommand.Create(
            MostraNascondiOpzioni,
            _isOpzioniDisponibili);
        ChiudiOpzioniCommand = ReactiveCommand.Create(
            NascondiOpzioni,
            _isOpzioniDisponibili);

        EscapeCommand = ReactiveCommand.CreateFromTask(
            OnEscapePressedAsync,
            this.WhenAnyValue(v => v.IsOpzioniVisible)
                .CombineLatest(_pagesCount, (v1, v2) => v1 || v2 > 1));

        OpenMenuCommand = ReactiveCommand.Create(
            OnMenuPressed,
            _pagesCount.Select(c => c == 1 && !IsMenuShown));

        InitLayout();

        this.WhenActivated(disposables =>
        {
            RegisterInteractions(disposables);

            InitBindings(disposables);

            WhenActivated(disposables);
            WhenViewFocused();

            _initialized = true;
        });
    }

    public ReactiveCommand<Unit, Unit> OpenMenuCommand { get; }

    public ReactiveCommand<Unit, Unit> ApriOpzioniCommand { get; }

    public ReactiveCommand<Unit, Unit> ChiudiOpzioniCommand { get; }

    public ReactiveCommand<Unit, Unit> EscapeCommand { get; }

    public bool IsMenuShown =>
        (Application.Current.MainPage as MasterDetailPage)?.IsPresented == true;

    protected IKeyboardService KeyboardListener { get; }

    #region Bindable properties

    public static readonly BindableProperty HeaderBackgroundColorProperty = BindableProperty.Create(
        propertyName: nameof(HeaderBackgroundColor),
        returnType: typeof(Color),
        declaringType: typeof(Color),
        defaultValue: Color.FromHex("#363636"),
        defaultBindingMode: BindingMode.OneTime);

    public static readonly BindableProperty HeaderPaddingProperty = BindableProperty.Create(
        propertyName: nameof(HeaderPadding),
        returnType: typeof(Thickness),
        declaringType: typeof(ContentPageBase<>),
        defaultValue: new Thickness(10),
        defaultBindingMode: BindingMode.OneTime);

    public static readonly BindableProperty HeaderMarginProperty = BindableProperty.Create(
        propertyName: nameof(HeaderMargin),
        returnType: typeof(Thickness),
        declaringType: typeof(ContentPageBase<>),
        defaultValue: new Thickness(10),
        defaultBindingMode: BindingMode.OneTime);

    public static readonly BindableProperty HeaderProperty = BindableProperty.Create(
        propertyName: nameof(Header),
        returnType: typeof(View),
        declaringType: typeof(ContentPageBase<>),
        defaultValue: null,
        defaultBindingMode: BindingMode.OneTime);

    public static readonly BindableProperty FooterProperty = BindableProperty.Create(
        propertyName: nameof(Footer),
        returnType: typeof(View),
        declaringType: typeof(ContentPageBase<>),
        defaultValue: null,
        defaultBindingMode: BindingMode.OneTime);

    public static readonly BindableProperty OpzioniProperty = BindableProperty.Create(
        propertyName: nameof(Opzioni),
        returnType: typeof(View),
        declaringType: typeof(ContentPageBase<>),
        defaultValue: null,
        defaultBindingMode: BindingMode.OneTime);

    public static readonly BindableProperty IsOpzioniVisibleProperty = BindableProperty.Create(
        propertyName: nameof(IsOpzioniVisible),
        returnType: typeof(bool),
        declaringType: typeof(ContentPageBase<>),
        defaultValue: false,
        defaultBindingMode: BindingMode.OneWay);

    public static readonly BindableProperty ViewCellTemplateProperty = BindableProperty.Create(
        propertyName: nameof(ViewCellTemplate),
        returnType: typeof(DataTemplate),
        declaringType: typeof(ContentPageBase<>),
        defaultValue: null,
        defaultBindingMode: BindingMode.OneTime);

    public static readonly BindableProperty RiepilogoViewProperty = BindableProperty.Create(
        propertyName: nameof(RiepilogoView),
        returnType: typeof(IViewFor),
        declaringType: typeof(ContentPageBase<>),
        defaultValue: null,
        defaultBindingMode: BindingMode.OneTime);

    public Color HeaderBackgroundColor
    {
        get => (Color)GetValue(HeaderBackgroundColorProperty);
        set => SetValue(HeaderBackgroundColorProperty, value);
    }

    public Thickness HeaderMargin
    {
        get => (Thickness)GetValue(HeaderMarginProperty);
        set => SetValue(HeaderMarginProperty, value);
    }

    public Thickness HeaderPadding
    {
        get => (Thickness)GetValue(HeaderPaddingProperty);
        set => SetValue(HeaderPaddingProperty, value);
    }

    public View Header
    {
        get => (View)GetValue(HeaderProperty);
        set => SetValue(HeaderProperty, value);
    }

    public View Footer
    {
        get => (View)GetValue(FooterProperty);
        set => SetValue(FooterProperty, value);
    }

    public View Opzioni
    {
        get => (View)GetValue(OpzioniProperty);
        set => SetValue(OpzioniProperty, value);
    }

    public bool IsOpzioniVisible
    {
        get => (bool)GetValue(IsOpzioniVisibleProperty);
        set => SetValue(IsOpzioniVisibleProperty, value);
    }

    public DataTemplate ViewCellTemplate
    {
        get => (DataTemplate)GetValue(ViewCellTemplateProperty);
        set => SetValue(ViewCellTemplateProperty, value);
    }

    public IViewFor RiepilogoView
    {
        get => (IViewFor)GetValue(RiepilogoViewProperty);
        set => SetValue(RiepilogoViewProperty, value);
    }

    #endregion Bindable properties

    private void InitBindings(CompositeDisposable disposables)
    {
        KeyboardListener.KeysPressed
            .Where(k => k.Key == KeyCode.F10)
            .Select(_ => Unit.Default)
            .InvokeCommand(ApriOpzioniCommand)
            .DisposeWith(disposables);

        KeyboardListener.KeysPressed
            .Where(k => k.Key == KeyCode.Escape)
            .Select(_ => Unit.Default)
            .InvokeCommand(EscapeCommand)
            .DisposeWith(disposables);

        this.OneWayBind(ViewModel, vm => vm.Title, v => v.Title)
            .DisposeWith(disposables);

        //esegue il comando alla prima attivazione o sempre a seconda della proprietà nel ViewModel
        if (ViewModel.OnActivateCommand != null && (!_initialized || !ViewModel.ActivateOnce))
        {
            Observable.Return(Unit.Default)
                .InvokeCommand(this, v => v.ViewModel.OnActivateCommand)
                .DisposeWith(disposables);
        }
    }

    private void InitLayout()
    {
        NavigationPage.SetHasNavigationBar(this, false);
        ControlTemplate = (ControlTemplate)Application.Current.Resources["MainPageTemplate"];
    }

    protected abstract void WhenActivated(CompositeDisposable disposables);

    protected abstract void WhenViewFocused();

    protected override async void OnAppearing()
    {
        try
        {
            _pagesCount.OnNext(Application.Current.MainPage is MasterDetailPage ? Navigation.NavigationStack.Count : 0);
            _pagesCount.OnCompleted();

            _isOpzioniDisponibili.OnNext(Opzioni != null);
            _isOpzioniDisponibili.OnCompleted();

            if (IsOpzioniVisible)
            {
                NascondiOpzioni();
            }
            if (Opzioni is IBindable view)
            {
                view.InitBindings();
            }

            if (RiepilogoView is IBindable viewRiepilogo)
            {
                viewRiepilogo.InitBindings();
            }

            var scroller = (ScrollView)GetTemplateChild("scrollViewContent");

            await scroller.ScrollToAsync(0, scroller.Height, false).ConfigureAwait(true);
        }
        catch (Exception ex)
        {
            await DisplayExceptionAsync(ex).ConfigureAwait(true);
        }
    }

    protected override void OnDisappearing()
    {
        if (Opzioni is IBindable view)
        {
            view.DisposeBindings();
        }

        if (RiepilogoView is IBindable viewRiepilogo)
        {
            viewRiepilogo.DisposeBindings();
        }

        base.OnDisappearing();
    }

    private async Task OnEscapePressedAsync()
    {
        // Se il menù è aperto
        if (IsOpzioniVisible)
        {
            NascondiOpzioni();
        }
        // Se non sono la pagina root dello stack di navigazione
        else if (Navigation != null && Navigation.NavigationStack.Count > 1)
        {
            await ViewModel.NavigationService.NavigateBackAsync().ConfigureAwait(true);
        }
    }

    private void OnMenuPressed() =>
        ((MasterDetailPage)Application.Current.MainPage).IsPresented = true;

    private void MostraNascondiOpzioni()
    {
        if (Opzioni != null)
        {
            if (IsOpzioniVisible)
            {
                NascondiOpzioni();
            }
            else
            {
                MostraOpzioni();
            }
        }
    }

    public void MostraOpzioni() => IsOpzioniVisible = true;

    public void NascondiOpzioni()
    {
        IsOpzioniVisible = false;

        WhenViewFocused();
    }
}

这是一个没有条目的页面,它受到问题的影响:

<?xml version="1.0" encoding="utf-8" ?>
<views:ContentPageBase xmlns="http://xamarin.com/schemas/2014/forms"
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       xmlns:vm="clr-namespace:MadLab.Spectrum.ViewModels.Inventario;assembly=MadLab.Spectrum.ViewModels"
                       xmlns:views="clr-namespace:MadLab.Spectrum.Views"
                       xmlns:vCommon="clr-namespace:MadLab.Spectrum.Views.Common"
                       xmlns:vDatiLotto="clr-namespace:MadLab.Spectrum.Views.Common.DatiAggiuntiviLotto"
                       x:Class="MadLab.Spectrum.Views.Inventario.RiepilogoConfermaPage"
                       x:TypeArguments="vm:RiepilogoConfermaViewModel">
    <ContentPage.Content>
        <Label x:Name="lblAvvisoQuantita"
                        VerticalOptions="Center"
                        HorizontalOptions="Center"
                        FontAttributes="Bold"
                        FontSize="Medium"
                        HorizontalTextAlignment="Center" />
    </ContentPage.Content>
    <views:ContentPageBase.Footer>
        <StackLayout Orientation="Horizontal"
                     VerticalOptions="FillAndExpand"
                     HorizontalOptions="CenterAndExpand"
                     Margin="10">
            <Image Source="{helper:ImageResource EnterKeyGreen32}">
                <Image.GestureRecognizers>
                    <TapGestureRecognizer x:Name="gestRecognizer" />
                </Image.GestureRecognizers>
            </Image>

            <Label Text="{Binding Text, Source={x:Reference view}}"
                   FontSize="Large"
                   FontAttributes="Bold"
                   TextColor="{StaticResource GreenColor}"
                   HorizontalOptions="CenterAndExpand"
                   VerticalOptions="CenterAndExpand"
                   VerticalTextAlignment="Center"
                   Padding="0,0,0,5" />
        </StackLayout>
    </views:ContentPageBase.Footer>
</views:ContentPageBase>

我已经尝试了IsTabStop = false,TabIndex和我在这里找到的所有提示,但都不起作用,它保持焦点,并按Enter键给出相同的结果.当我按Enter键时,什么都不应该做(似乎焦点在Android上是强制性的).

2w3rbyxf

2w3rbyxf1#

与此问题相关的github问题在github上。
执行提议的建议解决了问题。

相关问题