{"id":349,"date":"2023-09-30T07:33:13","date_gmt":"2023-09-30T07:33:13","guid":{"rendered":"https:\/\/fredpayet.fr\/?page_id=349"},"modified":"2023-09-30T07:33:13","modified_gmt":"2023-09-30T07:33:13","slug":"nstableview","status":"publish","type":"page","link":"https:\/\/fredpayet.fr\/index.php\/nstableview\/","title":{"rendered":"NSTableView"},"content":{"rendered":"\n<h2>Basic methods<\/h2>\n<pre class=\"brush: objc; title: IBOutlet definition; notranslate\" title=\"IBOutlet definition\">\r\n@interface PatientList : NSViewController {\r\n   NSMutableArray *patientArray;     \/\/ this array will genuinely contain the data to display\r\n   IBOutlet NSTableView *tableView;  \/\/ this array is a link for IB\r\n}\r\n<\/pre>\n<p>The 2 following methods must be implemented :<\/p>\n<pre class=\"brush: objc; title: numberOfRowsInTableView:; notranslate\" title=\"numberOfRowsInTableView:\">\r\n- (int)numberOfRowsInTableView:(NSTableView *)aTableView\r\n{\r\n   return &#x5B;patientArray count];\r\n}\r\n<\/pre>\n<pre class=\"brush: objc; title: objectValueForTableColumn:; notranslate\" title=\"objectValueForTableColumn:\">\r\n- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn\r\n            row:(int)rowIndex\r\n{\r\n   NSString *identifier = &#x5B;aTableColumn identifier];  \/\/ get the column identifier\r\n   Patient *unPatient = &#x5B;patientArray objectAtIndex:rowIndex];\r\n   return &#x5B;unPatient valueForKey:identifier];\r\n}\r\n<\/pre>\n<p style=\"text-align: justify;\">Optionally, a sort method can be implemented. Under the IB inspector, <strong><em>Table column Attribute <\/em><\/strong>needs &#8220;sortKey&#8221; and &#8220;Selector&#8221; to be set. sortKey is usually equal to the col identifier. Selector can be <strong>compare:<\/strong> or <strong>localizedStandardCompare:<\/strong>. For integer values in an NSTableView, use <strong>localizedStandardCompare:<\/strong>. For NSString, use <strong>compare:<\/strong>.<\/p>\n<pre class=\"brush: objc; title: sortDescriptorsDidChange:; notranslate\" title=\"sortDescriptorsDidChange:\">\r\n- (void)tableView:(NSTableView *)aTableView sortDescriptorsDidChange:(NSArray *)oldDescriptors\r\n{\r\n   NSArray *newDescriptors = &#x5B;aTableView sortDescriptors];\r\n   &#x5B;patientArray sortUsingDescriptors:newDescriptors];\r\n   &#x5B;tableView reloadData];\r\n}\r\n<\/pre>\n<h2>IB connections<\/h2>\n<ol>\n<li>File&#8217;s Owner Class Id must be set to PatientList<\/li>\n<li>connect &#8216;File&#8217;s Owner&#8217; tableView outlet to the NSTableView graphic<\/li>\n<li>connect _NSTableView_ graphic -&gt; datasource to &#8216;File&#8217;s Owner&#8217;<\/li>\n<\/ol>\n<p style=\"text-align: justify;\">Note : if you do not see datasource in the inspector, it&#8217;s certainly because you have selected the <strong>NSScrollView<\/strong> instead. The <strong>NSTableView<\/strong> is inside the <strong>NSScrollView<\/strong>. The column <strong>identifier<\/strong> is used to be able to give the right value from patientArray. This must match the field name of Patient Class. From IB, select an entire column and set identifiers in a simple way, something as follows :<\/p>\n<div class=\"table-responsive\"><table  style=\"width:400px; \"  class=\"easy-table easy-table-default \" >\n<caption>Title and Identifier relationship<\/caption>\n<thead>\r\n<tr><th  style=\"width:200px;text-align:center\" >Title<\/th>\n<th  style=\"width:200px;text-align:center\" >Identifier<\/th>\n<\/tr>\n<\/thead>\n<tbody>\r\n<tr><td  style=\"text-align:center\" >Name<\/td>\n<td  style=\"text-align:center\" >name<\/td>\n<\/tr>\n\r\n<tr><td  style=\"text-align:center\" >FirstName<\/td>\n<td  style=\"text-align:center\" >firstname<\/td>\n<\/tr>\n\r\n<tr><td  style=\"text-align:center\" >Nb Consults<\/td>\n<td  style=\"text-align:center\" >nbConsults<\/td>\n<\/tr>\n<\/tbody><\/table><\/div>\n<h2>Capture row selection change<\/h2>\n<p>This needs a delegate link from NSTableView IB to &#8220;File&#8217;s Owner&#8221;<\/p>\n<pre class=\"brush: objc; title: tableViewSelectionDidChange:; notranslate\" title=\"tableViewSelectionDidChange:\">\r\n- (void)tableViewSelectionDidChange:(NSNotification *)notification\r\n{\r\n   int row = &#x5B;tableView selectedRow];\r\n   if (row == -1) return;\r\n   Patient* aPatient = &#x5B;patientArray objectAtIndex:row];\r\n   NSInteger idp = aPatient.idPatient;\r\n   DLog(@&amp;quot;%d&amp;quot;,idp);\r\n}\r\n<\/pre>\n<h2>Reselect the same row after [tableView reloadata]<\/h2>\n<pre class=\"brush: objc; title: ; notranslate\" title=\"\">\r\nNSInteger row = &#x5B;self.tableView selectedRow];\r\n&#x5B;self.tableView reloadData];\r\n&#x5B;self.tableView selectRowIndexes:&#x5B;NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];\r\n<\/pre>\n<h2>Deselect all rows<\/h2>\n<pre class=\"brush: objc; title: ; notranslate\" title=\"\">\r\n&#x5B;patientTableView deselectAll:self];\r\n<\/pre>\n<h2>Retrieve information from multiple selected rows<\/h2>\n<p>We need to use the following method :<\/p>\n<pre class=\"brush: objc; title: selectedRowIndexes:; notranslate\" title=\"selectedRowIndexes:\">\r\n- (NSIndexSet *)selectedRowIndexes \/\/ Returns an index set containing the indexes of the selected rows.\r\n<\/pre>\n<p>Let&#8217;s say multiple lines have been selected from an <strong>NSTableView<\/strong> : patientTableView. A button has been pushed and the action is <strong>addMember<\/strong>:<\/p>\n<pre class=\"brush: objc; title: addMember:; notranslate\" title=\"addMember:\">\r\n- (IBAction)addMember:(id)sender\r\n{\r\n   NSIndexSet *allrows = &#x5B;patientTableView selectedRowIndexes];\r\n   if (&#x5B;allrows count] == 0) { return; }\r\n\r\n   \/\/ Parse the different rows as follows\r\n   NSUInteger row = &#x5B;allrows firstIndex];\r\n   while (row != NSNotFound)\r\n   {\r\n      \/\/ use the currentIndex\r\n      aPatient = &#x5B;patientArray objectAtIndex:row];\r\n\r\n      \/\/ Do whatever you want with the aPatient instance, here we update a database\r\n      &#x5B;self mysqlAddPatientToGroup:aPatient gid:10];\r\n\r\n      \/\/ step to the next row\r\n      row = &#x5B;allrows indexGreaterThanIndex:row];\r\n   }\r\n}\r\n<\/pre>\n<h2>Multiple NSTableView in an NSView<\/h2>\n<p>We only have some general delegate methods to deal with the different\u00a0NSTableView(s).<br \/>\nSo we need to add some extra code to check with which NStableView instance the delegate method is dealing with.<br \/>\nFor details, see <a title=\"stackoverflow\" href=\"http:\/\/stackoverflow.com\/questions\/12250028\/cocoa-multiple-view-based-nstableviews\" target=\"_blank\" rel=\"noopener\">http:\/\/stackoverflow.com\/questions\/12250028\/cocoa-multiple-view-based-nstableviews<\/a><\/p>\n<pre class=\"brush: objc; title: myClass.h; notranslate\" title=\"myClass.h\">\r\n@interface Comptes : NSViewController {\r\n   NSMutableArray *consultArray;\r\n   NSMutableArray *chargeArray;\r\n   IBOutlet NSTableView *consultTableView;\r\n   IBOutlet NSTableView *chargeTableView;\r\n}\r\n<\/pre>\n<pre class=\"brush: objc; title: myClass.c; notranslate\" title=\"myClass.c\">\r\n\/\/ **************************************************************\r\n\/\/ NSTableView functions\r\n\/\/\r\n\/\/ ==============================================================\r\n- (int)numberOfRowsInTableView:(NSTableView *)aTableView\r\n{\r\n   if (aTableView == consultTableView ) {\r\n      return &#x5B;consultArray count];\r\n   }\r\n   if (aTableView == chargeTableView ) {\r\n      return &#x5B;chargeArray count];\r\n   }\r\n   return 0;\r\n}\r\n\r\n\/\/ We use the column identifier to be able to give the right value from patientArray.\r\n- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn\r\n            row:(int)rowIndex\r\n{\r\n   ConsultExtent *aConsult;\r\n   Charge *aCharge;\r\n\r\n   NSString *identifier = &#x5B;aTableColumn identifier];  \/\/ get the column identifier \r\n\r\n   if (aTableView == consultTableView ) {\r\n      aConsult = &#x5B;consultArray objectAtIndex:rowIndex];\r\n      return &#x5B;aConsult valueForKey:identifier];\r\n   }\r\n   if (aTableView == chargeTableView) {\r\n      aCharge = &#x5B;chargeArray objectAtIndex:rowIndex];\r\n      return &#x5B;aCharge valueForKey:identifier];\r\n   }\r\n\r\n   \/\/ some extra checks\r\n   assert(aTableView == consultTableView || aTableView == chargeTableView );\r\n   return 0;\r\n}\r\n\r\n\/\/ column sort\r\n- (void)tableView:(NSTableView *)aTableView sortDescriptorsDidChange:(NSArray *)oldDescriptors\r\n{\r\n   NSArray *newDescriptors = &#x5B;aTableView sortDescriptors];\r\n\r\n   if (aTableView == consultTableView ) {\r\n      &#x5B;consultArray sortUsingDescriptors:newDescriptors];\r\n      &#x5B;consultTableView reloadData];\r\n      return;\r\n   }\r\n   if (aTableView == chargeTableView ) {\r\n      &#x5B;chargeArray sortUsingDescriptors:newDescriptors];\r\n      &#x5B;chargeTableView reloadData];\r\n      return;\r\n   }\r\n   assert(aTableView == consultTableView || aTableView == chargeTableView );\r\n}\r\n\r\n- (void)tableViewSelectionDidChange:(NSNotification *)notification\r\n{\r\n   NSTableView *aTableView = &#x5B;notification object];\r\n\r\n   if (aTableView == consultTableView ) {\r\n      NSInteger row = &#x5B;consultTableView selectedRow];\r\n      if (row == -1) { return; }\r\n      return;\r\n   }\r\n   if (aTableView == chargeTableView ) {\r\n      NSInteger row = &#x5B;chargeTableView selectedRow];\r\n      if (row == -1) { return; }\r\n         return;\r\n   }\r\n   assert(aTableView == consultTableView || aTableView == chargeTableView );\r\n}\r\n<\/pre>\n<h2>Cells edition<\/h2>\n<p>From the Interface Builder window<\/p>\n<ul>\n<li>select the NSTableView Oject<\/li>\n<li>Right click -&gt; Delegate -&gt; File&#8217;s Owner<\/li>\n<li>Add this method in the code :<\/li>\n<\/ul>\n<pre class=\"brush: objc; title: setObjectValue; notranslate\" title=\"setObjectValue\">\r\n- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject\r\n   forTableColumn:(NSTableColumn *)aTableColumn\r\n              row:(NSInteger)rowIndex\r\n{\r\n   ConsultExtent *uneConsult;\r\n\r\n   \/\/ Check the NSTableView instance in which the cell edition has been triggered\r\n   if (aTableView == consultTableView ) {\r\n      \/\/ Check the column identifier\r\n      if (&#x5B;&#x5B;aTableColumn identifier] isEqualToString:@&quot;invoice&quot;]) {\r\n         \/\/ Assign the typed value to the consult instance\r\n         aConsult = &#x5B;consultArray objectAtIndex:rowIndex];\r\n         aConsult.paiementEncaisse = &#x5B;NSString stringWithFormat:@&quot;%@&quot;,anObject];\r\n         \/\/ modify the NSMutableArray\r\n         &#x5B;consultArray replaceObjectAtIndex:rowIndex withObject:aConsult];\r\n         &#x5B;consultTableView reloadData];\r\n      }\r\n   }\r\n   \/\/ some assertions, to make sure we are not making bad things\r\n   assert(aTableView == consultTableView || aTableView == chargeTableView );\r\n}\r\n<\/pre>\n<h2>Deal with NSTableView cell edition aborted by another event<\/h2>\n<p style=\"text-align: justify;\">This is really something to be dealt precociously. If an <strong>NSButton<\/strong> is pressed while an <strong>NSTableView<\/strong> cell is being edited, this will make empty cells displayed and\/or some rows not in sync with what should be displayed. I did spend some time trying to figure this out. The most elegant solution is to make sure the <strong>NSTextFieldCell<\/strong> is not the <em>firstResponder<\/em> anymore. Actually, we force the end of the cell edition so that we get something clean. In each <strong>IBAction<\/strong> method, we have to call the <strong>makeFirstResponder:<\/strong> method by giving any Graphical object outside the selected <strong>NSTableView<\/strong>. Let&#8217;s say we have an <strong>NSDatePicker<\/strong> object.<\/p>\n<pre class=\"brush: objc; title: program.h; notranslate\" title=\"program.h\">\r\n   program.h\r\n   IBOutlet NSDatePicker        *startDate;\r\n<\/pre>\n<p>The following action method for an NSButton pressure will look like :<\/p>\n<pre class=\"brush: objc; title: program.m; notranslate\" title=\"program.m\">\r\n- (IBAction)currYear:(id)sender\r\n{\r\n   \/\/ Forces the end of the cell edition so that we get something clean.\r\n   &#x5B;&#x5B;startDate window] makeFirstResponder:startDate];\r\n}\r\n<\/pre>\n<h2>How to edit an NSTableView cell with a single-click<\/h2>\n<p>As I have posted onto stackoverflow, I think the most elegant option is to subclass NSTableView as follows :<\/p>\n<pre class=\"brush: objc; title: myNSTableView.h; notranslate\" title=\"myNSTableView.h\">\r\n#import &lt;Cocoa\/Cocoa.h&gt;\r\n@interface myNSTableView : NSTableView {\r\n}\r\n- (void)singleClickEdit: (id)sender;\r\n@end\r\n<\/pre>\n<pre class=\"brush: objc; title: myNSTableView.m; notranslate\" title=\"myNSTableView.m\">\r\n#import &quot;myNSTableView.h&quot;\r\n@implementation myNSTableView\r\n- (void)awakeFromNib\r\n{\r\n   &#x5B;self setAction:@selector(singleClickEdit:)];\r\n}\r\n\r\n- (void)singleClickEdit: (id)sender\r\n{\r\n   NSLog(@&quot;singleClickEdit:&quot;);\r\n   &#x5B;self editColumn:&#x5B;self clickedColumn] row:&#x5B;self clickedRow] withEvent:nil select:NO];\r\n}\r\n\r\n@end\r\n<\/pre>\n<p>Instead of NSTableView, use the myNSTableview class, ie :<\/p>\n<pre class=\"brush: objc; title: Preference.h; notranslate\" title=\"Preference.h\">\r\n#import &lt;Cocoa\/Cocoa.h&gt;\r\n#import &quot;myNSTableView.h&quot;\r\n\r\n@interface Preference : NSPanel {\r\n   NSMutableArray             *chargeArray;\r\n   IBOutlet    myNSTableView  *chargeTableView;\r\n}\r\n<\/pre>\n<p>From IB, select the <strong>NSTableView<\/strong> graphical object which has been put in the <strong>NSView<\/strong>. Select the &#8220;Indentity Inspector&#8221; Tab, and select <strong>myNSTableView<\/strong> instead of <strong>NSTableView<\/strong>.<br \/>\nYou are done.<\/p>\n<h2>Change the background color of a selected row<\/h2>\n<p>We just have to subclass <strong>NSTableRowView<\/strong>. It has methods for drawing the background for selected and deselected rows. To get the NSTableView instance to use your subclass, just implement the tableview delegate method : <strong>tableView:rowViewForRow:<\/strong>, and return an instance of the <strong>NSTableRowView<\/strong> subclass.<\/p>\n<pre class=\"brush: objc; title: MyNSTableRowView.h; notranslate\" title=\"MyNSTableRowView.h\">\r\n   #import &lt;Cocoa\/Cocoa.h&gt;\r\n   @interface MyNSTableRowView : NSTableRowView\r\n   @end\r\n<\/pre>\n<p>&nbsp;<\/p>\n<pre class=\"brush: objc; title: MyNSTableRowView.m; notranslate\" title=\"MyNSTableRowView.m\">\r\n   #import &quot;MyNSTableRowView.h&quot;\r\n\r\n   @implementation MyNSTableRowView\r\n   - (id)init\r\n   {\r\n      if (!(self = &#x5B;super init])) return nil;\r\n      return self;\r\n   }\r\n\r\n   - (void)drawSelectionInRect:(NSRect)dirtyRect {\r\n     if (self.selectionHighlightStyle != NSTableViewSelectionHighlightStyleNone) {\r\n        NSRect selectionRect = NSInsetRect(self.bounds, 2.5, 2.5);\r\n        &#x5B;&#x5B;NSColor colorWithCalibratedWhite:.65 alpha:1.0] setStroke];\r\n        &#x5B;&#x5B;NSColor colorWithCalibratedWhite:.82 alpha:1.0] setFill];\r\n        NSBezierPath *selectionPath = &#x5B;NSBezierPath bezierPathWithRoundedRect:selectionRect\r\n                                                                      xRadius:6 yRadius:6];\r\n        &#x5B;selectionPath fill];\r\n        &#x5B;selectionPath stroke];\r\n     }\r\n   }\r\n   @end\r\n<\/pre>\n<p>From the main delegate class, add this :<\/p>\n<pre class=\"brush: objc; title: Account.m; notranslate\" title=\"Account.m\">\r\n   - (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row\r\n   {\r\n      DLog(@&quot;row =%ld&quot;,row);\r\n      MyNSTableRowView *rowView = &#x5B;&#x5B;MyNSTableRowView alloc]init];\r\n      return rowView;\r\n   }\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Basic methods @interface PatientList : NSViewController { NSMutableArray *patientArray; \/\/ this array will genuinely contain the data to display IBOutlet NSTableView *tableView; \/\/ this array is a link for IB &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-fullwidth.php","meta":{"_themeisle_gutenberg_block_has_review":false,"footnotes":""},"_links":{"self":[{"href":"https:\/\/fredpayet.fr\/index.php\/wp-json\/wp\/v2\/pages\/349"}],"collection":[{"href":"https:\/\/fredpayet.fr\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/fredpayet.fr\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/fredpayet.fr\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fredpayet.fr\/index.php\/wp-json\/wp\/v2\/comments?post=349"}],"version-history":[{"count":1,"href":"https:\/\/fredpayet.fr\/index.php\/wp-json\/wp\/v2\/pages\/349\/revisions"}],"predecessor-version":[{"id":350,"href":"https:\/\/fredpayet.fr\/index.php\/wp-json\/wp\/v2\/pages\/349\/revisions\/350"}],"wp:attachment":[{"href":"https:\/\/fredpayet.fr\/index.php\/wp-json\/wp\/v2\/media?parent=349"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}