I'm working with a NSOutlineView located on a HUD panel. I configured it so that it doesn't draw its background. Everything looks fine until I double click to edit a cell.
The field editor draws its background and focus ring which completely ruin the whole user experience.
This is what I'm doing in the subclass of NSTextFieldCell:
- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
{
NSText *text = [super setUpFieldEditorAttributes:textObj];
[text setDrawsBackground:YES];
[text setBackgroundColor:[NSColor darkGrayColor]];
return text;
}
If I use setDrawsBackground:NO it's completely ignored and I get a white background. My solution is far from being good because I can't touch the alpha component of the color (if I do that, again the field editor will use another color as a background), but at least I don't get a white background.
I'm wondering if there's an actual solution to this problem. Do I have to provide my own field editor? Is it worth it?
What I want is simply a field editor with no background and no focus ring, just the cursor blinking.
Thanks!
The problem is that the white background is drawn by NSTableView when it's sent -editColumn:row:withEvent:select:. It fills the cell's rect with +[NSColor textBackgroundColor].
If there's a public API for overriding the current setting for named colors from the developer colorspace, we could set it inside an override of -editColumn:row:withEvent:select: or the like. I do not recall such an API (pointers are appreciated). ALSO: I've only tested this code on Snow Leopard (even the Leopard SDK addendum below). Verify the code against the actual SDKs and runtime environments you intend to support.
NSTableView has a private accessor it uses for the fill color, but it's a read-only property. No setter, so we can't just change the value on a standard NSTableView. We must subclass it. (Since you want the same behavior in an outlineView and NSOutlineView is already a subclass of NSTableView, we're going to subclass NSOutlineView. But, aside from the superclass, the code is identical.)
#interface ASCOutlineView : NSOutlineView {
}
#end
#implementation ASCOutlineView
- _textBackgroundColor
{
return ([NSColor clearColor]);
}
#end
seems to be all one needs to prevent that glaring white block from ruining your HUD when editing table cells in Snow Leopard.
Apps compiled against the Leopard SDK need a little more support though. Leopard's tableViews may have hard-coded some rendering properties so we need to override a choice method.
NSTextFieldCells are actually wrappers for NSTextViews so they can be used inside controls. They normally share the same textView instance, which is managed by the window (or its subclass, panel, in this case). NSTableView alters the settings of the NSTextFieldCell to conform to system UI settings for editing data. Mostly. The NSTextFieldCell then propagates those settings to the NSTextView. At any point along this pipeline we can override a method or two to alter the values of those properties to match our own UI.
I use -[NSTextFieldCell setDrawsBackground:] because it requires little effort to get correct. It's also important to keep the internal state as consistent with the effect we're hoping to achieve in the event some other object might depend on that state.
#interface ASCTextFieldCell : NSTextFieldCell {
}
#end
#implementation ASCTextFieldCell
- (void)setDrawsBackground: (BOOL)flag
{
[super setDrawsBackground: NO];
}
#end
And preventing the focus ring from appearing while the cell's being edited is a simple matter of changing the setting of its focus ring type. Frustratingly, IB doesn't provide access to this property, so it must be done programmatically:
for(eachColumn in [hudOutlineView tableColumns])
{
columnCell = [[ASCTextFieldCell alloc] initTextCell: #""];
[eachColumn setDataCell: columnCell];
if([columnCell respondsToSelector: #selector(setFocusRingType:)] != NO)
[(NSTextFieldCell *)columnCell setFocusRingType: NSFocusRingTypeNone];
}
It looks like there is other background behind field editor, which is drawn as white.
Probably, NSCell, or background of row, whatever else.
Related
I'm working on a database application that has a graphical editor that is very similar to Interface Builder. Unlike IB, however, this editor can be flipped from editor mode to live mode, where the user interface is fully operational (buttons can be clicked, text edited, etc.) To do this, the graphical editor uses standard Appkit interface classes -- NSButton, NSTextView, etc. The editor itself is implemented with a custom subclass of NSView. All of the user interface elements are subviews of this custom NSView, new elements are added using the addSubview: method, making the new element the topmost visible element (note -- the views are not layer-backed, just regular views). The user can also use Bring-to-Front and Send-to-Back commands to change the ordering of the subviews. This movie shows two overlapping NSButton elements (for illustration purposes, of course normally you would never overlap them), and how the program can re-arrange the subview to change the Z-order of the user interface elements.
The problem is, this works with every kind of interface element except NSImageView. In the movie before there are two elements, an NSButton and an NSImageView. The NSButton is actually "on top" the whole time, the NSImageView element should appear behind the button, but no matter the order of the subviews, the NSImageView always appears on top.
If there are two overlapping NSImageView objects, the visible stacking order between them is unpredictable, but they will always appear above all other objects, no matter what the order of the subviews is.
A possibly useful clue is that if I implement my own custom view that draws an image directly in it's drawRect: method, that works fine. So that's one possible solution, but I am reluctant because that means re-implementing a large swath of useful features that NSImageView normally takes care of, some of which are quite complex like supporting animated GIF display. Other than this layering/z-order issue, everything else about NSImageView works fine.
Perhaps NSImageView is using layer-backing without my asking for it, so it isn't mixing in properly with my other objects? I can't find any documentation that indicates this. I am not linking against the QuartzCore framework.
Here is the code that adds the NSImageView element as a subview to the graphic editor view.
- (void)objectDidAppearBelow:(NSView *)nextView
{
FormView * formView = [FormWindowController currentFormView]; // get view element will be placed into
NSScrollView * imageContainer = [[NSScrollView alloc] initWithFrame:insideBorderRect];
ImageView * ixView = [[ImageView alloc] initWithFrame:[self insideFormObjectBorder:objectRectangle]];
[ixView setOwnerObject:self];
[imageContainer setDocumentView:ixView];
[imageContainer setAutoresizesSubviews:YES];
[shapeView addSubview:imageContainer Below:nextView];
imageDocumentView = ixView; // save weak reference to image view so it can be manipulated
}
In other places there is nearly identical code for NSButton (in several variations for push buttons, radio buttons, etc.), NSTextView, NSTableView (for lists and matrixes), NSSlider, NSScroller, NSSegmentedControl, even WebView. All the others work correctly with overlapping objects, including WebView, only NSImageView doesn't work as expected.
For my reference this is #429 in the Panorama X issue tracker.
I discussed this problem with Apple engineers at WWDC 2018. It turns out that as I suspected, in some cases Appkit will use layer backing for NSImageView even if you did not ask for it! So the best solution is to switch all views to layer backing (which will happen automatically with Mojave).
In this particular case the NSImageView was inside a NSScrollView, which I didn't mention because I didn't think it was important (my bad). It turns out, this is the case where Appkit thinks using layer backing would be a good idea (to optimize scrolling). So another way to fix this is to subclass NSImageView (which I had already done for other reasons) and add this method (written on the spot by an Apple engineer who prefers to remain uncredited).
+ (BOOL)isCompatibleWithResponsiveScrolling {
if (NSAppKitVersionNumber <= 1561. /* NSAppKitVersionNumber10_13 */) {
return NO;
} else {
return YES;
}
}
I was assured that this is all part of the public, documented API, though the documentation is minimal (surprise surprise). There is some discussion of responsive scrolling in the What's new in OS X 10.9 release notes. The check with NSAppKitVersionNumber is to make sure to turn off this patch when running on Mojave, since everything is layer backed.
In CSS, you can change the effect of focusing on something using:
.myelement:focus { ... }
While in Cocoa, the text fields always have an ugly blue glow. How do I change the effect of focusing on an NSTextField (or not have it do anything at all)?
The Cocoa equivalent of the command above would be:
[[textField window] makeFirstResponder:textField];
As to changing the appearance, are you asking how to change it for all controls in all applications ("globally") or just for a text field in your own app? There's no API for a global change, so system hacks are your only avenue. Good luck.
For controls you own (those that belong to your own application), you can set the focus ring type in Interface Builder or by code at runtime like this:
[textField setFocusRingType:NSFocusRingTypeNone];
// (or NSFocusRingTypeDefault or NSFocusRingTypeExterior)
I have an NSView in IB which sits above the app window. I have a subclass of NSView (AddSource) which I assign to the NSView.
On awakeFromNib I instantiate the view:
//add a new Add Source class
addSourceView = [[AddSource alloc] initWithFrame:NSMakeRect(0.0, 959.0, 307.0, 118.0)];
[[winMain contentView] addSubview:addSourceView];
in addSourceView's drawRect method I am adding a white background to the view:
[[NSColor whiteColor] set];
NSRectFill(rect);
[self setNeedsDisplay:YES];//added this to see if it might solve the problem
In winMain's contentView I have a NSButton that when clicked slides the addSourceView onto the window:
NSRect addSourceViewFrame = [addSourceView frame];
addSourceViewFrame.origin.y = 841.0;
[[addSourceView animator] setFrame:addSourceViewFrame];
But it seems as if the app is painting over the IBOutlets I placed on the NSView in IB. If, in IB, I repoistion the NSView so that it is on screen when the app launches everything works fine, the IBOutlets are there as well as the background color.
I'm not sure why this is happening. I've done this before with no problems. I must be doing something different this time.
Thanks for any help.
*note - on the 3rd screen capture, when I say this is what the app looks like when opened, that's when I hard code the Y position of the NSView. When it is functioning correctly it should open as screen capture 1.
Most likely your buttons and custom view are siblings, i.e. they are both subviews of your window's content view. Since siblings are "Stacked" depending on the order in which they are added, when you add the view in code it is being added on top of the buttons. You should be able to fix it by explicitly specifying where the view should be positioned relative to its new siblings like so:
[[winMain contentView] addSubview:addSourceView positioned:NSWindowBelow relativeTo:nil];
which should place it below any existing subviews of your window's content view. Also, remove the setNeedsDisplay: line in drawRect, that leads to unncessary, possibly infinite, redrawing.
EDIT: OK I see what you're doing.
I would suggest creating a standalove view in the NIB by dragging a "Custom View" object into the left hand side (the vertically-aligned archived objects section) and adding your controls there, that should ensure the controls are actualy subviews of the view, then you can just create a reference to the archived view in code, and add/remove it dynamically as needed.
Honestly though, you should probably be using a sheet for these kinds of modal dialogs. Why reinvent the wheel, and make your app uglier in the process?
You added TWO AddSource views to the window. You added one in IB - this view contains your textFields and buttons that are connected to the IBOutlets and it is positioned outside the window.
Then in -awakeFromNib you create another, blank AddSource view (containing nothing) and animate it into the window.
I can't recommend highly enough the Hillegass as the best introduction to IB and the correct way to build Cocoa Apps.
Also, Assertions can be useful to make sure what you think is happening is actually what is happening.
If you are certain you added a button to your view in IB, assert it is so:-
- (void)awakeFromNib {
NSAssert( myButton, #"did i hook up the outlet?");
}
NSAssert is a macro that has zero overhead in a release build.
Calling [self setNeedsDisplay:YES] from -drawRect just causes the same -drawRect to be called again. This will give you big problems.
I have a standard NSPanel set to HUD style. I want to change the background color, primarily because I want to have a toolbar and don't see any way of making either a standard nstoolbar look good on a HUD nor a way of customizing the background of a toolbar directly.
I am aware of the multitude of ways for creating a completely custom window, and use those in other circumstances. In this case, I want all of the good things that a window provides, but I just don't want transparency. Interestingly, I can change the background color, but not the alpha. Setting alpha values has no effect on the window.
Anyone solved this problem before?
Set the panel's content-view's (just click inside the panel to select it, not the titlebar) subclass to SGPanelView and make that SGPanelView with this implementation of a drawRect method of your class: SGPanelView, a subclass of NSView:
- drawRect:(NSRect)dirtyrect {
[[NSColor blackColor] set];
[NSBezierPath fillRect:[self bounds]];
}
Should work. If not working, try changing bounds to frame.
See http://developer.apple.com/library/mac/#documentation/cocoa/reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html for more information.
I have made a window with an NSOpenGLView that I am rendering openGL content into.
I want to add some buttons and text fields to the view: I can add NSTextFields and NSButtons using interface builder (or code) but they do not appear.
NSOpenGLView is documented as not being able to have sub views, so I then made my own CustomGLView by deriving directly from NSView and implementing the code to create and use a NSOpenGLContext in it. But the subviews are still not appearing :- the OpenGL context paints over them.
On Windows this problem does not exist:- Windows used to host OpenGL MUST have the WS_CLIPCHILDREN and WS_CHIPSIBLINGS styles set ensuring that any peer, or sub children (views) will not be obscured by the OpenGL surface.
How do I get subviews to display over a NSView thats drawing using OpenGL ?
You have 2 choices:
Create a window just for the text field. Add as a child window of the one hosting the OpenGL view. Major downside is you have to manage positioning it correctly if the Open GL view is moved.
Set up your view hierarchy like so:
Layer-backed view
Layer-hosting view whose layer contains an OpenGL layer
Text field
Simply call -setWantsLayer:YES on the subviews of the NSOpenGLView.
NSOpenGLView cannot have subviews according to the documentation. Even if you subclass the NSOpenGLView, that will change nothing.
What you can do is to create a NSView that will hold both the NSOpenGLView and the NSTextField. You then overlap them in the right order to make one draw atop the other.
I'm not heavily into OpenGL yet, but it's my understanding that you can accomplish the visual effect of subviews with Quartz Extreme using layer-backed views; however, those may be problematic. Since subviews are not supported directly, any solution is liable to be a hack.
Indeed, the solution in that link actually hacks a second window to appear over your OpenGL display, the second window displaying the Cocoa views you desire.
The following code (from the above link) is something I've not tested (again not being an OpenGL guy by nature -- yet), but appears like a fairly clever approach:
// This is the GL Window and view you already have
glWindow = [[GLWindow alloc] initWithContentRect:windowRect];
glView = [[[GLView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[glView translateOriginToPoint:NSMakePoint(glView.bounds.size.width/2, glView.bounds.size.height/2)];
[glWindow setContentView:glView];
// And here's your transparent UI window
uiWindow = [[TransparentWindow alloc] initWithContentRect:windowRect];
uiView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[uiView translateOriginToPoint:NSMakePoint(uiView.bounds.size.width/2, uiView.bounds.size.height/2)];
uiView.wantsLayer = YES;
[uiWindow setContentView:uiView];
[glWindow addChildWindow:uiWindow ordered:NSWindowAbove];
Again, I've not tested this, but it looks like it will get you the visual effect you desire.
The text can be rendered into a texture -- I just used this for a project, did a lot of looking for sample code, and ultimately found Apple's GLString demo code, which was an absolute trove of how-to:
http://developer.apple.com/library/mac/#samplecode/CocoaGL/Listings/GLString_m.html
I haven't tried adding buttons, but you can, of course, draw your own and comparing the positions of click events with those of your buttons...
This was my solution:
1) Create a parent NSView (let's call it parentView).
2) Add an NSOpenGLView Child to parentView.
3) Add an additional NSView Child to parentView (make sure this is after the OpenGLView within the hierarchy). You can add additional TextFields, etc. to this view.
4) In the ViewController for the parent make sure you call [parentView setWantsLayer: TRUE]; I did this within -(void) viewWillAppear
1) The NSOpenGLView can have a subview. It can have plenty even.
2) The reason some views, controls and other elements are being bullied by NSOpenGLView is due to the loading process when the Application launches. I.e If you add a slider or textfield above and into the content view of the window where the NSOpenGLView also resides, upon Application-Launch that textfield will most likely wind up beneath the NSOpenGLView.
This is an Apple Bug. And they know about it.
You can solve it quite easily even without adding a subview to NSOpenGLView...
In Interface Builder drag i.e. a CustomView into the canvas (Not the view). And set it the way you want it with sliders, text and what not. Then create an outlet (Call it i.e topView) in your view controller. Then somewhere in your code... Perhaps (applicationDidFinishLaunching) add this line...
[_window.contentView addSubview:_topView];
(Do your positioning & layout)
This will do the exact same thing as if you had dragged it into the contentView yourself inside IB. Only it will draw the darn thing in the correct Z position.
You loose IB's constraints this way and have to it manually
One could also just subclass and CAOpenGLLayer and use that as a backing layer inside of a regular NSView. There too it is drawn correctly...
Here is Apple's way of wanting to do that. CALayers are a Godsend ;)
Enter following String ** NSOpenGLLayer** in search and hit enter to get to where it is...
NSOpenGLLayer
Hope this helps....