【问题标题】:NSButton subclass as colorwell & preventing NSColorPanel from touching the first responderNSButton 子类作为 colorwell 并防止 NSColorPanel 接触第一响应者
【发布时间】:2012-12-28 23:59:36
【问题描述】:

我按照一些示例将NSButton 子类作为NSColorWell 工作(因为我们的NSButton 子类已经为我们提供了我们需要的外观行为),但是我注意到在使用按钮调用面板和改变颜色,它也改变了我们文档中选定文本的颜色。如果我改为使用我们的外观自定义子类化NSColorWell,它不会有这个问题吗?

但是,我仍然希望有一种解决方法可以避免这种情况,并且仍然可以让我们使用我们的按钮子类。我已经看到讨论线程建议让按钮本身成为第一响应者,但是由于按钮位于单独的调色板中,我无法使其正常工作。此外,我不希望更改响应者链或让调色板成为关键窗口。 NSColorPanel 上的类别会被覆盖 setColor:,使其发布预期的通知但不触及第一响应者,这有多邪恶?

(注意,我目前正在使用 DrummerB 的BFColorPickerPopover https://github.com/DrummerB/BFColorPickerPopover,而不是简单地打开颜色面板。但是我不认为这很复杂。我有同样的NSColorPanel /集成之前的第一响应者问题)。

被要求发布源代码,所以这是我的 NSButton 子类中的相关位(注意,使用上面提到的选择器弹出框,而不是直接使用 NSColorPanel):

.h:

@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 成为真正的第一响应者,还是别的什么?

【问题讨论】:

    标签: objective-c cocoa nsbutton first-responder nscolorpanel


    【解决方案1】:

    是的,一个更好的网络搜索词(NSColorWell“第一响应者”),我看到其他人长期以来一直在为NSColorWell 和同样的问题而苦苦挣扎。许多旧的邮件列表线程已经涵盖了这一点,并看到了 3 个解决方案:

    1. http://www.cocoabuilder.com/archive/cocoa/82832-nscolorwell-changecolor-and-first-responder.htmlDouglas Davidson 建议对潜在的第一响应者进行子类化,以便他们忽略 changeColor:(我可能会这样做)

    2. http://www.cocoabuilder.com/archive/cocoa/3263-your-nscolorwell-got-in-my-nstext.htmlGuy English 建议暂时让颜色井成为第一响应者(我尝试过但由于面板中的颜色井而出现问题,我不想成为关键)

      李>
    3. http://www.cocoabuilder.com/archive/cocoa/180323-detecting-currently-active-nscolorwell.html Martin 建议首先通过冒充NSColorPanel 并覆盖私有方法(最接近我想要的方法,但应用商店拒绝的风险比我更大)来阻止changeColor: 调用我很舒服)

    更新:我尝试了 #1,但事实证明我无法覆盖第一响应者 (WebView / WebHTMLView grrr)。与#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));
        }
    }
    

    我希望其他人觉得这很有用。

    【讨论】:

    • 有抱负的应用程序开发人员在这里...您的代码是否调用了任何私有方法?
    • 如前所述,它覆盖了未记录的NSColorPanel 方法_forceSendAction:notification:firstResponder:。我这样做的应用程序最终甚至从未尝试进入 Mac App Store,所以这对我们来说不是问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-17
    • 2020-12-31
    • 2011-08-21
    • 1970-01-01
    相关资源
    最近更新 更多