Properties of checkboxes inside table views

Hello Scripting Gurus!
After some research and a lot of trial and error I’ve finally got the hang of accessing/changing the state of checkboxes in a table view. Now I’d like to be able to control some of the properties of the checkboxes. Since the content of the checkbox data cell is the true/false state of the checkbox, how do I actually access properties of the checkboxes. What I’m looking for is the ability to change the visibility of checkboxes for specific rows. I’ve scoured the web and available documentation for days and have yet to find an answer. Any help would be appreciated.

-Mike

P.S. I did just find a thread here about this topic and the final decision seemed to be that this was not possible. It was a thread from 1/04 so I’m hoping something has changed in the past two and a half years, or maybe others have more thoughts on the topic.

Hi :slight_smile:

I sought a good moment, the question also interests me, but I did not find anything of conclusive, except a very instructive page of Jonn8 which could certainly give you some ideas: Initialize the “Popup Button Cell” in the “Table View”

Jonn8 successful to even change some propriétées of “Popup Button Cell” (the gray commented lines), but this applies to the whole of the objects of the “Table View”.

For my part, I tried to change propriétées of only one “Popup Button Cell” on a selected row, but I did not succeed.
If you find it, I would be happy to know the method which you would have employed.

Thanks :slight_smile:

Edit: see two messages downwards for a better solution …

Hi Mike,

I don’t believe you can influence a single button cell’s properties from AppleScript. A working solution could be subclassing NSTableColumn - it provides the method:

dataCellForRow:

  • (id)dataCellForRow:(int)row

Returns the NSCell object used by the NSTableView to draw values for the receiver. NSTableView always calls this method. By default, this method just calls dataCell. Subclassers can override if they need to potentially use different cells for different rows. Subclasses should expect this method to be invoked with row equal to “1 in cases where no actual row is involved but the table view needs to get some generic cell info.

I suggest that your subclass should have

  • one method to set an Array of the numbers for all rows which you want to be invisible
    and
  • the method “dataCellForRow:” I mentioned above where you simply compare it’s row value to that Array and return a TextCell instead of the Button cell if it was in the list.

then the only remaining thing to do is to return an empty string “” for this colum/these rows and you’re done.

here some code:

// file: MyInvisibleButtonColumn.h

#import <Cocoa/Cocoa.h>

@interface MyInvisibleButtonColumn : NSTableColumn{
	NSArray *invisibleRows;
}

-(void)setInvisibleRows:(NSArray *)theRows;

@end
// file: MyInvisibleButtonColumn.m

#import "MyInvisibleButtonColumn.h"

@implementation MyInvisibleButtonColumn

- (id) init {
	self = [super init];
	if (self != nil) {
		invisibleRows = [[NSArray alloc] init];
	}
	return self;
}

- (void) dealloc {
	[invisibleRows release];
	[super dealloc];
}

- (id)dataCellForRow:(int)row{
	if (! [invisibleRows containsObject:[NSNumber numberWithInt:++row]]) {
		return ([super dataCellForRow:row]);
	} else {
		NSCell *returnedCell = ([[[NSCell alloc] initTextCell:@""] autorelease]) ;
		return returnedCell;
	}
	return nil;
}

-(void)setInvisibleRows:(NSArray *)theRows{
	[invisibleRows release];
	invisibleRows = [[NSArray alloc] initWithArray:theRows];
}

@end

in your applescript then you can set the invisible rows:


global invisibleRows
 -- ...
set invisibleRows to {1,4,6,8,10}
-- ...
tell table column "col" of table view "tv" of scroll view "sv" of window "main"
	call method "setInvisibleRows:" of it with parameter invisibleRows
end tell

and in “on cell value theObject row theRow table column tableColumn”:


if (name of tableColumn) is "col" then
	if theRow is in invisibleRows then
		return ""
	end
else
-- ...

Btw.: with a little modification this also can be used to create other mixed cell type columns :wink:

Hope that helps,

D.

Hi Dominik :slight_smile: and thank-you for your suggestion…

Personally, I do not know yet how to integrate ObjC methods in an AS-S project, but it seems me to have seen an article which speaks about it.

For my part, I work currently on a project in which I try to enabled or disabled a precise cell on precise row, but I did not find a solution yet.

For visualizing well the situation, here a screenshoot as example of my project:
Table View with a Check Buttons Cell and Popup Buttons Cell Enabled

I would like to be able to enabled/disabled the “Popup Button Cell” of each row according to the state of “Check Button Cell” “Enabled” corresponding…
For the moment, I managed to change the state of all “Popup Buttons Cell” in the “Table View”, but not individually for each row concerned, here a screenshoot for exemple:
Table View with a Check Buttons Cell and Popup Buttons Cell Disabled

Here the code which I use:


set theTableView to table view "tevTest" of scroll view "slvTest" of window "winMain"
set theSelectedDataRow to (selected data row of theTableView)
set theEnabledOk to not (contents of data cell "col01" of theSelectedDataRow) as boolean
set theDataCellForRow to ¬
	call method "dataCellForRow:" of (table column "col02" of theTableView) with parameter (selected row of theTableView)
call method "setEnabled:" of theDataCellForRow with parameter theEnabledOk

Would you have some ideas to be able to do that, i.e., to change the properties of only one cell, without touching the remainder of the row and the other rows?

Thank-you for your help :slight_smile:

Hi Fredo d;o) & Mike,

thinking about Fredo’s problem I found a better solution for Mike’s as well …

back to start - again we subclass NSTableColumn:

Here a detailed description how to:

  1. in Interface Builder you select NSTableColumn in the main window/‘Classes’ tab (-> NSObject → NSTableColumn)
  2. Ctrl-Click or Right-Mousebutton-Click on it and select “Subclass NSTableColumn” from it’s context menu - rename the result to “MyInvisibleButtonColumn” (for example)
  3. select the newly created subclass and select “Create Files for NameOfNewClass
  4. select the TableColumn you want to use the subclass for and change it’s custom class (Inspector → Cusom Class or Apple-5) to the subclass; repeat this step for all columns you want to use this subclas for
  5. switch to Xcode and edit the two subclass files (MyInvisibleButtonColumn.h and MyInvisibleButtonColumn.m in our example)

Here the new subclass files:

// file: MyInvisibleButtonColumn.h

#import <Cocoa/Cocoa.h>

@interface MyInvisibleButtonColumn : NSTableColumn{
	NSArray *invisibleRows;
}

-(void)setInvisibleRows:(NSArray *)theRows;

@end
// file: MyInvisibleButtonColumn.m

#import "MyInvisibleButtonColumn.h"

@implementation MyInvisibleButtonColumn

- (id) init {
	self = [super init];
	if (self != nil) {
		invisibleRows = [[NSArray alloc] init];
	}
	return self;
}

- (void) dealloc {
	[invisibleRows release];
	[super dealloc];
}

- (id)dataCellForRow:(int)row{
	NSButtonCell *returnedCell = [super dataCellForRow:row];
	if ([invisibleRows containsObject:[NSNumber numberWithInt:++row]]) {
		[returnedCell setEnabled:NO];
		[returnedCell setTransparent:YES];	// @Fredo: for just disabling omit this line	
	} else {
		[returnedCell setEnabled:YES];
		[returnedCell setTransparent:NO];	// @Fredo: for just disabling omit this line
	}

	return returnedCell;
}

-(void)setInvisibleRows:(NSArray *)theRows{
	[invisibleRows release];
	invisibleRows = [[NSArray alloc] initWithArray:theRows];
}

@end

Now you just have to set the Array for the lines you want to disable/hide by using a call method (and update the table view if necessary) - for example:


set invisibleRows to {1,3,5,7,9,10}
call method "setInvisibleRows:" of table column "col4" of table view "tv" of scroll view "sv" of window "main" with parameter invisibleRows

D.

Hi Dominik :slight_smile:

Really impressive your knowledge, that work really marvelously, cheer and thank you very much for your invaluable assistance !

Here the proof: Table View with a Check Buttons Cell and Popup Buttons Cell Enabled & Disalbled !!!

A last small question (while hoping not to misuse your patience):

If I understand correctly, the code considers the whole of the rows contained in the “Table View”, then it changes the state of the selected cell of each row according to the numbers existing in the provided list… that is ok?

So, is it possible to adapt the code to be able to modify only one row (the selected row for exemple), without considering all the others?

Sorry to annoy you with that, but I does not know absolutely anything with the programming in ObjC, I cannot nothing to make by myself in this case, I did not even understand how your code works, just to tell you my incompetence.

Thank-you very much Dominik for your help ! :slight_smile:

Hi Dominik!!
Wow! Thanks so much for your time and effort. You’ve given me a lot to study. I’m going to have to make a run to Borders to brush up on my C programming (it’s been many years) and learn a little Objective-C. I was hoping I could stay in the “applescript studio world”. I guess I keep thinking that Apple made it so easy to drag a button cell (or a text field cell, or a popup button cell, …) into a table view that it would be naturally easy to control the properties of these cells and thus greatly expand the capabiltities of table views. Oh well. Time to hit the books. It’ll probably take me a while to digest your posts, but I hope you don’t mind follow up questions. Thanks again.

-Mike

Hi Fredo,

I hope I understood: You need a method to set enabled/disabled for a single row? This could be done like so:
(This time I’ve added some comments to the code - I hope this will help you understand how it works - it isn’t complicated at all:

//-- file: MyInvisibleButtonColumn.h
//-- this file is just for declaration:

#import <Cocoa/Cocoa.h>

@interface MyInvisibleButtonColumn : NSTableColumn{
	NSArray *invisibleRows;
}

-(void)setInvisibleRows:(NSArray *)theRows;
-(void)setDisabledOfRow:(int)theRow to:(BOOL)state; //-- the newly added method to set a sigle row's value

@end

// file: MyInvisibleButtonColumn.m

#import "MyInvisibleButtonColumn.h"

@implementation MyInvisibleButtonColumn

- (id) init { 								//-- initialisation of the TableColumn
	self = [super init]; 						//--  do the regular initialisation stuff
	if (self != nil) { 							//-- if it was successfully initialized,  then:
		invisibleRows = [[NSArray alloc] init];	//-- create the NSArray object we need to hold the rows list
	}
	return self;							//-- return the Table Column
}

- (void) dealloc {							//-- clear memory when the column is no longer needed
	[invisibleRows release];					//-- release the NSArray's memory
	[super dealloc];						//-- do the regular deallocating stuff
}

- (id)dataCellForRow:(int)row{
	NSButtonCell *returnedCell = [super dataCellForRow:row]; //-- get the Cell the Table column regularly crates
	if ([invisibleRows containsObject:			//-- is the row already in our list?
		[NSNumber numberWithInt:++row]]) { 	//-- we need an increment (++) since AS starts numbering with 1, 
										//-- ... but here we get a number starting from zero
		[returnedCell setEnabled:NO];			//-- then disable the row
		[returnedCell setTransparent:YES];		//-- optional: set it to transparent (hide it)
	} else {
		[returnedCell setEnabled:YES];			//-- if not, then set the opposite values
		[returnedCell setTransparent:NO];		
	}

	return returnedCell;
}

-(void)setInvisibleRows:(NSArray *)theRows{		//-- method to set the Array from outside
	[invisibleRows release];					//-- forget the old values
	invisibleRows = [[NSArray alloc] initWithArray:theRows]; //-- and replace it with the new list
}

-(void)setDisabledOfRow:(int)theRow to:(BOOL)state{ //-- method to add/remove a single row in the list
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //-- automated memory management method
	if (state) {								//-- the row shall be disabled (added to the list):
		if (! [invisibleRows containsObject:
			[NSNumber numberWithInt:theRow]]) {	//-- is it really not yet a member of the list? 
											//-- (if not, we simply ignore this call)
			NSArray *tempArray = 				//-- we create a temporary Array with the new row
				[invisibleRows arrayByAddingObject:[NSNumber numberWithInt:theRow]];
			[self setInvisibleRows:tempArray];		//-- we use the above method to replace the old list with this temp list
		}
	} else {									//-- the row shall be enabled (removed from the list):
		if ([invisibleRows containsObject:[NSNumber numberWithInt:theRow]]) {
											//-- is this row already in the list? (ignore, if so ...)
											//-- copy the array to a (mutable) temp array ...
			NSMutableArray *tempArray = [[[NSMutableArray alloc] initWithArray:invisibleRows] autorelease];
											//-- and remove the row:
			[tempArray removeObject:[NSNumber numberWithInt:theRow]];
											//-- replace the old list
			[self setInvisibleRows:[NSArray arrayWithArray:tempArray]];
		}
	}
	[pool release];								//-- free memory (see NSAutoreleasePool declaration above)
}


@end

you call the new method from AppleScript:

call method "setDisabledOfRow:to:" of <.table column ...> with parameters {rowNr, true} -- to disable the row
call method "setDisabledOfRow:to:" of <.table column ...> with parameters {rowNr, false} -- to enable the row

@ Mike & Fredo - don’t hesistate to ask if you got further questions … I’ll try to help if I can …

D.

Edit: corrected a wrong boolean value in the applescript call examples …

Hi Dominik & Mike :slight_smile:

Thousand thank you Dominik for your invaluable assistance.

Your code really functions very well, and it renders a great service to me.

Thank you also to have taken time to comment the code, I can now understand certain things, even if I am completely unable for the moment to modify the least line.

By attentively reading the code, helped by the comments especially, it seems me to have understood that the method “setDisabledOfRow” recomposes a list, according to values of the provided row (number of the row and state to be applied), which is then sent like argument to the method “setInvisibleRows”, which in its turn changes the properties of all the rows according to the provided list.

That is right?

If it is the case, then I suppose that the building of this list of rows can be made directly with the AppleScript code, not?

In any case, it is exactly what I had made to use the method “setInvisibleRows” on the preceding version of the ObjC code, like this:


set theTableView to table view "tevTest" of scroll view "slvTest" of window "winMain"
set theDataSource to data source of theTableView
set theSelectedRow to (selected row of theTableView)
set theEnabledOkList to (contents of data cell "col01" of every data row of theDataSource)
	
set theEnabledRowList to {}
repeat with theNro from 1 to (count theEnabledOkList)
	set theEnabledOk to (contents of item theNro of theEnabledOkList) as boolean
	if theNro is theSelectedRow then set theEnabledOk to not theEnabledOk
	if not theEnabledOk then set end of theEnabledRowList to theNro
end repeat

Now, I think that the ObjC code works much more quickly than the same AS code, and than there should not be important decelerations most of the time.

But, I have a question however:

If the “Table View” contains a very great quantity of data, can we fear a deceleration of the operation?
Or you think that it is really negligible?

In all the cases, your current code is really perfect for the use which I want to make in my current project, thank-you again.

However, if ever you find a little time, and that you think that it is realizable, could you to study the possibility of conceiving a direct method, without passing by the building of a list, and without to work on all the rows, I think that could be useful for more ambitious projects.

I really learned much from interesting things in this topic… Thanks so much… :slight_smile:

Hallo Fredo,

yes - you got everything correct - the Obj-C method should be much faster than an AppleScript equivalent so the deceleration should be neglectable in most cases …

Directly accessing the cells? Hmm … let’s talk about some table view theory first (my knowledge about this subject is far from perfect - forgive me, if I made mistakes in the following description …):

A table column is not a fixed thing - it’s more or less created on the fly. So there is not a permanently existing cell object for every row which could be accessed like an NSButton for example - there is only some information/properties given how the cells should look like and the column creates them using this information whenever it needs one. The only point (I know of) to influence this process is the method: - (id)dataCellForRow:(int)row. That’s what we’ve already used:
when processing it’s display the column permanently asks this method to return NSCell objects - one for every row.
This is the chance for taking influence - in a subclass. We use an if/else and return the default Cell object in one case ([super dataCellForRow:row]) and an other object or a modified version of the default cell in the other.

So … to really have access to single cell objects (this might be useful for creating columns with lots of different cell types for example) it could be possible to build an array holding these NSCells - one for every row. The dataCellForRow method returns the matching cell object from this array and the default cells normally created are totally bypassed. Then it was possible to create methods for modifying every single row’s cell.
OR - we could use an array holding all necessary information and properties for the different cell objects we want in our table (in an NSDictionary).

But … this will also mean:

  • that this method will need some extra memory - maybe a lot of memory, when we’re talking of huge tables
  • that you will have to take care, that every modification (deletion/insertion) of the table’s rows will also be made to this array
  • that this sublass will be a little more complicated than the above :wink:

Is this what you got in mind?

D.

Hi Dominik :slight_smile:

Thank-you again for your availability.

Your knowledge is much richer than mine, in any case, it has nothing there to forgive you, on the contrary.

Very very interesting the “table view” and “table column” work’s mechanism…
I understand better now why it is not so simple to handle.

In any case, your code works perfectly well, I could test it with a great quantity of data, and I did not notice a significant deceleration. Cheer! :wink:

Thanks again Dominik…

hi everyone,

i’m looking to find a way to automatically uncheck all the check boxes after i hit a point in my script, i have a list of aprox 200 items and rather than rebuilding the same list i want to uncheck the boxes that were checked, i currently have a seperate list that holds all the check box values either true or false, so i know which ones need to be unchecked…or is there i way i could just rebuild the 1st column and leave the rest intact

i have the code to wipe the entire table, i was wondering if someone could help me refine this to be more efficient…

delete every data row of --data source of table view _____ of scroll view _____ of theObject

thanks,

matt