Cocoa 可选NSTextField和NSColorPanel–;如何打破他们不希望的相互作用?

Cocoa 可选NSTextField和NSColorPanel–;如何打破他们不希望的相互作用?,cocoa,nstextfield,nscolorpanel,nscolorwell,Cocoa,Nstextfield,Nscolorpanel,Nscolorwell,在一个看似微不足道的设置中,我遇到了一个可选的NSTextField和一个NSColorPanel之间不必要的相互作用,我无法摆脱它,这让我发疯 设置如下:在一个窗口中,我有一个可选的多行标签(事实上是NSTextField)和一个NSColorWell 颜色井允许用户为GUI中的几何对象着色;它与文本无关。当然,单击颜色井会激活它,即打开共享的NSColorPanel,并将颜色井连接到它 文本字段完全独立于GUI中的彩色对象,并向用户显示数据。它是只读的,即不可编辑。由于数据是按列组织的,因此

在一个看似微不足道的设置中,我遇到了一个可选的NSTextField和一个NSColorPanel之间不必要的相互作用,我无法摆脱它,这让我发疯

设置如下:在一个窗口中,我有一个可选的多行标签(事实上是NSTextField)和一个NSColorWell

颜色井允许用户为GUI中的几何对象着色;它与文本无关。当然,单击颜色井会激活它,即打开共享的NSColorPanel,并将颜色井连接到它

文本字段完全独立于GUI中的彩色对象,并向用户显示数据。它是只读的,即不可编辑。由于数据是按列组织的,因此我使用选项卡进行文本格式设置,并使用
setAttributedStringValue:
方法NSTextField来显示数据

乍一看,在如此琐碎的设置中,一切都如您所期望的那样工作

但问题来了:我希望用户能够复制文本字段中的数据,以便在其他地方进行处理。因此,NSTextField必须是可选择的。将其设置为
可选
是问题的开始:

当用户单击可选文本字段选择文本时,窗口的字段编辑器将接管,因此,属性文本的所有选项卡设置将丢失,文本将混合。防止这种情况发生的通常方法是将NSTextField
allowEditingTextAttributes
属性设置为
YES
。如果我这样做,当用户选择文本时,选项卡格式将保留现在NSColorPanel(如果可见)也会无意中切换到文本颜色(始终为黑色),如果颜色井处于活动状态(连接到NSColorPanel),它将保持活动状态,从而将所有几何图形用户界面对象的颜色更改为黑色。哎哟

我没有办法将
可选
允许编辑NSTextField的
属性设置为
YES
,但仍然阻止它与NSColorPanel通信

明显的替代方法是保留所选文本的选项卡格式,即使
allowEditingTextAttributes
设置为
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];
    }
@implementation myTextFieldCell

- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
    {
        NSTextView *newTextObj = (NSTextView*)[super setUpFieldEditorAttributes:textObj];
        NSLog(@"STYLE: %@", [newTextObj defaultParagraphStyle]);
        return newTextObj;
    }

@end
并为
窗口中的文本字段激活它。WillReturnFieldEditor:toObject:
委托方法:

- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client
    {
        if (client == myTextField) return myFieldEditor;
        return nil;
    }
- (void)textDidEndEditing:(NSNotification*)notification
    {
        [[notification object] setSelectedRange:NSMakeRange(UINT64_MAX, 0)];
    }
我甚至通过对文本字段的NSTextFieldCell子类化,并记录传播的字段编辑器,来确保使用我的自定义字段编辑器:

- (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];
    }
@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
这正是人们所期望的

但是,只要我选择文本,选项卡格式就会在文本字段中消失。我不知道为什么这不起作用

所以不管怎样我都被困住了。如果我将NSTextField
allowEditingTextAttributes
属性设置为
YES
,则选中文本时会保留选项卡格式,但GUI中的彩色对象无意中变为黑色。如果我将
AllowEditingTextAttributes
属性设置为
,则颜色面板将正常工作,但一旦选择文本,选项卡格式将丢失

这是一个非常不幸的案例,Cocoa试图变得过于聪明,从而使一个完全琐碎的设置成为一个巨大的问题


任何人有什么想法吗?

好的,所以我最后得到了@Willeke(谢谢!!)在对我的问题的评论中提出的建议:使用NSTextView而不是NSTextField来实现我的多行标签

我将首先总结为什么使用NSTextField似乎无法实现我想要的目标,然后总结使用NSTextView的解决方案

为什么NSTextField不起作用 如前所述,我的解决方案是为NSTextField自定义字段编辑器,设置所需的制表位,这样我就不需要将NSTextField
AllowEditingTextAttributes
属性设置为
(这会无意中将文本字段耦合到颜色面板)。我希望,当我在文本字段中选择文本时,这将保留属性字符串段落样式的制表位,从而激活字段编辑器

大量测试表明,由于以下几个原因,该方法不起作用:

  • 正如@Willeke指出的,将NSTextView
    usesFontPanel
    属性设置为
    NO
    也会断开文本视图与颜色面板的连接(如需要)。但是,这不适用于NSTextView,它是NSTextField的字段编辑器,因为在此上下文中,此设置总是被NSTextField
    AllowEditingTextAttributes
    属性覆盖:如果
    AllowEditingTextAttributes
    YES
    ,则,字体和颜色面板是耦合的,与
    usesFontPanel
    的值无关;如果是
    NO
    ,则字体和颜色面板是解耦的,与
    usesFontPanel
    的值无关
  • 使用制表符的想法停止了
    #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