是否可以在Xamarin Forms中的ScrollView中阻止第一个Entry获得焦点

icnyk63a  于 2023-04-18  发布在  其他
关注(0)|答案(5)|浏览(162)

在我的申请中,我有以下情况:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:conv="clr-namespace:NumericTextBoxTest.Converters;assembly=NumericTextBoxTest"
             xmlns:numericTextBox="clr-namespace:Syncfusion.SfNumericTextBox.XForms;assembly=Syncfusion.SfNumericTextBox.XForms"
             x:Class="NumericTextBoxTest.MainPage">
  <ScrollView>
    <StackLayout>    
      <Entry/>
      <Entry/>
      <Entry/>
      <Entry/>
      <Entry/>
      <Entry/>
      <Entry/>
      <Entry/>
      <Entry/>
      <Entry/>
    </StackLayout>

  </ScrollView>
</ContentPage>

现在,如果我点击底部的空白处(条目下方),即ScrollView,则ScrollView中的第一个Entry将获得焦点。
非常烦人,如果我正在改变第一个Entry上的值,并试图取消对Entry的聚焦来设置值。
有可能阻止这种行为吗?

gt0wga4j

gt0wga4j1#

现在,如果我点击底部的空白区域(条目下方),即ScrollView,ScrollView中的第一个条目将获得焦点。
在UWP中,当点击StackLayout时,系统将搜索StackLayout中的每个元素,直到第一个可以关注的元素。作为解决此问题的解决方案,您可以在StackLayout的顶部放置一个不可见的按钮。

<ScrollView>
        <StackLayout>
            <Button HeightRequest="0" WidthRequest="1" />
            <Entry  />
            ....
            <Entry />
        </StackLayout>
    </ScrollView>

当点击StackLayout时,按钮将被聚焦。Entry将不会被聚焦

hec6srdp

hec6srdp2#

最后,我实际上最终覆盖了默认的ScrollViewRenderer,正如这条评论中提到的:
https://github.com/microsoft/microsoft-ui-xaml/issues/597#issuecomment-513804526
我在UWP上的ScrollViewRenderer看起来像这样:

using Xamarin.Forms;
using Xamarin.Forms.Platform.UWP;
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using ScrollBarVisibility = Xamarin.Forms.ScrollBarVisibility;
using UwpScrollBarVisibility = Windows.UI.Xaml.Controls.ScrollBarVisibility;
using Size = Xamarin.Forms.Size;
using Point = Xamarin.Forms.Point;
using Thickness = Xamarin.Forms.Thickness;
using FieldStrikeMove.Forms.CustomControls;

//https://github.com/microsoft/microsoft-ui-xaml/issues/597

//https://github.com/xamarin/Xamarin.Forms/blob/f17fac7b9e2225b1bfe9e94909d2b954106f8f1f/Xamarin.Forms.Platform.UAP/ScrollViewRenderer.cs
//07/01/20

[assembly: ExportRenderer(typeof(ExtendedScrollView), typeof(MyApp.UWP.CustomRenderers.Controls.ScrollViewRenderer))]
[assembly: ExportRenderer(typeof(ScrollView), typeof(MyApp.UWP.CustomRenderers.Controls.ScrollViewRenderer))]
namespace MApp.UWP.CustomRenderers.Controls
{
    public class ScrollViewRenderer : ViewRenderer<ScrollView, ScrollViewer>//, IDontGetFocus
    {
        VisualElement _currentView;
        bool _checkedForRtlScroll = false;

        public ScrollViewRenderer()
        {
            AutoPackage = false;
        }

        public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
        {
            SizeRequest result = base.GetDesiredSize(widthConstraint, heightConstraint);
            result.Minimum = new Size(40, 40);
            return result;
        }

        protected override Windows.Foundation.Size ArrangeOverride(Windows.Foundation.Size finalSize)
        {
            if (Element == null)
                return finalSize;

            Element.IsInNativeLayout = true;

            Control?.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));

            Element.IsInNativeLayout = false;

            return finalSize;
        }

        protected override void Dispose(bool disposing)
        {
            CleanUp(Element, Control);
            base.Dispose(disposing);
        }

        protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Size availableSize)
        {
            if (Element == null)
                return new Windows.Foundation.Size(0, 0);

            double width = Math.Max(0, Element.Width);
            double height = Math.Max(0, Element.Height);
            var result = new Windows.Foundation.Size(width, height);

            Control?.Measure(result);

            return result;
        }

        void CleanUp(ScrollView scrollView, ScrollViewer scrollViewer)
        {
            if (Element != null)
                Element.PropertyChanged -= OnContentElementPropertyChanged;

            if (ContainerElement != null)
                ContainerElement.LayoutUpdated -= SetInitialRtlPosition;

            if (scrollView != null)
            {
                scrollView.ScrollToRequested -= OnScrollToRequested;
            }

            if (scrollViewer != null)
            {
                scrollViewer.ViewChanged -= OnViewChanged;
                if (scrollViewer.Content is FrameworkElement element)
                {
                    element.LayoutUpdated -= SetInitialRtlPosition;
                }
            }

            if (_currentView != null)
                _currentView.Cleanup();
        }

        protected override void OnElementChanged(ElementChangedEventArgs<ScrollView> e)
        {
            base.OnElementChanged(e);
            CleanUp(e.OldElement, Control);

            if (e.NewElement != null)
            {
                if (Control == null)
                {
                    SetNativeControl(new ScrollViewer
                    {
                        HorizontalScrollBarVisibility = ScrollBarVisibilityToUwp(e.NewElement.HorizontalScrollBarVisibility),
                        VerticalScrollBarVisibility = ScrollBarVisibilityToUwp(e.NewElement.VerticalScrollBarVisibility),
                    });

                    Control.ViewChanged += OnViewChanged;
                }

                Element.ScrollToRequested += OnScrollToRequested;

                UpdateOrientation();

                UpdateContent();
            }
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName == "Content")
                UpdateContent();
            else if (e.PropertyName == Layout.PaddingProperty.PropertyName)
                UpdateContentMargins();
            else if (e.PropertyName == ScrollView.OrientationProperty.PropertyName)
                UpdateOrientation();
            else if (e.PropertyName == ScrollView.VerticalScrollBarVisibilityProperty.PropertyName)
                UpdateVerticalScrollBarVisibility();
            else if (e.PropertyName == ScrollView.HorizontalScrollBarVisibilityProperty.PropertyName)
                UpdateHorizontalScrollBarVisibility();
        }

        protected void OnContentElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == View.MarginProperty.PropertyName)
                UpdateContentMargins();
        }

        void UpdateContent()
        {
            if (_currentView != null)
                _currentView.Cleanup();

            if (Control?.Content is FrameworkElement oldElement)
            {
                oldElement.LayoutUpdated -= SetInitialRtlPosition;

                if (oldElement is IVisualElementRenderer oldRenderer
                    && oldRenderer.Element is View oldContentView)
                    oldContentView.PropertyChanged -= OnContentElementPropertyChanged;
            }

            _currentView = Element.Content;

            IVisualElementRenderer renderer = null;
            if (_currentView != null)
                renderer = _currentView.GetOrCreateRenderer();

            Control.Content = renderer != null ? renderer.ContainerElement : null;

            UpdateContentMargins();
            if (renderer?.Element != null)
                renderer.Element.PropertyChanged += OnContentElementPropertyChanged;

            if (renderer?.ContainerElement != null)
                renderer.ContainerElement.LayoutUpdated += SetInitialRtlPosition;
        }

        async void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e)
        {
            ClearRtlScrollCheck();

            // Adding items into the view while scrolling to the end can cause it to fail, as
            // the items have not actually been laid out and return incorrect scroll position
            // values. The ScrollViewRenderer for Android does something similar by waiting up
            // to 10ms for layout to occur.
            int cycle = 0;
            while (Element != null && !Element.IsInNativeLayout)
            {
                await Task.Delay(TimeSpan.FromMilliseconds(1));
                cycle++;

                if (cycle >= 10)
                    break;
            }

            if (Element == null)
                return;

            double x = e.ScrollX, y = e.ScrollY;

            ScrollToMode mode = e.Mode;
            if (mode == ScrollToMode.Element)
            {
                Point pos = Element.GetScrollPositionForElement((VisualElement)e.Element, e.Position);
                x = pos.X;
                y = pos.Y;
                mode = ScrollToMode.Position;
            }

            if (mode == ScrollToMode.Position)
            {
                Control.ChangeView(x, y, null, !e.ShouldAnimate);
            }
            Element.SendScrollFinished();
        }

        void SetInitialRtlPosition(object sender, object e)
        {
            if (Control == null) return;

            if (Control.ActualWidth <= 0 || _checkedForRtlScroll || Control.Content == null)
                return;

            if (Element is IVisualElementController controller && controller.EffectiveFlowDirection.IsLeftToRight())
            {
                ClearRtlScrollCheck();
                return;
            }

            var element = (Control.Content as FrameworkElement);
            if (element.ActualWidth == Control.ActualWidth)
                return;

            ClearRtlScrollCheck();
            Control.ChangeView(element.ActualWidth, 0, null, true);
        }

        void ClearRtlScrollCheck()
        {
            _checkedForRtlScroll = true;
            if (Control.Content is FrameworkElement element)
                element.LayoutUpdated -= SetInitialRtlPosition;
        }

        void OnViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
        {
            ClearRtlScrollCheck();
            Element.SetScrolledPosition(Control.HorizontalOffset, Control.VerticalOffset);

            if (!e.IsIntermediate)
                Element.SendScrollFinished();
        }

        Windows.UI.Xaml.Thickness AddMargin(Thickness original, double left, double top, double right, double bottom)
        {
            return new Windows.UI.Xaml.Thickness(original.Left + left, original.Top + top, original.Right + right, original.Bottom + bottom);
        }

        // UAP ScrollView forces Content origin to be the same as the ScrollView origin.
        // This prevents Forms layout from emulating Padding and Margin by offsetting the origin. 
        // So we must actually set the UAP Margin property instead of emulating it with an origin offset. 
        // Not only that, but in UAP Padding and Margin are aliases with
        // the former living on the parent and the latter on the child. 
        // So that's why the UAP Margin is set to the sum of the Forms Padding and Forms Margin.
        void UpdateContentMargins()
        {
            if (!(Control.Content is FrameworkElement element
                && element is IVisualElementRenderer renderer
                && renderer.Element is View contentView))
                return;

            var margin = contentView.Margin;
            var padding = Element.Padding;
            switch (Element.Orientation)
            {
                case ScrollOrientation.Horizontal:
                    // need to add left/right margins
                    element.Margin = AddMargin(margin, padding.Left, 0, padding.Right, 0);
                    break;
                case ScrollOrientation.Vertical:
                    // need to add top/bottom margins
                    element.Margin = AddMargin(margin, 0, padding.Top, 0, padding.Bottom);
                    break;
                case ScrollOrientation.Both:
                    // need to add all margins
                    element.Margin = AddMargin(margin, padding.Left, padding.Top, padding.Right, padding.Bottom);
                    break;
            }
        }

        void UpdateOrientation()
        {
            //Only update the horizontal scroll bar visibility if the user has not set a desired state.
            if (Element.HorizontalScrollBarVisibility != ScrollBarVisibility.Default)
                return;

            var orientation = Element.Orientation;
            if (orientation == ScrollOrientation.Horizontal || orientation == ScrollOrientation.Both)
            {
                Control.HorizontalScrollBarVisibility = UwpScrollBarVisibility.Auto;
            }
            else
            {
                Control.HorizontalScrollBarVisibility = UwpScrollBarVisibility.Disabled;
            }
        }

        UwpScrollBarVisibility ScrollBarVisibilityToUwp(ScrollBarVisibility visibility)
        {
            switch (visibility)
            {
                case ScrollBarVisibility.Always:
                    return UwpScrollBarVisibility.Visible;
                case ScrollBarVisibility.Default:
                    return UwpScrollBarVisibility.Auto;
                case ScrollBarVisibility.Never:
                    return UwpScrollBarVisibility.Hidden;
                default:
                    return UwpScrollBarVisibility.Auto;
            }
        }

        void UpdateVerticalScrollBarVisibility()
        {
            Control.VerticalScrollBarVisibility = ScrollBarVisibilityToUwp(Element.VerticalScrollBarVisibility);
        }

        void UpdateHorizontalScrollBarVisibility()
        {
            var horizontalVisibility = Element.HorizontalScrollBarVisibility;

            if (horizontalVisibility == ScrollBarVisibility.Default)
            {
                UpdateOrientation();
                return;
            }

            var orientation = Element.Orientation;
            if (orientation == ScrollOrientation.Horizontal || orientation == ScrollOrientation.Both)
                Control.HorizontalScrollBarVisibility = ScrollBarVisibilityToUwp(horizontalVisibility);
        }
    }

    public static class Extensions
    {
        internal static void Cleanup(this VisualElement self)
        {
            if (self == null)
                throw new ArgumentNullException("self");

            IVisualElementRenderer renderer = Platform.GetRenderer(self);

            foreach (Element element in self.Descendants())
            {
                var visual = element as VisualElement;
                if (visual == null)
                    continue;

                IVisualElementRenderer childRenderer = Platform.GetRenderer(visual);
                if (childRenderer != null)
                {
                    childRenderer.Dispose();
                    Platform.SetRenderer(visual, null);
                }
            }

            if (renderer != null)
            {
                renderer.Dispose();
                Platform.SetRenderer(self, null);
            }
        }
    }
}
j5fpnvbx

j5fpnvbx3#

如果我点击底部的空白处(条目下面)
你可以拉伸你的stacklayout,然后它将接收焦点而不是scrollview。
您还可以为stacklayout设置backgroundcolor,以确保stacklayout被拉伸

sauutmhj

sauutmhj4#

[更新]
我刚刚发现,我真的需要完全qulify我的自定义ScrollViewRenderer的命名空间为ExportRenderer属性的Target参数时,类具有相同的名称从Xamarin窗体。
现在它正按预期工作
[原文]
我试着实现了User1发布的CustomRenderer,但它似乎没有在UWP项目中使用。虽然我在页面上有一个ScrollView,但ScrollViewRenderer的构造函数从未被调用。
如果我从ScrollView派生一个CustomScrollView,并让渲染器应用于CustomScrollView,它会被使用,解决方案按预期工作,但似乎我无法覆盖所有ScrollViews的渲染器。
有没有提示为什么渲染器没有被用来替换XF的默认内置ScrollViewRenderer?(顺便说一句,我使用的是XF5.0.0.1874)

jdgnovmf

jdgnovmf5#

在MAUI应用程序中,添加假按钮的解决方案效果很好。我也不太喜欢它,但它是快速简单的解决方案,无需从本主题的另一条评论中找出ScrollViewRenderer。

相关问题