Выбираемые NSTextField и NSColorPanel - как прервать их нежелательное взаимодействие?
В на первый взгляд тривиальной установке я сталкиваюсь с нежелательным взаимодействием между выбираемым NSTextField и NSColorPanel, от которого я не могу избавиться, и это сводит меня с ума.
Вот настройка: в одном окне у меня есть выбираемая многострочная метка (де-факто NSTextField) и NSColorWell.
Color Well позволяет пользователю раскрашивать геометрические объекты в графическом интерфейсе; это не имеет никакого отношения к тексту вообще. Конечно, нажатие на цвет хорошо активирует его, то есть вызывает общую NSColorPanel и хорошо связывает цвет с ним.
Текстовое поле полностью не зависит от цветных объектов в графическом интерфейсе и предоставляет данные пользователю. Это только для чтения, то есть не редактируется. Поскольку данные организованы в столбцы, я использую вкладки для форматирования текста и setAttributedStringValue:
метод NSTextField для отображения данных.
На первый взгляд, все работает так, как и следовало ожидать в такой тривиальной установке.
Но тут возникает проблема: я хочу, чтобы пользователь мог копировать данные в текстовом поле, чтобы обрабатывать их в другом месте. Следовательно, NSTextField должен быть выбираемым. И установив его как selectable
вот где начинаются проблемы:
Когда пользователь нажимает на выбираемое текстовое поле, чтобы выделить текст, редактор поля окна вступает во владение, и, как следствие, все настройки вкладок приписанного текста теряются, и текст смешивается. Обычный способ предотвратить это - установить allowsEditingTextAttributes
свойство NSTextField для YES
, Если я это сделаю, форматирование вкладки сохраняется, когда пользователь выбирает текст. Но теперь NSColorPanel (если он виден) непреднамеренно также переключается на цвет текста (всегда черный), и если цветовая ячейка активна (подключена к NSColorPanel), она будет оставаться активной, тем самым изменяя цвет всех геометрических объектов графического интерфейса на черный., Ой!
Я не нашел способа установить selectable
а также allowsEditingTextAttributes
свойства NSTextField для YES
но все же не позволяют ему общаться с NSColorPanel.
Очевидным альтернативным способом было бы сохранить форматирование вкладок для выделенного текста даже при allowsEditingTextAttributes
установлен в NO
(который будет отключать цветную панель от текстового поля, по желанию). Но у меня тоже не было успеха с этим подходом, хотя я не очень понимаю, почему:
Моя идея заключалась в том, чтобы установить необходимые вкладки в качестве defaultParagraphStyle
редактора поля текстового поля. Итак, я настроил настраиваемый редактор полей:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSArray *myTabs = @[
[[NSTextTab alloc] initWithType:NSRightTabStopType location:100],
[[NSTextTab alloc] initWithType:NSRightTabStopType location:200],
[[NSTextTab alloc] initWithType:NSRightTabStopType location:300]
];
NSMutableParagraphStyle *myParagraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
[myParagraphStyle setTabStops:myTabs];
myFieldEditor = [NSTextView new]; // myFieldEditor is an instance variable
[myFieldEditor setDefaultParagraphStyle:myParagraphStyle];
[window setDelegate:self];
[window fieldEditor:YES forObject:myTextField];
}
И активировать его для текстового поля в windowWillReturnFieldEditor:toObject:
метод делегата:
- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client
{
if (client == myTextField) return myFieldEditor;
return nil;
}
Я даже убедился, что мой редактор пользовательских полей действительно используется, создав подкласс NSTextFieldCell моего текстового поля и зарегистрировав редактор распространяемого поля:
@implementation myTextFieldCell
- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
{
NSTextView *newTextObj = (NSTextView*)[super setUpFieldEditorAttributes:textObj];
NSLog(@"STYLE: %@", [newTextObj defaultParagraphStyle]);
return newTextObj;
}
@end
Теперь, когда я выделяю текст в текстовом поле, я получаю следующий вывод журнала:
2017-11-02 11:51:07.432 Demo[94807:303] STYLE: Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
100R,
200R,
300R
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningFactor 0.05, HeaderLevel 0
Что именно то, что ожидается.
Но тем не менее форматирование вкладок исчезает в текстовом поле, как только я выделяю текст. Я понятия не имею, почему это не работает.
Так что я застрял в любом случае. Если я установлю allowsEditingTextAttributes
свойство NSTextField для YES
форматирование вкладок сохраняется при выделении текста, но мои цветные объекты в графическом интерфейсе непреднамеренно изменяются на черный. Если я установлю allowsEditingTextAttributes
собственность на NO
цветная панель ведет себя как следует, но форматирование вкладки теряется, как только я выделяю текст.
Это очень прискорбный случай, когда Какао пытается быть слишком умным и, таким образом, делает совершенно тривиальную установку огромной проблемой.
Любые идеи кто-нибудь?
1 ответ
Итак, я закончил с предложением, которое @Willeke (спасибо!!) сделал в своем комментарии к моему вопросу: использовать NSTextView вместо NSTextField для реализации моей многострочной метки.
Сначала я подведу итог, почему кажется невозможным сделать то, что я хотел, с помощью NSTextField, а затем решение с помощью NSTextView.
Почему NSTextField не работает
Как описано, моей идеей решения было настроить редактор полей для NSTextField, установив нужные табуляции, чтобы мне не нужно было устанавливать NSTextField. allowsEditingTextAttributes
собственность на YES
(что непреднамеренно связывает текстовое поле с цветной панелью). Я надеялся, что это сохранит позиции табуляции стиля абзаца моей приписанной строки, когда я выделю текст в текстовом поле и тем самым активирую редактор поля.
Обширное тестирование показало, что это не работает по нескольким причинам:
- Как отметил @Willeke, настройка
usesFontPanel
свойство NSTextView дляNO
также нарушает связь текстового представления с цветной панелью (по желанию). Однако это не работает для NSTextView, который является редактором полей NSTextField, потому что в этом контексте этот параметр всегда перезаписываетсяallowsEditingTextAttributes
свойство NSTextField: еслиallowsEditingTextAttributes
являетсяYES
панели шрифта и цвета связаны независимо от значенияusesFontPanel
, если этоNO
панели шрифта и цвета отделены независимо от значенияusesFontPanel
, - Идея использовать табуляцию настраиваемого редактора полей вместо использования табуляций стиля абзаца моей приписанной строки (что потребовало бы
allowsEditingTextAttributes
бытьYES
) все равно не будет работать, потому что настройки табуляции в редакторе полей, очевидно, всегда полностью игнорируются NSTextField, независимо от значенияallowsEditingTextAttributes
имущество. NSTextField всегда использует равномерно расположенные позиции табуляции по умолчанию.
Судя по интенсивному поиску в Google, другой вариант - настройка allowsEditingTextAttributes
в YES
но каким-то образом модифицировать NSColorPanel так, чтобы он не подключался к NSTextField, тем не менее - невозможно реализовать, не возвращаясь к закрытым методам NSColorPanel.
Как реализовать решение с помощью NSTextView
Хотя кажется излишним создавать экземпляр полного NSTextView, встроенного в представление клипов и представление прокрутки, просто чтобы получить функциональность текстового поля, в конце концов, это самое простое (или даже единственно возможное) решение.
Чтобы исчезло представление прокрутки, вам придется в основном снять все флажки в инспекторе атрибутов NSScrollView в IB, в частности показать вертикальную прокрутку. Установите Draw Background и тип границы на вид, который вы хотите; если вы хотите имитировать многострочную метку (как я), снимите флажок " Рисовать фон" и выберите невидимый тип границы. В инспекторе атрибутов встроенного NSTextView также снимите все атрибуты, кроме Selectable, в частности, использует панель шрифтов.
Убедитесь, что размер NSTextView достаточно большой, чтобы вместить всю строку содержимого, чтобы избежать непреднамеренных эффектов прокрутки и исправить положение текста. Если ваша строка содержимого заканчивается строкой, вам понадобится достаточно места для пустой строки под ней. Если вы не сняли флажок " Рисовать фон", и это выглядит не так, как вы хотите, не рисуйте фон NSScrollView или NSTextView, выберите невидимую границу для NSScrollView, а затем поместите NSBox нужного размера и внешнего вида под ними.,
Теперь вы можете установить атрибутивную строку содержимого с помощью:
[[myTextView textStorage] setAttributedString:myAttributedString];
Обратите внимание, что это работает, хотя editable
свойство NSTextView установлено в NO
так как вы изменяете NSTextStorage, а не сам NSTextView.
Но, к сожалению, мы еще не закончили.
Когда вы используете NSTextField для отображения данных только для чтения, как вы это обычно делаете в Label, чаще всего вы не захотите, чтобы текстовое поле было частью вашего цикла представления ключа (который проходит по вашим элементам управления нажатием клавиши Tab), Чтобы достичь этого, вы можете просто установить refusesFirstResponder
свойство NSTextField для YES
, Но NSTextView не наследуется от NSControl и поэтому не имеет этого свойства. Итак, в конце концов, нам нужно будет создать подкласс NSTextView, чтобы добавить refusesFirstResponder
имущество.
Реализация переписывает becomeFirstResponder
и идет так:
- (BOOL)becomeFirstResponder
{
if (!_refusesFirstResponder) return [super becomeFirstResponder];
NSEvent *event = [NSApp currentEvent];
if ([event type] == NSLeftMouseDown || [event type] == NSRightMouseDown) return [super becomeFirstResponder];
NSView *validKeyView = ([event modifierFlags] & NSShiftKeyMask)? [[[self previousValidKeyView] previousValidKeyView] previousValidKeyView] : [self nextValidKeyView];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{[[self window] makeFirstResponder:validKeyView];}];
return NO;
}
Если refusesFirstResponder
является NO
мы просто возвращаем реализацию super.
Если это YES
, мы проверяем, станет ли NSTextView первым респондентом из-за щелчка мышью внутри него. Если это так, мы также просто возвращаем реализацию super, что позволяет выделять текст мышью.
Кроме этого, мы перенаправляем первый ответчик на следующий или предыдущий вид клавиш (в зависимости от того, была ли нажата клавиша Shift) и возвращаем NO
, отказываясь стать первым респондентом. Определить предыдущее представление ключа немного сложно, потому что ближайший предыдущий просмотр ключа - это встраивание NSClipView, которое нам не нужно или не нужно, но нужно использовать, потому что Interface Builder не предлагает "чистый" NSTextView. Затем идет встраивание NSScrollView, и только тогда предыдущий ключевой вид, который мы действительно хотим.
Кроме того, поскольку мы находимся в процессе, который определяет первого респондента, мы не можем просто вызвать makeFirstResponder:
, но придется отложить это до следующей итерации цикла выполнения.
Теперь, когда мы реализовали refusesFirstResponder
нам все равно придется имитировать поведение NSTextField, чтобы отклонить любой выделенный текст, когда он теряет состояние первого респондента. Мы можем сделать это в методе делегата NSText. Предполагая, что нам не нужны другие функции делегата, мы можем сделать наш подкласс своим собственным делегатом и добавить этот метод делегата:
- (void)textDidEndEditing:(NSNotification*)notification
{
[[notification object] setSelectedRange:NSMakeRange(UINT64_MAX, 0)];
}
Наконец, если мы должны подкласс, в любом случае, мы могли бы добавить setAttributedString:
удобный метод.
Итак, что мы получим в итоге это:
Заголовок:
#import <Cocoa/Cocoa.h>
IB_DESIGNABLE
@interface MyTextFieldLikeTextView : NSTextView <NSTextViewDelegate>
@property IBInspectable BOOL refusesFirstResponder;
- (void)setAttributedString:(NSAttributedString*)attributedString;
@end
Реализация:
#import "MyTextFieldLikeTextView.h"
@implementation MyTextFieldLikeTextView
- (void)awakeFromNib
{
[self setDelegate:self];
}
- (BOOL)becomeFirstResponder
{
if (!_refusesFirstResponder) return [super becomeFirstResponder];
NSEvent *event = [NSApp currentEvent];
if ([event type] == NSLeftMouseDown || [event type] == NSRightMouseDown) return [super becomeFirstResponder];
NSView *validKeyView = ([event modifierFlags] & NSShiftKeyMask)? [[[self previousValidKeyView] previousValidKeyView] previousValidKeyView] : [self nextValidKeyView];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{[[self window] makeFirstResponder:validKeyView];}];
return NO;
}
- (void)textDidEndEditing:(NSNotification*)notification
{
[[notification object] setSelectedRange:NSMakeRange(UINT64_MAX, 0)];
}
- (void)setAttributedString:(NSAttributedString*)attributedString
{
[[self textStorage] setAttributedString:attributedString];
}
@end
Все еще много усилий только потому, что Какао пытается перехитрить нас и настаивает на подключении NSColorPanel к каждому NSTextField, который допускает атрибутивный текст…