【问题标题】:How to tab to a Cocoa text editing field that is scrolled out of view如何选择滚动到视图之外的 Cocoa 文本编辑字段
【发布时间】:2018-02-08 21:32:59
【问题描述】:

我在滚动视图旁边有一个带有单个文本编辑字段的窗口。滚动视图的文档视图包含多个子视图,其中一些子视图包含以编程方式创建为 NSTextViews 的文本编辑字段。

当应用程序启动窗口时,顶级文本编辑字段显示为焦点,因此(我认为)它是第一响应者。然后,用户点击 TAB 键,但第一个文本编辑字段(在许多字段中)位于文档视图的子视图中,该子视图已滚动到用户视线之外。

应用程序的默认行为是将焦点移至“下一个”文本字段。除了用户不知道这是在哪里,因为它不在视线范围内。

因此,应用应该有两种可能的行为方式。应用程序应该找出下一个响应者明显超出范围,并防止 TAB 键更改当前焦点。或者应用程序应该确定哪个文本编辑字段获得了新的焦点,并自动滚动,以便该文本字段现在对用户可见。可以说这两种情况都是合乎逻辑的,但我认为后者更有用。

如何确定焦点已自动更改为认为可见但不可见的普通文本编辑控件,因为被父滚动视图剪切?

【问题讨论】:

标签: cocoa nstextfield nsscrollview nsresponder


【解决方案1】:

听起来你有几个问题。它们很容易解决,但这些都不是自动的。

首先,按 Tab 键时循环浏览的视图顺序是“键视图循环”。你可以阅读一下。它有点,有点,自动,但您可以通过设置文本字段(按钮、其他控件等)的 nextKeyViewpreviousKeyView 属性来表达明确的顺序。

如果您希望滚动视图的文档视图中的任何内容可见,则需要重新定向剪辑视图。有很多方法可以做到这一点(其中大多数都很难理解),但你想要的很常见,NSView 中有一个方便的方法可以做到这一点:scrollRectToVisible:

因此,当您的文本字段变为活动状态时,您所要做的就是[textField scrollRectToVisible:textField.bounds]

可以在文本字段开始编辑时执行此操作,这可以通过将委托附加到文本字段并捕获textDidBeginEditing: 或观察NSControlTextDidBeginEditingNotification 通知并确定它是否是其中之一来完成您的文本字段。

【讨论】:

  • NSTextField 被子类化并以编程方式即时构建,而不知道在应用程序启动之前可能有多少。子类定义还将它们声明为:NSTextField s。但是当我实现 textDidBeginEditing: 作为一个方法,并在初始化时将文本字段对象安装为它自己的委托时,该方法永远不会被调用。
  • 我的错:textDidBeginEditing: 不是委托方法,而是NSTextFIeld 的方法。只需覆盖它,看看它是否被调用。
  • 好的,我摆脱了委托的东西,并将textDidBeginEditing:覆盖方法添加到我的子类中,以及相关的方法textShouldBeginEditing:(提供YES)。当用户点击它后文本字段获得焦点时,都不会调用。原来这不是一个编辑事件。但是,当用户在文本字段中键入第一个字符时,两者都会被调用。所以这个策略对于我原来的问题来说似乎不是正确的事情,它需要知道是否允许跳到下一个编辑字段,如果允许,它是哪个。
  • 我们越来越近了!然后我认为解决方案是覆盖becomeFirstResponder 并抓住文本字段成为焦点的那一刻。如果super.becomeFirstResponder 返回true,则将字段滚动到视图中。
【解决方案2】:

因为我是 NSTextField 的子类,所以我相信正确的(或至少是有效的)答案是,如建议的那样,用以下内容覆盖 becomeFirstResponder:,其中 myDocView 是滚动内容父滚动视图在视图层次结构中的较高位置:

- (BOOL)becomeFirstResponder
{
    BOOL done = [super becomeFirstResponder];
    if (done) {
        // Ensure new focus ring is shown also (shouldn't be hardwired, but for now ...)
        NSSize margin = NSMakeSize(20.0, 20.0);
        // Get where text field lives in myDocView's coordinates
        NSRect r = [myDocView convertRect:[self bounds] fromView:self];
        // Try scrolling if partially visible first, and if not ...
        if (![myDocView adjustRectIntoView:r withMargin:margin]) {
            // Text edit field is not visible in parent scroll view
            // so tell document view where to scroll itself to
            margin = NSMakeSize(-30.0, -30.0);
            [myDocView specialScrollTo:self withOffset:margin];
            }
        }

    return done;
}

在以编程方式创建文本编辑对象时,必须设置属于文本编辑字段子类的属性字段myDocView,以便文本编辑字段在成为第一响应者时知道将滚动消息发送给谁.这是因为在我的特定情况下,文本编辑子类对象实际上比滚动文档视图低几个视图级别。

部分是出于一般用户界面的原因,部分是因为我的滚动内容视图布局的特殊性,有必要在三种情况下做一些不同的事情。第一种情况是成为第一响应者的文本编辑字段已经完全可见。很简单,adjustRectIntoView 什么都不做,只返回YES,因为用户可以看到对焦环的变化。

在第二种情况下,文本编辑字段是部分可见的。在这种情况下,adjustRectIntoView:withMargin: 方法使其完全可见(如果可能,否则,它使原点区域可见)。但它只使用最小的滚动移动,水平或垂直(并且仅在必要时两者),将字段留在滚动视图的可见矩形的最近边缘旁边。这让用户最不“吃惊”。

最后,如果该字段完全不可见,那么在我的特殊情况下,我必须对与文本编辑视图相关的其他附近视图进行特殊分析,以便将其(或全部)显示给用户见。

adjustRectIntoView:withMargin:specialScrollTo:withOffset: 都是添加到正在滚动的(子类)myDocView 的方法。

我的后一个特殊例程过于特定于应用程序,但前者相当通用,看起来像这样(并且可以轻松修改以完成后者):

- (BOOL)adjustRectIntoView:(NSRect)r withMargin:(NSSize)margin
 {
    CGRectInset(r, -margin.width, -margin.height);
    NSRect vis = [myScroller documentVisibleRect];

    if (CGRectContainsRect(vis, r)) {
        // The enhanced rectangle `r` is already fully visible,
        // so we're done (no change)
        return(YES);
        }

    if (!CGRectIntersectsRect(vis, r)) {
        // The enhanced rectangle `r` is fully invisible, so caller
        // must apply whatever other custom strategy it needs to
        // scroll `r` into view; or don't return and fall through.
        return(NO);
        }

    // Rectangle `r` is partly visible in scroll view.  So nudge the
    // scrolling view enough to bring `r` into view near where it
    // already is, with a minimum of motion.  If `r` contains `vis`,
    // which can happen if `r` is part of a highly magnified view,
    // this gives preference to `r`'s origin becoming visible.

    NSPoint ul = r.origin;
    NSPoint ur = NSMakePoint(r.origin.x+r.size.width, r.origin.y);
    NSPoint ll = NSMakePoint(r.origin.x, r.origin.y+r.size.height);

    NSSize amt;
    if (ul.x < vis.origin.x)
        amt.width = (ul.x - vis.origin.x);
     else if (ur.x > vis.origin.x+vis.size.width)
        amt.width = (ur.x - (vis.origin.x+vis.size.width));
     else
        amt.width = 0.0;

    if (ul.y < vis.origin.y)
        amt.height = (ul.y - vis.origin.y);
     else if (ll.y > vis.origin.y+vis.size.height)
        amt.height = (ll.y - (vis.origin.y+vis.size.height));
     else
        amt.height = 0.0;

    vis.origin.x += amt.width;
    vis.origin.y += amt.height;
    [[myScroller documentView] scrollPoint:vis.origin];

    return(YES);
 }

【讨论】:

  • 可以加adjustRectIntoView:withMargin:specialScrollTo:withOffset:吗?请问my answer为什么不起作用?
  • 我没看到,也不知道enclosingScrollView 方法。另外,因为我的子类已经知道它的滚动条是什么对象,所以不需要在对象层次结构上进行一般计算、检查类型等。无论如何,scrollRectToVisible 方法在某些更复杂的情况下并不是最好的情况,例如需要在想要使其可见的文本编辑字段旁边显示其他上下文。
  • 这就是为什么方法是scrollRectToVisible 而不是scrollViewToVisible。只需将其他视图添加到矩形即可。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-17
  • 1970-01-01
  • 2013-05-14
  • 1970-01-01
  • 2012-06-22
  • 2012-10-29
  • 1970-01-01
相关资源
最近更新 更多