PDA

View Full Version : Referencing Controls within / from sub views




CylonGlitch
Jun 24, 2012, 11:41 AM
I have been playing around with drag and drop commands. I have all that working, but I want to be able to update controls after the drop has occurred. I've been trying all different things but can't seem to get the Outlets to work.

Here is what I have setup :
http://i.imgur.com/1jUSv.png

I have the left square being an image and the right a NSView; both handle drag and drop without a problem. But I want to say update the button at the top to say something else, or even the NSView on the right the "Label" to change when files are dropped.

Here is the interface :

@interface DragDropView : NSView
{
//highlight the drop zone
BOOL highlight;
__weak NSTextField *lbTest;
}

@property (weak) IBOutlet NSTextField *lbTest;
@end


With the "Label" on the right bound to lbTest.

Then I have the init code like this :

@synthesize lbTest;

- (id) initWithFrame:(NSRect)frameRect
{
NSLog(@"initWithFrame");
self = [super initWithFrame:frameRect];
if (self)
{
NSLog(@"iwfSelf");
[lbTest setStringValue:@"Test"];
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSColorPboardType, NSFilenamesPboardType, nil]];
}
return self;
}



When the code runs, it compiles without warning, the "Label" stays "Label", I would expect it to change to "Test". I never seem to have this problem with any other labels but I often don't do sub-views like this.

Next, I have not been able to figure out ANY way to get the Button to change text except from the main window. I read that there is a way by using properties but I don't see that happening. Any thoughts on this?

Thanks for the help.



Sydde
Jun 24, 2012, 02:03 PM
An IBOutlet is a way for one object (like a controller) to hold a pointer to another object (like a button). The controller can see the button if you make the outlet connection correctly, then it can change the button title appropriately. For instance, the controller is notified of a drop and thus updates the appropriate views, labels, controls and usually the backing data model based on what was dropped.

Try this experiment: put in two buttons, one that says "Yes" and one that says "No" and figure out how to use actions and outlets to cause the buttons to switch titles whenever one of them is pressed.

Odd text I see at the bottom of your post:

Last edited by CylonGlitch : Tomorrow at 37:05 AM.

chown33
Jun 24, 2012, 03:10 PM
Odd text I see at the bottom of your post:

It's part of his/her/its sig.

----------


Then I have the init code like this :

@synthesize lbTest;

- (id) initWithFrame:(NSRect)frameRect
{
NSLog(@"initWithFrame");
self = [super initWithFrame:frameRect];
if (self)
{
NSLog(@"iwfSelf");
[lbTest setStringValue:@"Test"];
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSColorPboardType, NSFilenamesPboardType, nil]];
}
return self;
}



When the code runs, it compiles without warning, the "Label" stays "Label", I would expect it to change to "Test". I never seem to have this problem with any other labels but I often don't do sub-views like this.

Next, I have not been able to figure out ANY way to get the Button to change text except from the main window. I read that there is a way by using properties but I don't see that happening. Any thoughts on this?

Thanks for the help.
You can't refer to the label from an init method. The ivar won't be filled in at that point in time, Connections aren't necessarily established at the point init methods are called. Properties won't solve the problem, either. The problem is timing.

You should look at NSNibAwaking (https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Protocols/NSNibAwaking_Protocol/Reference/Reference.html), and look at the awakeFromNib: method that you will implement or override.

You should also look at some of Apple's sample code that actually uses awakeFromNib:, so you can see it in action.

Sydde
Jun 24, 2012, 03:26 PM
It's part of his/her/its sig.
Geez, how embarrassing :o
You can't refer to the label from an init method... You should look at NSNibAwaking (https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Protocols/NSNibAwaking_Protocol/Reference/Reference.html)...

Good point. You cannot even be certain that the -init method will be called on a nib-instantiated object.

CylonGlitch
Jun 24, 2012, 03:35 PM
It's part of his/her/its sig.
:D (It is good, that will appease my Cylon side)


You should also look at some of Apple's sample code that actually uses awakeFromNib:, so you can see it in action.

I thought the same thing; but then I moved the code to the drop method which does operate correctly (I can see the files dropped on it, and I have it report how many files I dropped via NSLog message). But I can't update the label, nothing seems to happen. :(

CylonGlitch
Jun 24, 2012, 03:50 PM
Update:

I threw a button on the sub-view, and that has no problem changing the label. So now why doesn't the drop function allow me to change the label? I'm guess it because they aren't on the same thread.

Drag code copied from Apple, I get the NSLog and the rest of the code executes right; but the label is not updated.


- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;

NSLog(@"dd: draggingEntered");
[lbTest setStringValue:@"ddE"];
[lbTest setNeedsDisplay:TRUE];

sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];

if ( [[pboard types] containsObject:NSColorPboardType] ) {
if (sourceDragMask & NSDragOperationGeneric) {
return NSDragOperationGeneric;
}
}
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
if (sourceDragMask & NSDragOperationLink) {
return NSDragOperationLink;
} else if (sourceDragMask & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
return NSDragOperationNone;
}


This code for the button in the same class works without issue.

- (IBAction)pbTestClicked:(id)sender {
NSLog(@"Button Clicked");
[lbTest setStringValue:@"Clicked"];
}


Now I guess I need to figured out how to do cross thread updates. . . assuming that is the problem.

chown33
Jun 24, 2012, 04:32 PM
Update:

I threw a button on the sub-view, and that has no problem changing the label. So now why doesn't the drop function allow me to change the label? I'm guess it because they aren't on the same thread.

Drag code copied from Apple, I get the NSLog and the rest of the code executes right; but the label is not updated.


- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;

NSLog(@"dd: draggingEntered");
[lbTest setStringValue:@"ddE"];
[lbTest setNeedsDisplay:TRUE];

sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];

if ( [[pboard types] containsObject:NSColorPboardType] ) {
if (sourceDragMask & NSDragOperationGeneric) {
return NSDragOperationGeneric;
}
}
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
if (sourceDragMask & NSDragOperationLink) {
return NSDragOperationLink;
} else if (sourceDragMask & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
return NSDragOperationNone;
}


Is lbTest nil?

What other dragging methods are implemented? Dragging is a special case (it's modal), so things may not redraw the way you think they should. If you don't implement the drag-ending code, things may not redraw properly.


This code for the button in the same class works without issue.

- (IBAction)pbTestClicked:(id)sender {
NSLog(@"Button Clicked");
[lbTest setStringValue:@"Clicked"];
}


Now I guess I need to figured out how to do cross thread updates. . . assuming that is the problem.

At the time this method is called, it's long past any init method or awakeFromNib. Therefore, it's to be expected that all correctly connected IBOutlets will hold their correct object references. I.e. lbTest will actually hold a reference to an NSLabel object.

Now go back and look at your init method. At the point in time when this method executes, there is nothing yet stored in lbTest, so it will be nil (you should add NSLog code to check this). So telling the variable to set a string is fruitless; the variable is nil, there's no actual NSLabel object yet. Only after awakeFromNib: has been called will there be an NSLabel object stored in lbTest. I encourage you to carefully read the reference doc for NSNibAwaking. Pay close attention to what it says about when IBOutlets have valid values. Extract (emphasis added):
An awakeFromNib message is sent to each object loaded from the archive, but only if it can respond to the message, and only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet instance variables set.


The problem here probably has nothing to do with cross-thread updates. It's probably something to do with lifecycle timing, i.e. at what points in time (init method, awakeFromNib method, etc.) the IBOutlets will or won't have valid values.

You should get into the habit of checking variables for nil, before assuming they'll respond to messages. It's legal to message nil in Objective-C. It pretty much does nothing, though.

CylonGlitch
Jun 24, 2012, 04:53 PM
Maybe I'm not clear on my intent. I don't care about the init routine, and yeah, I had it at the wrong time but that was just for testing. What I really want to do is updated it when I'm done with the drop.



- (void)draggingExited:(id <NSDraggingInfo>)sender
{
NSLog(@"dd: draggingExited\n");
/*------------------------------------------------------
method called whenever a drag exits our drop zone
--------------------------------------------------------*/
highlight = NO;
[self setNeedsDisplay: YES];
}

- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;

NSLog(@"dd: performDragOperation");
[self.lbTest setStringValue:@"Dropped"];

sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];

if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
int numberOfFiles = [files count];
// Perform operation using the list of files
NSLog(@"Number of Files : %u", numberOfFiles);
}
return YES;
}


This code was copied from Apple, the only change I did was the add the line to update the lbTest . . . well for testing purposes. Eventually I'll iterate through the files dropped and perform some actions on them. But some of that performing I want to update labels to indicate what file I'm processing, I want to update a status bar, etc... but I figure once I get one control working, the reset will be easy. Normally I don't have so many problems and my other apps were fine, just running into issues here.

Sydde
Jun 24, 2012, 04:58 PM
Now I guess I need to figured out how to do cross thread updates. . . assuming that is the problem.

Cocoa creates exactly one thread for your application, the mainThread. Any other threads are created by you, with NSThread, NSOperationQueue or pthread_start(). All UI-related activity takes place on mainThread.

You should read Drag and Drop Topics (http://developer.apple.com/documentation/Cocoa/Conceptual/DragandDrop/DragandDrop.html) first n order to understand how you implement NSDraggingProtocol.

chown33
Jun 24, 2012, 05:34 PM
Maybe I'm not clear on my intent...

Your intent is perfectly clear. Which still doesn't answer the question, "Is lbTest nil?".

When a drag is in progress (i.e. before it's dropped), the UI is in a quasi-modal state, where some views or subviews may not be updated in the usual way. I realize you added the setting of lbTest for testing purposes, but that doesn't mean it will work for that purpose.

The methods you've posted are not the ones that have anything to do with the dropped data. Those methods are listed under "Managing a Dragging Session After an Image Is Released" in the NSDraggingDestination (https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Protocols/NSDraggingDestination_Protocol/Reference/Reference.html) reference.

CylonGlitch
Jun 24, 2012, 07:03 PM
Your intent is perfectly clear. Which still doesn't answer the question, "Is lbTest nil?".


Yes. Coming back nil. Yeah, gotta figure out how to reference te item or queue it up so that I can perform a refresh after te drop is complete.

Looking into using Notification Center and see if that will fix the problem.

Sydde
Jun 24, 2012, 11:26 PM
nil means the connection is not being made by the nib loader. Check you xib again, very carefully.

CylonGlitch
Jun 25, 2012, 09:02 AM
nil means the connection is not being made by the nib loader. Check you xib again, very carefully.

But that isn't quite true here. The button press has no problems using the connection to the outlet to update the label. All coming from the same XIB file. The issue is that the drag and drop commands are not running as one would expect and as mentioned earlier, this is how they operate.

chown33
Jun 25, 2012, 12:03 PM
But that isn't quite true here. The button press has no problems using the connection to the outlet to update the label. All coming from the same XIB file. The issue is that the drag and drop commands are not running as one would expect and as mentioned earlier, this is how they operate.
Apply basic debugging:
What do you know? When do you know it? Show your evidence.

What do you know? You know lbTest is nil.

When do you know it? In one method, but not others.

Show your evidence. Add NSLog statements that show lbTest's value in at least the following places: awakeFromNib, your drag-enter code, your drag-ended code, your drag-conclusion code, and your button-press code. Make sure each NSLog's output is clearly labeled. Post that code. Then run the program, and copy and paste the output into a post.

Worst case, zip up the entire test program and attach it to a post.

CylonGlitch
Jun 25, 2012, 08:06 PM
Of course, this is how it happens. Frustrated, I figured I would copy the core code and make as simple as possible to post it here. I just started a blank project and copied over the class for the drag and drop that I was using and setup the bindings just like I did before and, of course, it worked perfectly. :(

So, then I went on and loaded the main application and found something that I hadn't seen before in Interface Builder; it was a green circle under the application called "Shared Interface" or something like that. I deleted it, reconnected the controls and, it works. Not sure what went on there, why that was there and what it means, but I now have it all working.

Thanks guys, this one really confused me. It does look like InterfaceBuilder has been updated since the last time I used it. There is now a new connection wizard that I see popping up with a lot more options.
http://i.imgur.com/qolzU.png

I am wondering if I clicked the connect here one time and that caused something to go amiss.. not sure how to use this wizard, previously it would just give me the choice to make and Outlet or an Action, that I had no problem with. This one I'm unsure of.

Thanks again for your help, it has been very useful. At least I can move forward now. :D