Page 1 of 1

ListIconGadget with > 1 million items

Posted: Sat Mar 19, 2016 7:10 pm
by hesitate
I am porting an app from Windows to Mac OS. The program loads > 1 million items in a ListIconGadget sometimes. There are filters and the program also supports sorting. It works damn well (fast) on Windows with a virtual ListIconGadget.

Now, I'm porting it to Mac OS. I've started reading the Obj-C docs/AppKit/NSTableView and have already ported some auxiliary functions with CocoaMessage.

However, I can't figure out what's the optimal way to use the NSTableView++ for the task. (Load 1 mil of items, filter should immediately update the view..)

Any pointers on optimal(speed) implementation? Or a snippet demonstrating it with bogus data?

All cells are plain text.

Thank you.

Re: ListIconGadget with > 1 million items

Posted: Sat Mar 19, 2016 7:19 pm
by wilbert
The most optimal approach is to create your own class that conforms to the NSTableViewDataSource protocol and use an instance of that class as data source.

Re: ListIconGadget with > 1 million items

Posted: Sun Mar 20, 2016 1:36 pm
by hesitate
Thanks for the pointer.

I subclassed NSObject, added the 2 needed methods, instantiated it, set the datasource of the NSTableView and it started crashing (invalid memory access on EndProcedure or ProcedureReturn). The methods set bogus data, so they're not buggy.

I can call the 2 methods with CocoaMessage but when the NSTableView calls them, they crash.

I fixed it (don't know if it's a fix) by setting the delegate of NSTableView to nil, before setting its data source. The preset delegate (maybe set by Purebasic runtime?) doesn't work with my class. Now, it seems to be working fast.

Anyone had the same issue? I hope I don't introduce other problems by nilling the NSTableView delegate.

Re: ListIconGadget with > 1 million items

Posted: Sun Mar 20, 2016 2:26 pm
by wilbert
hesitate wrote:Anyone had the same issue? I hope I don't introduce other problems by nilling the NSTableView delegate.
I think maybe no one tried it before.

If you want to be sure, you can create the table view yourself with NSTableView alloc / initWithFrame instead of using the PB gadget.
When you do that, you can add it to the contentView of the window or create a PB ContainerGadget and use it with that (setContentView).
The main problem with PB gadgets in this case is that you don't know what methods PB itself subclasses and if it will stay the same for future versions.

Re: ListIconGadget with > 1 million items

Posted: Sun Mar 20, 2016 9:11 pm
by Danilo
hesitate wrote:(invalid memory access on EndProcedure or ProcedureReturn).
Did you use Procedure or ProcedureC?

Re: ListIconGadget with > 1 million items

Posted: Mon Mar 21, 2016 7:47 am
by wilbert
Danilo wrote:
hesitate wrote:(invalid memory access on EndProcedure or ProcedureReturn).
Did you use Procedure or ProcedureC?
I'm having the same problems when I try.
Also, clicking an item seems to work but walking the list not.
It seems you have to come up with your own delegate as well to make it work

Here's my attempt, a list of two million random numbers.
Clicking the second column will sort the data.

Code tested with PB 5.42

Code: Select all

; create array with two million 32 bit values
Global Dim MyArray.l(1999999)

; fill array with random numbers
RandomData(@MyArray(), 2000000 << 2)



; dataSource number of rows
ProcedureC.i numberOfRows(obj, sel, tableView)
  ProcedureReturn ArraySize(MyArray()) + 1
EndProcedure

; dataSource get value
ProcedureC.i getTableValue(obj, sel, tableView, tableColumn, rowIndex)
  Protected.i NSNumber, Columns
  Protected.i Column0, Column1
  
  Columns = CocoaMessage(0, tableView, "tableColumns")
  Column0 = CocoaMessage(0, Columns, "objectAtIndex:", 0)
  Column1 = CocoaMessage(0, Columns, "objectAtIndex:", 1)
  
  Select tableColumn
    Case Column0
      NSNumber = CocoaMessage(0, 0, "NSNumber numberWithInteger:", rowIndex)
    Case Column1
      NSNumber = CocoaMessage(0, 0, "NSNumber numberWithLong:", MyArray(rowIndex))
  EndSelect
  
  ProcedureReturn CocoaMessage(0, NSNumber, "stringValue")
EndProcedure

; dataSource set value (not implemented because we want read-only)
ProcedureC setTableValue(obj, sel, tableView, object, tableColumn, rowIndex)
EndProcedure

; delegate selection change
ProcedureC selectionChange(obj, sel, notification)
  PostEvent(#PB_Event_Gadget, 0, 0, #PB_EventType_Change)    
EndProcedure

; delegate column click
ProcedureC columnClick(obj, sel, tableView, tableColumn)
  Protected.i Columns
  Protected.i Column0, Column1
  
  Columns = CocoaMessage(0, tableView, "tableColumns")
  Column0 = CocoaMessage(0, Columns, "objectAtIndex:", 0)
  Column1 = CocoaMessage(0, Columns, "objectAtIndex:", 1)
  
  Select tableColumn
    Case Column1
      SortArray(MyArray(), #PB_Sort_Ascending)
      CocoaMessage(0, tableView, "reloadData")
  EndSelect
  
EndProcedure



; create classes
class = objc_allocateClassPair_(objc_getClass_("NSObject"), "MyTableDataSourceClass", 0)
class_addMethod_(class, sel_registerName_("numberOfRowsInTableView:"), @numberOfRows(), "v@:@")
class_addMethod_(class, sel_registerName_("tableView:objectValueForTableColumn:row:"), @getTableValue(), "@@:@@i")
class_addMethod_(class, sel_registerName_("tableView:setObjectValue:forTableColumn:row:"), @setTableValue(), "v@:@@@i")
objc_registerClassPair_(class)

class = objc_allocateClassPair_(objc_getClass_("NSObject"), "MyTableDelegateClass", 0)
class_addMethod_(class, sel_registerName_("tableViewSelectionDidChange:"), @selectionChange(), "v@:@")
class_addMethod_(class, sel_registerName_("tableView:didClickTableColumn:"), @columnClick(), "v@:@@")

objc_registerClassPair_(class)



; ListIconGadget callback
Procedure ListIconGadgetCB()
  If GetGadgetState(0) >= 0
    SetGadgetText(1, Str(MyArray(GetGadgetState(0))))
  EndIf  
EndProcedure



; test
OpenWindow(0, 0, 0, 400, 340, "ListIconGadget demo", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ListIconGadget(0, 10, 10, 380, 280, "Column 1", 150)
AddGadgetColumn(0, 1, "Column 2", 150)
StringGadget(1, 10, 300, 380, 30, "", #PB_String_ReadOnly)

DataSource = CocoaMessage(0, 0, "MyTableDataSourceClass new")
Delegate = CocoaMessage(0, 0, "MyTableDelegateClass new")
CocoaMessage(0, GadgetID(0), "setDataSource:", DataSource)
CocoaMessage(0, GadgetID(0), "setDelegate:", Delegate)

BindGadgetEvent(0, @ListIconGadgetCB(), #PB_EventType_Change)

Repeat 
  Event = WaitWindowEvent()
Until Event  = #PB_Event_CloseWindow

Re: ListIconGadget with > 1 million items

Posted: Wed Mar 23, 2016 11:16 am
by hesitate
Danilo,
I use ProcedureC.

wilbert,
it works both with a nilled delegate and NSApplication sharedApplication delegate that I needed for columnclicks/sorting.