Как перейти на поле редактирования текста Какао, которое прокручивается вне поля зрения
У меня есть окно с одним полем для редактирования текста рядом с прокруткой. Представление документа в представлении с прокруткой содержит несколько вложенных представлений, и некоторые из этих вложенных представлений содержат поля редактирования текста, созданные программным способом как NSTextViews.
Когда приложение запускает окно вверх, поле редактирования текста верхнего уровня показывается с фокусом, поэтому оно (я думаю) является первым респондентом. Затем пользователь нажимает клавишу TAB, но первое поле редактирования текста (среди многих) находится в подвью представления документа, которое было прокручено вне поля зрения пользователя.
Поведение приложения по умолчанию заключается в перемещении фокуса на "следующее" текстовое поле. За исключением того, что пользователь понятия не имеет, где это, потому что это вне поля зрения.
Таким образом, приложение может вести себя двумя способами. Либо приложение должно выяснить, что следующий респондент явно находится за границами, и не дать клавише TAB изменить текущий фокус. Или приложение должно определить, какое поле для редактирования текста получило новый фокус, и автоматически прокрутить так, чтобы это текстовое поле стало видимым для пользователя. Можно утверждать, что любой сценарий является логичным, но я думаю, что последний более полезен.
Как определить, что фокус был автоматически изменен на элемент управления редактирования текста ванили, который считает, что он виден, но не виден из-за обрезания родительским представлением прокрутки?
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);
}
Похоже, у вас есть пара вопросов. Они легко решаются, но ничего из этого не происходит автоматически.
Во-первых, порядок просмотра, который вы циклически повторяете при нажатии клавиши Tab, является "циклом просмотра ключа". Вы можете прочитать об этом. Это своего рода, автоматически, но вы можете выразить явный порядок, установив nextKeyView
а также previousKeyView
свойства текстовых полей (кнопки, другие элементы управления, ...).
Если вы хотите, чтобы что-либо в представлении документа вида прокрутки стало видимым, необходимо переориентировать представление клипа. Есть масса способов сделать это (большинство из них довольно трудно понять), но то, что вы хотите, настолько распространено, что в NSView
это делает именно это: scrollRectToVisible:
Поэтому, когда ваше текстовое поле становится активным, все, что вам нужно сделать, это [textField scrollRectToVisible:textField.bounds]
,
Единственное место, где это можно сделать, - это когда текстовое поле начинает редактироваться, что можно сделать, прикрепив делегата к текстовому полю (полям) и поймав его. textDidBeginEditing:
или соблюдать NSControlTextDidBeginEditingNotification
уведомить и определить, является ли это одним из ваших текстовых полей.