ObjC - method works on application launch, but not button press [duplicate] - objective-c

Hello I am new to Cocoa programming and I met a problem about NSRectFill.
There is one button in the window, and the following is my AppDelegate.m file:
#implementation LGAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[[NSColor redColor] set];
NSRectFill(NSMakeRect( 50,50,10,10));
}
- (IBAction)buttonPressed:(id)sender
{
[[NSColor greenColor] set];
NSRectFill(NSMakeRect( 60,60,10,10));
}
#end
What I expected to see is a rectangle shows when the application starts, and another rectangle shows after clicking the button. However, only one rectangle shows, nothing happened after clicking the button.
Please help me to solve this. Thank you.
Yours,
Z

well you don't have any context, the system has no idea where you want to draw.
if you want to draw on a view or an image you have to use a lockFocus / unlockFocus pair.
so if you have a view as an outlet called redView
[redView lockFocus];
[[NSColor redColor] set];
NSRectFill(NSMakeRect( 50,50,10,10));
[redView unlockFocus];
but this is a really poor model, you generally want your objects to draw themselves.
when a views drawRect: method is called you already have focus and don't need the lock unlock pair

Some background, applicationDidFinishLaunching: is called at the launch of a program once the program is 'done' loading resources.
But at this point, there could be many windows and many views displayed by the application. Simply calling NSRectFill() is not enough. The application wouldn't know where to draw said rectangle (is it in this window or that one? This NSView or that one). Even if there's only one window and it might seem obvious... there might be multiple NSViews displayed that you're unaware of.... and with computers you have to be very explicit with your commands.
The bottom line is: there is no 'context' established for where the drawing actions to occur. As d00dle points out, you should read up on Drawing Guide.
When an NSView's drawRect: is called, a context (itself) has already been set/defined. You could draw directly from your delegate's applicationDidFinishLaunching: but a 'context' needs to be defined.

Take a look at apple's Drawing Guide

Related

How to change background color for NSview in Cocoa

I want to change background color for many nsview. I override drawRect: on subclass NSview but i don't know how to set background color for myview( is reference IBOUTLET). please help me. Thanks so much
Code for CustomView.h
#import <Cocoa/Cocoa.h>
#interface CustomView : NSView
#end
Code for CustomView.m
#import "CustomView.h"
#implementation CustomView
- (void) drawRect:(NSRect)dirtyRect {
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
#end
And in main class, i added #import "CustomView.h" but i don't know how to set background for myview.
Welcome to Cocoa drawing.
Cocoa drawing uses Quartz which is a PDF model.
Drawing in this occurs in a back to front procedural order.
In Quartz drawing there is a drawing environment state object called the Graphics Context.
This is an implicit object in many of the drawing ops in AppKit.
(in Core Graphics or other APIs it could need to be explicitly called)
You tell the Graphics Context what the current color and other parameters are, then draw something, then change parameters and draw more, etc...
In AppKit, you do this by sending a message to the NSColor object, which is weird. but that's how it works.
In your drawRect: method you should call super first usually, because you probably want your drawing on top of that...
- (void) drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// This next line sets the the current fill color parameter of the Graphics Context
[[NSColor whiteColor] setFill];
// This next function fills a rect the same as dirtyRect with the current fill color of the Graphics Context.
NSRectFill(dirtyRect);
// You might want to use _bounds or self.bounds if you want to be sure to fill the entire bounds rect of the view.
}
If you want to change the color, you'll need an #property NSColor
You might need more than one for your drawing.
That allows you to set the color.
You might want the view to use KVO and observe its own color property then draw itself if the color property changes.
You could do a lot of different things to set the color. (a button or pallette elsewhere) But all of them would eventually result in sending a message to set the color of a property of your view for drawing.
Finally, if you want to update the drawing, you need to call [myView setNeedsDisplay:YES]; where myView is a reference to an instance of the NSView subclass.
There is also display but that's forceful.
setNeedsDisplay: says to schedule it on the next run of the event loop (runLoop). display kind of makes everything jump to that right away.
The event loop comes back around fast enough you shouldn't force it.
Of note, setNeedsDisplay: is the entire view.
In a fancy ideal world with complex views, you might want to more appropriately optimize things by calling setNeedsDisplayInRect: where you designate a specific CG/NSRect of the view as needing to be redrawn.
This allows the system to focus redrawing to the smallest union rect possible in the window.
I'm super late, but this is how I do it - there's no need to sub class:
NSView *myview = [NSView new];
[view setWantsLayer:YES];
view.layer.backgroundColor = [NSColor greenColor].CGColor;

Is drawRect really changing the background color of a NSView?

Everywhere on the internet I can read that to change the background color of a NSView you can just override its method drawRect like this:
- (void)drawRect:(NSRect)rect
{
[[NSColor yellowColor] set];
NSRectFill(rect);
}
For example here.
However, in my case, the color is drawn on top of the view (I can't see anymore the content), which is quite logical to me. DrawRect is supposed to draw the view, not just its background.
what am I missing?
You should call [super drawRect:rect] after filling the background. Otherwise, you're simply replacing everything that would be drawn by the superclass's implementation.
The examples you refer to are displaying the subviews of the view above the background.

Drawing Transparent Images

I created a custom view to a button, as I need to implement some highlighting when the mouse is over. The class is very simple, and I already implemented mouseEntered: as well as mouseExited:. The view was registered for tracking in the init method (not sure if it's the best place).
The problem is drawing. I keep an ivar mouseOver, set to YES on mouse enter and NO on mouse exited. The other ivar is for the image, called image. The difference between mouse over or not when it comes to drawing, is the transparency. Here is my drawRect::
- (void)drawRect:(NSRect)dirtyRect
{
[image drawAtPoint:NSMakePoint(0.0,0.0)
fromRect:dirtyRect
operation:NSCompositeCopy
fraction:((mouseOver) ? 1.0 : 0.0)];
}
It works nicely, but only when the mouse first entered, apparently. I guess the problem is that the view is not cleared before drawing the other image. I tried adding:
[[NSColor clearColor] set];
NSRectFillUsingOperation(dirtyRect, NSCompositeClear);
But without success. How can I fix this?
[NSColor clearColor] is a purely transparent color. You probably want to fill using a color with some opacity, like, say, [NSColor whiteColor].

Draw an NSView into an NSGraphicsContext?

I have a CGContext, which I can turn into an NSGraphicsContext.
I have an NSWindow with a clipRect for the context.
I want to put a scrollview into the context and then some other view into the scrollview so I can put an image into it... However, I can't figure out how to attach the scrollview into the context.
Eventually the view will probably be coming from a nib, but I don't see how that would matter.
I've seen this thread, (http://lists.apple.com/archives/quartz-dev/2006/Nov/msg00010.html) But they seem to leave off the step of how to attach the view into the context, unless there's something obvious I'm missing.
EDIT:
The reason I'm in this situation is that I'm writing a Mozilla Plugin. The browser gives me a CGContext (Quartz) and a WindowRef (QuickDraw). I can turn the CGContext into an NSGraphicsContext, and I can turn the windowRef into an NSWindow. From another data structure I also have the clipping rectangle...
I'm trying to draw an image into that context, with scrollbars as needed, and buttons and other UI elements... so I need (want) an NSView...
You can't put a view into a graphics context. A view goes either into another view, or as the content view of a window.
You can draw a view into a context by setting that context as the current context and telling the view to draw. You might do this as a means of rendering the view to an image, but otherwise, I can't think of a reason to do it. (Edit: OK, being a Netscape plug-in is probably a good reason.)
Normally, a view gets its own graphics context in NSView's implementation of the lockFocus method, which is called for you by display, which is called for you by displayIfNeeded (only if the view needs display, obviously), which is called for you as part of the event loop.
You don't need to create a context for a view except in very rare circumstances, such as the export-to-an-image case I mentioned. Normally, you let the view take care of that itself.
A partial solution?
What I have done currently is create a nib with a button in an IKImageView inside an NSScrollView. I load this in my plugin.
Then, since I have the NSWindow, I can get the contentView of the window. Then, I add the scrollview as subview of contentView.
It appears, but there seems to be some coordinate confusion about where the origin is. (top vs bottom) and since I'm mucking with the contentview of the WHOLE WINDOW, I'm doing some stuff very globally that perhaps I should be doing more locally. Like, the view never disappears, even when you close the tab, or go to another tab. (it does close when you close the window of course)
So, does this sound like a reasonable way of doing this? it feels a bit ... kludgy...
For future generations (and me when I forget how I did this and Google leads me back to my own question) Here's how I'm doing this:
I have a NIB with all my views, I load this on start-up.
on SetWindow, I set the clip rect and actually do the attaching:
NP_CGContext* npContext = (NP_CGContext*) window->window;
NSWindow* browserWindow = [[[NSWindow alloc] initWithWindowRef:npContext->window] autorelease];
NSView* cView = [browserWindow contentView];
NSView* hitView = [cView hitTest:NSMakePoint(window->x + 1, clip.origin.y + 1)];
if (hitView == nil || ![[hitView className] isEqualToString:#"ChildView"])
{
return;
}
superView = [hitView retain];
[superView addSubview: topView];
[superView setNextResponder: topView];
[topView setNextResponder: nil];
[browserWindow makeFirstResponder: topView];
To make sure I only addSubView once, I have a flag...
And then in handleEvent, I actually draw, Because I'm using an IKImageView, I can use the undocumented method: [imageView setImage: image]; which takes an NSImage.
So far this seems to be working for me. Hopefully this helps someone else.

Background image for a window in Cocoa framework

I am looking for a perfect solution to set a background image for a window in a cocoa application. I haven't found a solution to this, I am new in objective c, so please anyone help me...
A window in Cocoa has a root-level view called the "content view". This is the view that contains all the others in a window. By default, it's just a plain, blank NSView. But you could easily create your own custom NSView subclass, override the drawRect: method to draw your background image, and use that for your custom view.
However, it might just be easier to use a plain old NSImageView. The advantage of this is that you can set, for example, autosizing behavior to keep the image pinned to one corner (try this with Installer.app by resizing the installer window). You would also be able to make it semi-opaque so that the background shows through a bit. (Again, I'm thinking of Installer.app; your app could be totally different)
Hope that gets you going in the right direction!
Michael Vannorsdel suggests sublassing NSView for the purpose, and I quote:
You'd really be better off making an
NSView subclass and having it draw
the image you want in drawRect:.
- (void)awakeFromNib
{
myImage = [[NSImage alloc] init....
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)rect
{
NSSize isize = [myImage size];
[myImage drawInRect:[self bounds] fromRect:NSMakeRect(0.0, 0.0,
isize.width, isize.height) operation: NSCompositeCopy fraction:1.0];
}
Read that whole thread on cocoabuilder, it's quite instructive.