1

NSButtonサブクラスを として機能させるためのいくつかの例に従いましたNSColorWell(NSButtonサブクラスは既に必要な外観動作を提供しているため)、しかし、ボタンを使用してパネルを呼び出して色を変更した後、の色も変更されていることに気付きましたドキュメントで選択したテキスト。代わりに、外観のカスタマイズでサブクラス化した場合NSColorWell、この問題は発生しませんか?

ただし、それを回避し、ボタンのサブクラスを引き続き使用できる回避策を期待しています。ボタン自体をファーストレスポンダーにすることを提案するディスカッションスレッドを見たことがありますが、ボタンが別のパレットにあるため、これを機能させるのに問題があります。また、レスポンダー チェーンを変更したり、パレットをキー ウィンドウにしたりしたくありません。NSColorPanelsetColor: をオーバーライドして、予想される通知を投稿するが、最初のレスポンダーに触れないようにするカテゴリは、どれほど悪でしょうか?

(注:カラーパネルを開くだけでなく、現在BFColorPickerPopoverDrummerB https://github.com/DrummerB/BFColorPickerPopoverを利用しています。しかし、それはそれほど複雑ではないと思います。私は同じNSColorPanel/最初に持っていました統合する前のレスポンダーの問題)。

NSColorPanelソースコードを投稿するように求められたので、ここに私の NSButton サブクラスからの関連ビットを示します (直接ではなく、上記のピッカー ポップオーバーを使用することに注意してください)。

.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

補遺:

NSColorWellNSButton サブクラスの代わりに法線を使用してみましたが、同じ問題が発生しました。changeColor:パネルで選択された色は、アクション メソッドの呼び出しに加えて、ファーストレスポンダを呼び出します。私の質問のすべてを忘れてNSButton、一般的に、どのようにNSColorWell色をファーストレスポンダにも押し付けてはいけませんか? 予想されるファーストレスポンダーをカスタマイズして選択的に無視するchangeColor:必要がありNSColorWellますか、それともファーストレスポンダーを本当にやるべきこと、または何か他のものにしていますか?

4

1 に答える 1

2

はい、より良い Web 検索用語 ( NSColorWell "first Responder"NSColorWell ) と、他の人がこの同じ問題に長い間苦労しているのを見ます。多くの古いメーリング リスト スレッドがこれをカバーしており、3 つの解決策が見られました。

  1. http://www.cocoabuilder.com/archive/cocoa/82832-nscolorwell-changecolor-and-first-responder.html Douglas Davidson は、潜在的なファーストレスポンダーをサブクラス化して無視するよう提案してchangeColor:います (おそらく私がすることでしょう)。

  2. http://www.cocoabuilder.com/archive/cocoa/3263-your-nscolorwell-got-in-my-nstext.htmlガイ イングリッシュは、カラー ウェルを一時的にファーストレスポンダーにすることを提案しています (私が試したものの、カラーが原因で問題が発生しました)。キーになりたくないパネルにいる)

  3. http://www.cocoabuilder.com/archive/cocoa/180323-detecting-currently-active-nscolorwell.htmlchangeColor: Martin は、プライベート メソッドのふりをしてオーバーライドすることで、最初に呼び出しを防止することを提案していNSColorPanelます (私が望んでいたものに最も近い、しかし、私が満足しているよりもアプリストアの拒否のリスクが高い)

更新: #1 を試しましたが、ファーストレスポンダ ( WebView/ WebHTMLViewgrrr) をオーバーライドできないことがわかりました。#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));
    }
}

他の誰かがこれが役立つことを願っています。

于 2013-01-17T18:15:16.207 に答える