Подкласс NSButton как colorwell и предотвращает прикосновение NSColorPanel к первому респонденту
Я последовал несколько примеров для создания NSButton
подкласс работает как NSColorWell
(так как наш NSButton
подкласс уже дает нам поведение внешнего вида, в котором мы нуждаемся), однако я заметил, что после использования кнопки для вызова панели и изменения цвета она также меняет цвет выделенного текста в нашем документе. Если бы я вместо подкласса NSColorWell
с нашими настройками внешнего вида, не будет ли эта проблема?
Тем не менее, я все еще надеюсь на обходной путь, который позволяет избежать этого и все же позволяет нам использовать наш подкласс кнопок. Я видел в обсуждениях, в которых предлагается, чтобы сама кнопка стала первым респондентом, однако, когда кнопка находится в отдельной палитре, у меня возникают проблемы с тем, чтобы заставить это работать. Кроме того, я бы предпочел не изменять цепочку респондента и не использовать палитру в качестве ключевого окна. Насколько зла бы категория на NSColorPanel
быть переопределенным setColor:, заставляющим отправлять ожидаемое уведомление, но не трогать первого респондента?
(Обратите внимание, что вместо того, чтобы просто открывать цветную панель, я сейчас использую BFColorPickerPopover
DrummerB https://github.com/DrummerB/BFColorPickerPopover. Однако я не думаю, что это большая сложность. У меня было то же самое NSColorPanel
/ первая проблема респондента перед его интеграцией).
Было предложено опубликовать исходный код, так что вот соответствующие биты из моего подкласса NSButton (обратите внимание, использует всплывающее окно выбора, упомянутые выше, а не NSColorPanel
непосредственно):
.час:
@interface ...
@property (nonatomic, strong) NSColor *color;
@property (nonatomic, assign) BOOL active;
@property (nonatomic, strong) NSColor *buttonColor;
@property (nonatomic, weak) BFColorPickerPopover *popover;
- (void)activate:(BOOL)exclusive; // param ignored, always exclusive
- (void)activate;
- (void)deactivate;
- (void)takeColorFrom:(id)sender;
@end
.m:
@implementation ...
@dynamic color;
- (NSColor *)color
{
return self.buttonColor;
}
- (void)setColor:(NSColor *)newColor
{
self.buttonColor = newColor;
[self generateSwatch];
self.needsDisplay = YES;
self.popover.color = newColor;
}
- (void)activate:(BOOL)exclusive
{
[self activate]; // always exclusive
}
- (void)activate
{
self.popover = [BFColorPickerPopover sharedPopover];
self.popover.color = self.buttonColor;
[self.popover showRelativeToRect:self.frame ofView:self.superview
preferredEdge:self.preferredEdgeForPopover];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(popoverDidClose:)
name:NSPopoverDidCloseNotification
object:self.popover];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(colorDidChange:)
name:NSColorPanelColorDidChangeNotification
object:self.popover.colorPanel];
activeButton = self;
self.active = YES;
}
- (void)deactivate
{
if (self.popover)
{
[self.popover close];
self.popover = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSPopoverDidCloseNotification object:self.popover];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSColorPanelColorDidChangeNotification
object:self.popover.colorPanel];
if (activeButton == self) activeButton = nil;
self.active = NO;
}
- (void)popoverDidClose:(NSNotification *)notification
{
self.popover = nil; // don't let deactivate ask it to close again
[self deactivate];
}
- (void)colorDidChange:(NSNotification *)notification
{
self.buttonColor = self.popover.colorPanel.color;
[self generateSwatch];
self.needsDisplay = YES;
[self sendAction:self.action to:self.target];
}
- (void)mouseDown:(NSEvent *)theEvent
{
if (self.isEnabled && !self.active)
[self activate];
else if (self.active)
[self deactivate];
}
- (void)takeColorFrom:(id)sender
{
if ([sender respondsToSelector:@selector(color)])
self.color = [sender color];
}
@end
Приложение:
Я пытался использовать нормальный NSColorWell
вместо моего подкласса NSButton, и того же вопроса. Цвета, выбранные на панели, вызывают у первого респондента changeColor:
в дополнение к вызову метода действия. Так что забыв про все NSButton
на мой вопрос, как, в общем, NSColorWell
чей цвет также не должен быть помещен на первого респондента? Нужно ли прибегать к настройке ожидаемого первого респондента, чтобы выборочно игнорировать changeColor:
или делает NSColorWell
первый респондент действительно то, что нужно сделать, или что-то еще?
1 ответ
Да, лучший термин для веб-поиска (NSColorWell "первый респондент"), и я вижу, что другие боролись с NSColorWell
и эта же проблема давно. Многие старые темы списка рассылки рассмотрели это и увидели 3 решения:
http://www.cocoabuilder.com/archive/cocoa/82832-nscolorwell-changecolor-and-first-responder.html Дуглас Дэвидсон предлагает создать подкласс потенциального первого респондента, чтобы они игнорировали
changeColor:
(вероятно, что я буду делать)http://www.cocoabuilder.com/archive/cocoa/3263-your-nscolorwell-got-in-my-nstext.html Гай Инглиш предлагает сделать цвет хорошо первым респондентом временно (что я пробовал, но у меня были проблемы из-за цвета хорошо быть в панели, которую я не хочу становиться ключевой)
http://www.cocoabuilder.com/archive/cocoa/180323-detecting-currently-active-nscolorwell.html Мартин предлагает сокращать, предотвращая
changeColor:
позвони в первую очередь, представившисьNSColorPanel
и переопределение приватного метода (ближе всего к тому, что я хотел, но больше риск отказа от магазина приложений, чем мне удобно)
ОБНОВЛЕНИЕ: я попробовал #1, но оказывается, что я не могу переопределить первый респондент (WebView
/ WebHTMLView
гррр). Переходя к № 3, я положил следующее в NSColorPanel
категория, сделал мой набор кнопок цвета panel.avoidsChangingFirstResponder=YES
и похоже на работу
static char changeColorPatchAssociatedObjectKey; // address of this is used as a unique runtime value
- (BOOL)avoidsChangingFirstResponder
{
NSNumber *changeColorPatchFlag = (NSNumber *)objc_getAssociatedObject(self, &changeColorPatchAssociatedObjectKey);
return changeColorPatchFlag && changeColorPatchFlag.boolValue;
}
- (void)setAvoidsChangingFirstResponder:(BOOL)enablePatch
{
NSNumber *changeColorPatchFlag = (NSNumber *)objc_getAssociatedObject(self, &changeColorPatchAssociatedObjectKey);
if ((!changeColorPatchFlag && enablePatch) || (changeColorPatchFlag && changeColorPatchFlag.boolValue != enablePatch))
objc_setAssociatedObject(self, &changeColorPatchAssociatedObjectKey, @( enablePatch ), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+ (void)load
{
if (self == [NSColorPanel class])
{
// patch implementation of _forceSendAction:notification:firstResponder: (use swizzle technique from MAKVONotificationCenter.m)
// for one that calls original but with the last BOOL parameter conditionally changed to NO
SEL methodSel = NSSelectorFromString(@"_forceSendAction:notification:firstResponder:");
Method method = class_getInstanceMethod(self, methodSel);
IMP origImpl = method_getImplementation(method);
IMP newImpl = imp_implementationWithBlock(^(void *obj, SEL s, BOOL isAct, BOOL isNotif, BOOL isFirstResp) {
NSNumber *changeColorPatchFlag = (NSNumber *)objc_getAssociatedObject((__bridge id)(obj), &changeColorPatchAssociatedObjectKey);
if (changeColorPatchFlag && changeColorPatchFlag.boolValue)
isFirstResp = NO;
((void (*)(void *, SEL, BOOL, BOOL, BOOL))origImpl)(obj, s, isAct, isNotif, isFirstResp);
});
class_replaceMethod(self, methodSel, newImpl, method_getTypeEncoding(method));
}
}
Я надеюсь, что кто-то еще находит это полезным.