Xojo Conferences
XDCMay2019MiamiUSA

Platforms to show: All Mac Windows Linux Cross-Platform

/MacControls/Listbox and TableView Demos/ListboxTV drop-in/Hierarchical & Flat/ListBoxTV OutlineView
Function:
You find this example project in your Plugins Download as a Xojo project file within the examples folder: /MacControls/Listbox and TableView Demos/ListboxTV drop-in/Hierarchical & Flat/ListBoxTV OutlineView
This example is the version from Tue, 20th Aug 2018.
Project "ListBoxTV OutlineView.rbp"
Class App Inherits Application
Const kEditClear = "&Delete"
Const kFileQuit = "&Quit"
Const kFileQuitShortcut = ""
EventHandler Sub Open() LogWin.Left = ListboxDemoWin.Left + ListboxDemoWin.Width + 30 LogWin.Top = ListboxDemoWin.Top ListboxDemoWin.Show End EventHandler
End Class
MenuBar MenuBar1
MenuItem FileMenu = "&File"
MenuItem FileQuit = "#App.kFileQuit"
MenuItem EditMenu = "&Edit"
MenuItem EditUndo = "&Undo"
MenuItem UntitledMenu1 = "-"
MenuItem EditCut = "Cu&t"
MenuItem EditCopy = "&Copy"
MenuItem EditPaste = "&Paste"
MenuItem EditClear = "#App.kEditClear"
MenuItem UntitledMenu0 = "-"
MenuItem EditSelectAll = "Select &All"
End MenuBar
Class ListboxDemoWin Inherits Window
Const StaticListCount = 16
Control cocoaListbox Inherits ListBoxTV
ControlInstance cocoaListbox Inherits ListBoxTV
EventHandler Sub CellAction(row as Integer, column as Integer) log "cocoa.CellAction ("+Str(row)+", "+Str(column)+")" End EventHandler
EventHandler Function CellClick(row as Integer, column as Integer, x as Integer, y as Integer) As Boolean log "cocoa.CellClick ("+Str(row)+", "+Str(column)+", "+Str(x)+", "+Str(y)+"), isCMM: "+Str(IsContextualClick)+")" return mCellClickResult End EventHandler
EventHandler Sub Change() 'log "Change: "+Str(me.ListIndex) + " (" + Str(me.SelCount) + ")" End EventHandler
EventHandler Function CompareRows(cell1 as ListCellTV, cell2 as ListCellTV, row1 as Integer, row2 as Integer, column as Integer, ByRef result as Integer) As Boolean if column < 2 then // default sorting for first two columns return false else // custom sorting for 3rd and last col if column = 2 then // Use "smart" sorting, which identifies numbers, so that "10" comes after "2" const NSCaseInsensitiveSearch = 1 const NSLiteralSearch = 2 const NSBackwardsSearch = 4 const NSAnchoredSearch = 8 const NSNumericSearch = 64 const NSDiacriticInsensitiveSearch = 128 const NSWidthInsensitiveSearch = 256 const NSForcedOrderingSearch = 512 static options as Integer = NSCaseInsensitiveSearch or NSNumericSearch or NSDiacriticInsensitiveSearch result = NSStringCompareMBS (cell1.Text, cell2.Text, options) else // Use "dumb" String sorting ( "10" comes before "2") result = StrComp (me.Cell(row1, column), me.Cell(row2, column), 1) end if return true end if End EventHandler
EventHandler Function ConstructContextualMenu(base as MenuItem, x as Integer, y as Integer, clickedRow as Integer, clickedColumn as Integer, parentMenu as NSMenuMBS) As Boolean base.Append new MenuItem ("Item 1", 1) base.Append new MenuItem ("Item 2", 2) parentMenu.allowsContextMenuPlugIns = true ' enables System Services return true End EventHandler
EventHandler Function ConstructContextualMenuForHeader(base as MenuItem, x as Integer, y as Integer, clickedColumn as Integer, parentMenu as NSMenuMBS) As Boolean base.Append new MenuItem ("Header 1", 1) base.Append new MenuItem ("Header 2", 2) return true End EventHandler
EventHandler Function ContextualMenuAction(hitItem as MenuItem, clickedRow as Integer, clickedColumn as Integer) As Boolean if clickedRow < 0 then MsgBox "You chose "+hitItem.Text+" on column "+Str(clickedColumn) else dim allSel() as String for i as Integer = 0 to me.ListCount-1 if me.Selected (i) then allSel.Append Str(i) end if next MsgBox "You chose "+hitItem.Text+" on these rows:"+EndOfLine+Join(allSel, ", ") end if End EventHandler
EventHandler Function DenyReorderColumn(fromColumn as Integer, toColumn as Integer) As Boolean return fromColumn = 0 or toColumn = 0 ' this ensures that the first columns always remains the first (leftmost) End EventHandler
EventHandler Function DragRow(drag as DragItemTV, row as Integer, ByRef operationMask as Integer, ByRef thisAppOnly as Boolean) As Boolean // Add all selected rows as items to the drag pasteboard dim n as Integer = me.SelCount while n > 0 and row < me.ListCount if me.Selected(row) then drag.Text = "Row " + Str(row) + ": " + me.List(row) drag.RawData("Test") = Str(row) drag.FolderItem = App.ExecutableFile.Parent.Parent.Parent ' just some file n = n - 1 if n = 0 then exit drag.AddItem(0,0,20,4) end if row = row + 1 wend return true End EventHandler
EventHandler Sub DropObject(obj as DragItemTV, operationMask as Integer, aboveRow as Integer, draggingInfo as NSDraggingInfoMBS) dim s() as String do if obj.TextAvailable then s.Append "Text: "+obj.Text end if obj.FolderItemAvailable then s.Append "FolderItem: "+obj.FolderItem.AbsolutePath end loop until not obj.NextItem if s.Ubound < 0 then MsgBox "Got something unknown dropped." else MsgBox "Received drop"+EndOfLine+EndOfLine+Join(s, EndOfLine) end if End EventHandler
EventHandler Sub EnableMenuItems() log "cocoa.EnableMenuItems" End EventHandler
EventHandler Sub ExpandRow(row as Integer) dim elems() as String = Pair(me.RowTag(row)).Right dim depth as Integer = Pair(me.RowTag(row)).Left dim n as Integer, expand as Boolean for each s as String in elems n = n + 1 if n = 2 and depth <= 3 then me.AddFolder s me.RowTag (me.LastIndex) = depth + 1 : elems if depth <= 2 then expand = true end if else me.AddRow s end if me.Cell (me.LastIndex, 1) = Str (me.LastIndex) if expand then me.Expanded(me.LastIndex) = true end if next End EventHandler
EventHandler Function MouseDown(x as Integer, y as Integer) As Boolean log "MouseDown ("+Str(x) + ", " + Str(y) + ")" End EventHandler
EventHandler Sub Open() for row as integer = 0 to StaticListCount-1 if row mod 4 = 2 then me.AddFolder "f "+Format(row, "#") me.RowTag (me.LastIndex) = 1 : Array (Str(row)+"a", Str(row)+"b", Str(row)+"c") else me.AddRow Format(row, "#.0") end if for col as Integer = 1 to me.ColumnCount-1 if col = 1 or col = 2 then // 2nd and 3rd columns get random values for sort testing me.Cell (me.LastIndex, col) = Chr(col+65)+Str(Rnd*30,"#") else me.Cell (me.LastIndex, col) = Chr(col+65)+Str(row) end if next if row mod 12 = 10 then me.Expanded(me.LastIndex) = true end if next for row as Integer = 1 to Min (12, StaticListCount-1) me.CellAlignmentOffset (row, 1) = row * 4 next me.CellAlignment (1, 0) = ListBox.AlignLeft me.CellAlignment (2, 0) = ListBox.AlignCenter me.CellAlignment (3, 0) = ListBox.AlignRight me.CellAlignment (4, 0) = ListBox.AlignDecimal me.Cell (1, 0) = "AlignLeft" me.Cell (2, 0) = "AlignCenter" me.Cell (3, 0) = "AlignRight" me.Cell (4, 0) = "AlignDecimal" me.ColumnAlignment (2) = ListBox.AlignCenter me.Column(0).MinWidthActual = 40 me.Column(1).MinWidthActual = 60 me.Column(2).MinWidthActual = 30 me.Column(4).MinWidthActual = 10 me.ColumnType(0) = ListBox.TypeEditable me.CellType(1,1) = ListBox.TypeCheckbox me.CellType(1,2) = ListBox.TypeCheckbox me.CellType(2,1) = ListBox.TypeCheckbox me.CellType(2,2) = ListBox.TypeCheckbox me.CellType(3,1) = ListBox.TypeEditable me.CellType(4,1) = ListBox.TypeEditable me.Cell(1,1) = me.Cell(1,1) + " TypeCheckbox" me.Cell(1,2) = me.Cell(1,2) + " TypeCheckbox" me.Cell(2,1) = me.Cell(2,1) + " TypeCheckbox" me.Cell(2,2) = me.Cell(2,2) + " TypeCheckbox" me.Cell(3,1) = me.Cell(3,1) + " TypeEditable" me.Cell(4,1) = me.Cell(4,1) + " TypeEditable" me.RowPicture(5) = mSampleImage me.CellType(5,0) = ListBox.TypeEditable me.Cell(5,0) = "5 - RowPicture, TypeEditable" for row as Integer = 2 to 4 me.CellTag(row, 1) = &cFFFE2500 : &cF4004500 ' let's use the CellTag to specify their background and text colors me.Cell(row,1) = me.Cell(row,1) + " BgColor TextColor" next me.AcceptRawDataDrop ("Test") // // Custom settings that are not available in ListBox // for col as Integer = 0 to me.ColumnCount-1 me.Column(col).SetFontAndSize "System", 0 next for row as Integer = 2 to 4 me.CellStyle(row,1).BackgroundColor = &cFFFE2500 me.CellStyle(row,1).TextColor = &cF4004500 next me.TableView.usesAlternatingRowBackgroundColors = true me.TableView.allowsTypeSelect = true me.Reload ' this loads the listbox immediately - otherwise there may be a little delay (due to the Timer in ListBoxTV._needsReload) me.Expanded(2) = true me.Expanded(9) = true End EventHandler
EventHandler Function SortColumn(column as Integer) As Boolean log "cocoa.SortColumn "+Str(column) End EventHandler
EventHandler Function WillDisplayCell(row as Integer, column as Integer, cell as ListCellTV, textFieldCell as NSCellMBS, tableColumn as NSTableColumnMBS) As Boolean // The last column shall always show the current row if column = me.ColumnCount-1 then me.Cell(row, column) = "row " + Str(row) end if End EventHandler
End Control
Control xojoListbox Inherits Listbox
ControlInstance xojoListbox Inherits Listbox
EventHandler Sub CellAction(row As Integer, column As Integer) log "xojo.CellAction ("+Str(row)+", "+Str(column)+")" End EventHandler
EventHandler Function CellBackgroundPaint(g As Graphics, row As Integer, column As Integer) As Boolean if row >= me.ListCount then return false // The last column shall always show the current row if column = me.ColumnCount-1 then me.Cell(row, column) = "row " + Str(row) end if dim p as Pair = me.CellTag (row, column) if p <> nil then g.ForeColor = p.Left g.FillRect 0, 0, g.Width, g.Height return true end if End EventHandler
EventHandler Function CellClick(row as Integer, column as Integer, x as Integer, y as Integer) As Boolean log "xojo.CellClick ("+Str(row)+", "+Str(column)+", "+Str(x)+", "+Str(y)+"), isCMM: "+Str(IsContextualClick) return mCellClickResult End EventHandler
EventHandler Function CellTextPaint(g As Graphics, row As Integer, column As Integer, x as Integer, y as Integer) As Boolean dim p as Pair = me.CellTag (row, column) if p <> nil then g.ForeColor = p.Right g.DrawString me.Cell(row, column), 10, 13 return true end if End EventHandler
EventHandler Sub Change() 'log "Change: "+Str(me.ListIndex) + " (" + Str(me.SelCount) + ")" End EventHandler
EventHandler Function CompareRows(row1 as Integer, row2 as Integer, column as Integer, ByRef result as Integer) As Boolean if column < 2 then // default sorting for first two columns return false else // custom sorting for 3rd and last col if column = 2 then // Use "smart" sorting, which identifies numbers, so that "10" comes after "2" const NSCaseInsensitiveSearch = 1 const NSLiteralSearch = 2 const NSBackwardsSearch = 4 const NSAnchoredSearch = 8 const NSNumericSearch = 64 const NSDiacriticInsensitiveSearch = 128 const NSWidthInsensitiveSearch = 256 const NSForcedOrderingSearch = 512 static options as Integer = NSCaseInsensitiveSearch or NSNumericSearch or NSDiacriticInsensitiveSearch result = NSStringCompareMBS (me.Cell(row1, column), me.Cell(row2, column), options) else // Use "dumb" String sorting ( "10" comes before "2") result = StrComp (me.Cell(row1, column), me.Cell(row2, column), 1) end if return true end if End EventHandler
EventHandler Function ConstructContextualMenu(base as MenuItem, x as Integer, y as Integer) As Boolean base.Append new MenuItem ("Item 1", 1) base.Append new MenuItem ("Item 2", 2) return true End EventHandler
EventHandler Function ContextualMenuAction(hitItem as MenuItem) As Boolean dim allSel() as String for i as Integer = 0 to me.ListCount-1 if me.Selected (i) then allSel.Append Str(i) end if next MsgBox "You chose "+hitItem.Text+" on these rows:"+EndOfLine+Join(allSel, ", ") return true End EventHandler
EventHandler Function DragOver(x As Integer, y As Integer, obj As DragItem, action As Integer) As Boolean beep End EventHandler
EventHandler Function DragRow(drag As DragItem, row As Integer) As Boolean // Add all selected rows as items to the drag pasteboard dim n as Integer = me.SelCount while n > 0 and row < me.ListCount if me.Selected(row) then drag.Text = "Row " + Str(row) + ": " + me.List(row) drag.RawData("Test") = Str(row) drag.FolderItem = App.ExecutableFile.Parent.Parent.Parent ' just some file n = n - 1 if n = 0 then exit drag.AddItem(0,0,20,4) end if row = row + 1 wend return true End EventHandler
EventHandler Sub DropObject(obj As DragItem, action As Integer) dim s() as String do if obj.TextAvailable then s.Append "Text: "+obj.Text end if obj.FolderItemAvailable then s.Append "FolderItem: "+obj.FolderItem.AbsolutePath end loop until not obj.NextItem if s.Ubound < 0 then MsgBox "Got something unknown dropped." else MsgBox "Received drop"+EndOfLine+EndOfLine+Join(s, EndOfLine) end if End EventHandler
EventHandler Sub EnableMenuItems() log "xojo.EnableMenuItems" End EventHandler
EventHandler Sub ExpandRow(row As Integer) dim elems() as String = Pair(me.RowTag(row)).Right dim depth as Integer = Pair(me.RowTag(row)).Left dim n as Integer, expand as Boolean for each s as String in elems n = n + 1 if n = 2 and depth <= 3 then me.AddFolder s me.RowTag (me.LastIndex) = depth + 1 : elems if depth <= 2 then expand = true end if else me.AddRow s end if me.Cell (me.LastIndex, 1) = Str (me.LastIndex) if expand then me.Expanded(me.LastIndex) = true end if next End EventHandler
EventHandler Function MouseDown(x As Integer, y As Integer) As Boolean log "MouseDown ("+Str(x) + ", " + Str(y) + ")" End EventHandler
EventHandler Sub Open() for row as integer = 0 to StaticListCount-1 if row mod 4 = 2 then me.AddFolder "f "+Format(row, "#") me.RowTag (me.LastIndex) = 1 : Array (Str(row)+"a", Str(row)+"b", Str(row)+"c") else me.AddRow Format(row, "#.0") end if for col as Integer = 1 to me.ColumnCount-1 if col = 1 or col = 2 then // 2nd and 3rd columns get random values for sort testing me.Cell (me.LastIndex, col) = Chr(col+65)+Str(Rnd*30,"#") else me.Cell (me.LastIndex, col) = Chr(col+65)+Str(row) end if next if row mod 12 = 10 then me.Expanded(me.LastIndex) = true end if next for row as Integer = 1 to Min (12, StaticListCount-1) me.CellAlignmentOffset (row, 1) = row * 4 next me.CellAlignment (1, 0) = ListBox.AlignLeft me.CellAlignment (2, 0) = ListBox.AlignCenter me.CellAlignment (3, 0) = ListBox.AlignRight me.CellAlignment (4, 0) = ListBox.AlignDecimal me.Cell (1, 0) = "AlignLeft" me.Cell (2, 0) = "AlignCenter" me.Cell (3, 0) = "AlignRight" me.Cell (4, 0) = "AlignDecimal" me.ColumnAlignment (2) = ListBox.AlignCenter me.Column(0).MinWidthActual = 40 me.Column(1).MinWidthActual = 60 me.Column(2).MinWidthActual = 30 me.Column(4).MinWidthActual = 10 me.ColumnType(0) = ListBox.TypeEditable me.CellType(1,1) = ListBox.TypeCheckbox me.CellType(1,2) = ListBox.TypeCheckbox me.CellType(2,1) = ListBox.TypeCheckbox me.CellType(2,2) = ListBox.TypeCheckbox me.CellType(3,1) = ListBox.TypeEditable me.CellType(4,1) = ListBox.TypeEditable me.Cell(1,1) = me.Cell(1,1) + " TypeCheckbox" me.Cell(1,2) = me.Cell(1,2) + " TypeCheckbox" me.Cell(2,1) = me.Cell(2,1) + " TypeCheckbox" me.Cell(2,2) = me.Cell(2,2) + " TypeCheckbox" me.Cell(3,1) = me.Cell(3,1) + " TypeEditable" me.Cell(4,1) = me.Cell(4,1) + " TypeEditable" me.RowPicture(5) = mSampleImage me.CellType(5,0) = ListBox.TypeEditable me.Cell(5,0) = "5 - RowPicture, TypeEditable" for row as Integer = 2 to 4 me.CellTag(row, 1) = &cFFFE2500 : &cF4004500 ' let's use the CellTag to specify their background and text colors me.Cell(row,1) = me.Cell(row,1) + " BgColor TextColor" next me.AcceptRawDataDrop ("Test") // -------- me.Expanded(2) = true me.Expanded(9) = true End EventHandler
EventHandler Function SortColumn(column As Integer) As Boolean log "xojo.SortColumn "+Str(column) End EventHandler
End Control
Control Timer1 Inherits Timer
ControlInstance Timer1 Inherits Timer
EventHandler Sub Action() 'if Keyboard.AsyncShiftKey then break ' this is for breaking into the debugger, for setting breakpoints (as it doesn't work reliably with the IDE's Pause button) dim txt as String 'dim o as Variant = self.Focus 'if o = nil then 'txt = "No focus" 'elseif o = xojoListbox then 'txt = "Xojo ListBox has focus" 'elseif o = cocoaListbox then 'txt = "MBS TableView has focus" 'else 'txt = "Unknown focus" 'end if 'txt = txt + _ '", w: " + Str(cocoaListbox.TableView.bounds.Width,"#") + _ '" / " + Str (cocoaListbox.ScrollView.contentSize.Width) + _ '" / " + Str (cocoaListbox.ScrollView.visibleRect.Width) // show current row/col dim row, col, x, y as Integer if self.MouseY >= xojoListbox.Top and self.MouseY < xojoListbox.Top + xojoListbox.Height then x = self.MouseX - xojoListbox.Left y = self.MouseY - xojoListbox.Top row = xojoListbox.RowFromXY (x,y) col = xojoListbox.ColumnFromXY (x,y) txt = "In row "+Str(row)+" col "+Str(col) elseif self.MouseY >= cocoaListbox.Top and self.MouseY < cocoaListbox.Top + cocoaListbox.Height then x = self.MouseX - cocoaListbox.Left y = self.MouseY - cocoaListbox.Top row = cocoaListbox.RowFromXY (x,y) col = cocoaListbox.ColumnFromXY (x,y) txt = "In row "+Str(row)+" col "+Str(col) end if // show column widths dim s1(), s2() as String, tw as Double for i as Integer = 0 to cocoaListbox.ColumnCount-1 s1.Append cocoaListbox.Column(i).WidthExpression dim w as Double = cocoaListbox.Column(i).WidthActual s2.Append Str(w) tw = tw + w next dim lbw as Double = cocoaListbox.ScrollView.frameWidth - cocoaListbox.ScrollView.verticalScroller.frame.Width - cocoaListbox.ColumnCount * cocoaListbox.TableView.intercellSpacing.width txt = txt + " [" + Join (s1, ", ") + "] / [" + Join (s2, ", ") + "] = " + Str(tw) + " (" + Str (lbw) + ")" Label1.Text = txt.Trim End EventHandler
End Control
Control Label1 Inherits Label
ControlInstance Label1 Inherits Label
End Control
Control PushButton1 Inherits PushButton
ControlInstance PushButton1 Inherits PushButton
EventHandler Sub Action() for row as Integer = 0 to cocoaListbox.ListCount-1 cocoaListbox.Selected(row) = not cocoaListbox.Selected(row) next for row as Integer = 0 to xojoListbox.ListCount-1 xojoListbox.Selected(row) = not xojoListbox.Selected(row) next End EventHandler
End Control
Control PushButton3 Inherits PushButton
ControlInstance PushButton3 Inherits PushButton
EventHandler Sub Action() cocoaListbox.RecalculateColumnWidths End EventHandler
End Control
Control PushButton5 Inherits PushButton
ControlInstance PushButton5 Inherits PushButton
EventHandler Sub Action() cocoaListbox.Sort xojoListbox.Sort End EventHandler
End Control
Control CheckBox1 Inherits CheckBox
ControlInstance CheckBox1 Inherits CheckBox
EventHandler Sub Action() mCellClickResult = me.Value End EventHandler
End Control
Control CheckBox2 Inherits CheckBox
ControlInstance CheckBox2 Inherits CheckBox
EventHandler Sub Action() cocoaListbox.HasHeading = me.Value xojoListbox.HasHeading = me.Value End EventHandler
EventHandler Sub Open() me.Value = cocoaListbox.HasHeading End EventHandler
End Control
Control TextField1 Inherits TextField
ControlInstance TextField1 Inherits TextField
EventHandler Sub TextChange() dim i as Integer = me.Text.Val setSortedColumn.Enabled = i >= 1 and i <= cocoaListbox.ColumnCount End EventHandler
End Control
Control setSortedColumn Inherits PushButton
ControlInstance setSortedColumn Inherits PushButton
EventHandler Sub Action() dim i as Integer = TextField1.Text.Val cocoaListbox.SortedColumn = i xojoListbox.SortedColumn = i End EventHandler
End Control
Control PushButton6 Inherits PushButton
ControlInstance PushButton6 Inherits PushButton
EventHandler Sub Action() dim i as Integer = TextField1.Text.Val cocoaListbox.PressHeader i xojoListbox.PressHeader i End EventHandler
End Control
Control PushButton2 Inherits PushButton
ControlInstance PushButton2 Inherits PushButton
EventHandler Sub Action() self.Width = self.Width + 1 End EventHandler
End Control
Control PushButton7 Inherits PushButton
ControlInstance PushButton7 Inherits PushButton
EventHandler Sub Action() self.Width = self.Width - 1 End EventHandler
End Control
Control PushButton4 Inherits PushButton
ControlInstance PushButton4 Inherits PushButton
EventHandler Sub Action() cocoaListbox.updateColumnWidthExpressions End EventHandler
End Control
Control PushButton8 Inherits PushButton
ControlInstance PushButton8 Inherits PushButton
EventHandler Sub Action() for row as Integer = cocoaListbox.ListCount-1 downTo 0 if cocoaListbox.Selected (row) then cocoaListbox.RemoveRow row end if next for row as Integer = xojoListbox.ListCount-1 downTo 0 if xojoListbox.Selected (row) then xojoListbox.RemoveRow row end if next End EventHandler
End Control
Control cocoaListboxLbl Inherits Label
ControlInstance cocoaListboxLbl Inherits Label
End Control
Control xojoListboxLbl Inherits Label
ControlInstance xojoListboxLbl Inherits Label
End Control
EventHandler Sub Open() arrangeListBoxes() End EventHandler
EventHandler Sub Resizing() arrangeListBoxes End EventHandler
Sub Constructor() dim p as new Picture (16, 16) dim g as Graphics = p.Graphics g.ForeColor = &cDBE8FB00 g.FillRect 0, 0, g.Width, g.Height g.ForeColor = &c041DFF00 g.DrawRoundRect 1, 1, g.Width-2, g.Height-2, 8, 8 g.DrawLine 1, 1, g.Width-2, g.Height-2 g.DrawLine 1, g.Height-2, g.Width-2, 1 mSampleImage = p super.Constructor End Sub
Protected Sub arrangeListBoxes() // Make both list boxes share half of the window height each dim h as Integer = xojoListbox.Height + cocoaListbox.Height cocoaListbox.Height = h \ 2 dim h2 as Integer = h - cocoaListbox.Height xojoListbox.Top = xojoListbox.Top - (h2 - xojoListbox.Height) xojoListbox.Height = h2 xojoListboxLbl.Top = xojoListbox.Top - 20 End Sub
Sub log(msg as String) LogWin.Log msg End Sub
Property Private mCellClickResult As Boolean
Property Private mSampleImage As Picture
End Class
Class LogWin Inherits Window
Control TextArea1 Inherits TextArea
ControlInstance TextArea1 Inherits TextArea
End Control
Control PushButton1 Inherits PushButton
ControlInstance PushButton1 Inherits PushButton
EventHandler Sub Action() TextArea1.Text = "" End EventHandler
End Control
Sub Log(msg as String) TextArea1.AppendText msg + EndOfLine TextArea1.ScrollPosition = 99999 End Sub
End Class
Class ListBoxTV Inherits NSOutlineControlMBS
ComputedProperty AutoHideScrollBars As Boolean
Sub Set() if not mHadOpenEvent then mDelayedHideScrollers = value else me.autohidesScrollers = value end if End Set
Sub Get() if not mHadOpenEvent then return mDelayedHideScrollers else return me.autohidesScrollers end if End Get
End ComputedProperty
ComputedProperty ColumnCount As Integer
Sub Set() if not mHadOpenEvent then mDelayedColumnCount = value else if value < 1 then value = 1 dim newUbound as Integer = value-1 if newUbound = mCols.Ubound then // no change return end if // Add columns while newUbound > mCols.Ubound dim c as new ListColumnTV (mSelfRef, mCols.Ubound+1) mCols.Append c c.title = Str(mCols.Ubound) mTableView.addTableColumn c wend // Remove columns while mCols.Ubound > newUbound mTableView.removeTableColumn (mCols.Pop) wend // Make first column the one for the outline's expand control dim cols() as NSTableColumnMBS =mTableView.tableColumns() mTableView.outlineTableColumn = cols(0) ' use the first column mDataSource.DataSource_SetColumnCount value _needsReload end if End Set
Sub Get() if not mHadOpenEvent then return mDelayedColumnCount else return mCols.Ubound+1 end if End Get
End ComputedProperty
ComputedProperty ColumnWidths As String
Sub Set() if not mHadOpenEvent then mDelayedColumnWidths = value else dim w() as String for each s as String in value.Split(",") w.Append s next redim w(mCols.Ubound) if value = "" or w.Ubound <= 0 then // User has not set any rules for the widths or has only one column - let's use the TableView's auto resizing mHasDynamicColumnWidths = false mTableView.columnAutoresizingStyle = mDefaultColumnAutoresizingStyle else // User has set explicit rules for the widths - use our own resizing implementation (see '_recalcColumnWidths') for i as Integer = 0 to mCols.Ubound dim col as ListColumnTV = mCols(i) col.WidthExpression = w(i) next end if end if End Set
Sub Get() if not mHadOpenEvent then return mDelayedColumnWidths else dim res() as String for each col as ListColumnTV in mCols if mMaintainColumnWidths then res.Append col.WidthExpression else res.Append Str (col.WidthActual,"-#") end if next return Join (res, ",") end if End Get
End ComputedProperty
ComputedProperty ColumnsResizable As Boolean
Sub Set() if not mHadOpenEvent then mDelayedColumnResizing = value else me.allowsColumnResizing = value end if End Set
Sub Get() if not mHadOpenEvent then return mDelayedColumnResizing else return me.allowsColumnResizing end if End Get
End ComputedProperty
ComputedProperty DataSource As ListBoxTVDataSource
Sub Set() if value = nil then value = new ListBoxTV_Support.ListBoxTVStaticOutlineDataSource (mSelfRef) end if mDataSource = value mDataSource.DataSource_SetColumnCount mCols.Ubound+1 me.Reload() ' needs to reload without delay, or rowCount won't be inquired before a redraw may occur End Set
Sub Get() return mDataSource End Get
End ComputedProperty
ComputedProperty EnableDrag As Boolean
Sub Set() mEnableDrag = value End Set
Sub Get() return mEnableDrag End Get
End ComputedProperty
ComputedProperty EnableDragReorder As Boolean
Sub Set() mEnableDragReorder = value if value then mTableView.registerForDraggedTypes Array ("private.EnableDragReorder") end if End Set
Sub Get() return mEnableDragReorder End Get
End ComputedProperty
ComputedProperty GridLinesHorizontal As Integer
Sub Set() const NSTableViewDashedHorizontalGridLineMask = 8 dim v as Integer select case value case ListBox.BorderDefault, ListBox.BorderNone v = NSTableViewMBS.NSTableViewGridNone case ListBox.BorderThinDotted v = NSTableViewMBS.NSTableViewSolidHorizontalGridLineMask or NSTableViewDashedHorizontalGridLineMask else v = NSTableViewMBS.NSTableViewSolidHorizontalGridLineMask end select me.TableView.gridStyleMask = (me.TableView.gridStyleMask AND NOT (NSTableViewMBS.NSTableViewSolidHorizontalGridLineMask or NSTableViewDashedHorizontalGridLineMask)) OR v End Set
Sub Get() const NSTableViewDashedHorizontalGridLineMask = 8 dim v as Integer = me.TableView.gridStyleMask AND (NSTableViewMBS.NSTableViewSolidHorizontalGridLineMask or NSTableViewDashedHorizontalGridLineMask) if (v AND NSTableViewDashedHorizontalGridLineMask) <> 0 then return ListBox.BorderThinDotted elseif v <> 0 then return ListBox.BorderThinSolid else return ListBox.BorderNone end if End Get
End ComputedProperty
ComputedProperty GridLinesVertical As Integer
Sub Set() dim v as Integer select case value case ListBox.BorderDefault, ListBox.BorderNone v = NSTableViewMBS.NSTableViewGridNone else v = NSTableViewMBS.NSTableViewSolidVerticalGridLineMask end select me.TableView.gridStyleMask = (me.TableView.gridStyleMask AND NOT NSTableViewMBS.NSTableViewSolidVerticalGridLineMask) OR v End Set
Sub Get() dim v as Integer = me.TableView.gridStyleMask AND NSTableViewMBS.NSTableViewSolidVerticalGridLineMask if v <> 0 then return ListBox.BorderThinSolid else return ListBox.BorderNone end if End Get
End ComputedProperty
ComputedProperty HasHeading As Boolean
Sub Set() if value <> me.HasHeading then if value then mTableView.headerView = mHeaderView else mTableView.headerView = nil end if end if End Set
Sub Get() return mTableView.headerView <> nil End Get
End ComputedProperty
ComputedProperty Hierarchical As Boolean
Sub Set() if value then mTableView.indentationPerLevel = mOrigIndentationPerLevel else mTableView.indentationPerLevel = 0 end _needsReload() End Set
Sub Get() return mTableView.indentationPerLevel <> 0 End Get
End ComputedProperty
ComputedProperty IntercellSpacing As Integer
Sub Set() dim ss as new NSSizeMBS ss.Width = value ss.Height = value mTableView.intercellSpacing = ss End Set
End ComputedProperty
ComputedProperty LastIndex As Integer
Sub Get() return mLastAddedIndex End Get
End ComputedProperty
ComputedProperty ListCount As Integer
Sub Get() return mDataSource.DataSource_CurrentRowCount() ' do not call 'mTableView.numberOfRows', as it may not be up-to-date yet End Get
End ComputedProperty
ComputedProperty ListIndex As Integer
Sub Set() if value >= 0 and value < me.ListCount then mTableView.selectRowIndexes (NSIndexSetMBS.indexSetWithIndex(value), false) else me.DeselectAll end if End Set
Sub Get() if mSelectionCache.count = 0 then return -1 else return mSelectionCache.firstIndex end if End Get
End ComputedProperty
ComputedProperty MaintainColumnWidths As Boolean
True: Emulate Xojo's algorithm to keep columns sized based on "%" and "*" values in ColumnWidths or Columns(n).WidthExpression. False: If ColumnWidths is set, the columns are adjusted once. After that, they are maintained by NSTableView's columnAutoresizingStyle property.
Sub Set() mMaintainColumnWidths = value End Set
Sub Get() return mMaintainColumnWidths End Get
End ComputedProperty
ComputedProperty RequiresSelection As Boolean
Sub Set() if not mHadOpenEvent then mDelayedRequiresSelection = value else me.allowsEmptySelection = not value end if End Set
Sub Get() if not mHadOpenEvent then return mDelayedRequiresSelection else return not me.allowsEmptySelection end if End Get
End ComputedProperty
ComputedProperty ScrollPosition As Integer
Sub Set() ' End Set
Sub Get() return 0 End Get
End ComputedProperty
ComputedProperty ScrollbarHorizontal As Boolean
Sub Set() if not mHadOpenEvent then mDelayedHorScroller = value else me.hasHorizontalScroller = value end if End Set
Sub Get() if not mHadOpenEvent then return mDelayedHorScroller else return me.hasHorizontalScroller end if End Get
End ComputedProperty
ComputedProperty ScrollbarVertical As Boolean
Sub Set() if not mHadOpenEvent then mDelayedVerScroller = value else me.hasVerticalScroller = value end if End Set
Sub Get() if not mHadOpenEvent then return mDelayedVerScroller else return me.hasVerticalScroller end if End Get
End ComputedProperty
ComputedProperty SelectionType As Integer
Sub Set() if not mHadOpenEvent then mDelayedSelectionType = value else mTableView.allowsMultipleSelection = value <> 0 end if End Set
Sub Get() if not mHadOpenEvent then return mDelayedSelectionType else if mTableView.allowsMultipleSelection then return 1 end if end if End Get
End ComputedProperty
ComputedProperty SortedColumn As Integer
Sub Set() // Note: This method should not depress the header, i.e. not indicate sorting, but it does currently. Not perfect, but should work for most cases. if not mHadOpenEvent then return // Re-arrange the existing sort descriptors so that the specified column comes first dim sorters() as NSSortDescriptorMBS = mTableView.sortDescriptors() for each sorter as NSSortDescriptorMBS in sorters dim col as Integer = mTableView.columnWithIdentifier (sorter.key) if col = value then // found it dim i as Integer = sorters.IndexOf (sorter) if i <> 0 then sorters.Remove i sorters.Insert 0, sorter mTableView.setSortDescriptors sorters end if return end if next // If there are no sort descriptors for our column, set it from our ListColumnTV dim col as ListColumnTV = Column(value) sorters.Insert 0, col.SortDescriptorPrototype mTableView.setSortDescriptors sorters End Set
Sub Get() dim sorters() as NSSortDescriptorMBS = mTableView.sortDescriptors() if sorters.Ubound >= 0 then dim firstSorter as NSSortDescriptorMBS = sorters(0) return mTableView.columnWithIdentifier (firstSorter.key) end if End Get
End ComputedProperty
ComputedProperty TextFont As String
Sub Set() mTextFont = value _needsReload End Set
Sub Get() return mTextFont End Get
End ComputedProperty
ComputedProperty TextSize As Integer
Sub Set() mTextSize = value _needsReload End Set
Sub Get() return mTextSize End Get
End ComputedProperty
ComputedProperty UseContextualClickEvent As Boolean
Set this to True if you want to use the ContextualMenu events.
Sub Set() mUseContextualClickEvent = value if value then mTableView.menu = mContextMenu else mTableView.menu = nil end if End Set
Sub Get() return mUseContextualClickEvent End Get
End ComputedProperty
ComputedProperty UseFocusRing As Boolean
Sub Set() if value then me.ScrollView.focusRingType = NSScrollViewMBS.NSFocusRingTypeExterior else me.ScrollView.focusRingType = NSScrollViewMBS.NSFocusRingTypeNone end if End Set
Sub Get() return me.ScrollView.focusRingType = NSScrollViewMBS.NSFocusRingTypeExterior End Get
End ComputedProperty
Const DragReorderPBType = "private.DragReorder"
Const Support_OSX_10_6 = true
Const Version = 5
Enum columnWidthCalcTypes actual minimum maximum End Enum
Enum columnWidthTypes absolute percentage remain End Enum
Event CellAction(row as Integer, column as Integer) End Event
Event CellBackgroundPaint(g As Graphics, row As Integer, column As Integer, textFrame as NSRectMBS, controlView as NSViewMBS) As Boolean End Event
Event CellClick(row as Integer, column as Integer, x as Integer, y as Integer) As Boolean End Event
Event CellTextPaint(g As Graphics, row As Integer, column As Integer, x as Integer, y as Integer, textFrame as NSRectMBS, controlView as NSViewMBS) As Boolean End Event
Event Change() End Event
Event CollapseRow(row As Integer) End Event
Event ColumnsMoved(oldColumn as Integer, newColumn as Integer) End Event
Event ColumnsResized(column as Integer, oldWidth as Integer) End Event
Event CompareRows(cell1 as ListCellTV, cell2 as ListCellTV, row1 as Integer, row2 as Integer, column as Integer, ByRef result as Integer) As Boolean End Event
Event ConstructContextualMenu(base as MenuItem, x as Integer, y as Integer, clickedRow as Integer, clickedColumn as Integer, parentMenu as NSMenuMBS) As Boolean End Event
Event ConstructContextualMenuForHeader(base as MenuItem, x as Integer, y as Integer, clickedColumn as Integer, parentMenu as NSMenuMBS) As Boolean End Event
Event ContextualMenuAction(hitItem as MenuItem, clickedRow as Integer, clickedColumn as Integer) As Boolean End Event
Event DenyReorderColumn(fromColumn as Integer, toColumn as Integer) As Boolean End Event
Event DidResizeCells() End Event
Event DragReorderColumns(movedColumn as integer, insertColumn as Integer) End Event
Event DragReorderRows(newPosition as Integer, parentRow as Integer) As Boolean End Event
Event DragRow(drag as DragItemTV, row as Integer, ByRef operationMask as Integer, ByRef thisAppOnly as Boolean) As Boolean End Event
Event DropObject(obj as DragItemTV, operationMask as Integer, aboveRow as Integer, draggingInfo as NSDraggingInfoMBS) End Event
Event ExpandRow(row as Integer) End Event
Event HeaderPressed(column as Integer) As Boolean End Event
Event MouseDown(x as Integer, y as Integer) As Boolean End Event
Event Open() End Event
Event SortColumn(column as Integer) As Boolean End Event
Event WillDisplayCell(row as Integer, column as Integer, cell as ListCellTV, textFieldCell as NSCellMBS, tableColumn as NSTableColumnMBS) As Boolean End Event
Event didTile() End Event
EventHandler Sub ColumnDidMove(notification as NSNotificationMBS, OldColumn as Integer, NewColumn as Integer) mInsideHeaderDrag = false mIgnoreTileEvents = false dim indexes() as Integer for each col as ListColumnTV in mCols col._updateOwnIndex indexes.Append col.ColumnIndex next indexes.SortWith mCols mDataSource.DataSource_MoveColumn (oldColumn, newColumn) RaiseEvent ColumnsMoved(oldColumn, newColumn) End EventHandler
EventHandler Sub ColumnDidResize(notification as NSNotificationMBS, tableColumn as NSTableColumnMBS, OldWidth as Double) if mInsideHeaderDrag then mInsideHeaderDrag = false mIgnoreTileEvents = false me.updateColumnWidthExpressions // Does the cursor remain in "Resizing" state? This happens only with Real Studio 2012, however, but not with Xojo 2016. end if if not mIgnoreTileEvents then RaiseEvent ColumnsResized (ListColumnTV(tableColumn).ColumnIndex, oldWidth) end End EventHandler
EventHandler Function ConstructContextualMenu(base as MenuItem, x as Integer, y as Integer) As Boolean ' End EventHandler
EventHandler Function ContextualMenuAction(hitItem as MenuItem) As Boolean ' End EventHandler
EventHandler Function DragEnter(obj As DragItem, action As Integer) As Boolean ' End EventHandler
EventHandler Sub DragExit(obj As DragItem, action As Integer) ' End EventHandler
EventHandler Function DragOver(x As Integer, y As Integer, obj As DragItem, action As Integer) As Boolean ' End EventHandler
EventHandler Sub DropObject(obj As DragItem, action As Integer) break ' this would be unexpected unless the user invoked AcceptDrop... on the Xojo control directly, circumventing our ListBoxTV functions. End EventHandler
EventHandler Sub ItemDidCollapse(notification as NSNotificationMBS, item as NSOutlineViewItemMBS) didCollapse ListRowTV(item) End EventHandler
EventHandler Sub ItemDidExpand(notification as NSNotificationMBS, item as NSOutlineViewItemMBS) expandEnd ListRowTV(item) End EventHandler
EventHandler Sub ItemWillExpand(notification as NSNotificationMBS, item as NSOutlineViewItemMBS) expandBegin ListRowTV(item) End EventHandler
EventHandler Function MenuAction(HitItem as MenuItem) As Boolean ' End EventHandler
EventHandler Function MouseDown(x as Integer, y as Integer, Modifiers as Integer) As Boolean return handleMouseDown (x, y, modifiers) End EventHandler
EventHandler Sub Open() me.setDelayedProperties() me.setupFromInitialValue() mCurrentRootItem = nil mLastAddedIndex = -1 RaiseEvent Open() End EventHandler
EventHandler Sub SelectionDidChange(notification as NSNotificationMBS) mSelectionCache = mTableView.selectedRowIndexes() RaiseEvent Change() End EventHandler
EventHandler Function acceptDrop(info as NSDraggingInfoMBS, item as NSOutlineViewItemMBS, index as Integer) As Boolean // Finishes the drop // item is the parent item, and index the child's position where to insert the dropped items dim pb as NSPasteboardMBS = info.draggingPasteboard dim operationMask as Integer = info.draggingSourceOperationMask dim row as Integer if index >= 0 then // -> drag between rows dim childItem as NSOutlineViewItemMBS childItem = mTableView.child (index, item) if childItem = nil then // index is past the last child of the parent item -> append to end of children row = mTableView.rowForItem (item) + 1 + mTableView.numberOfChildrenOfItem (item) else row = mTableView.rowForItem (childItem) end if else // -> drag onto a row row = mTableView.rowForItem (item) end if dim done as Boolean // Handle "drag reorder" if mEnableDragReorder and info.draggingSource = mTableView then dim mb as MemoryBlock = pb.dataForType(DragReorderPBType) if mb <> nil then dim srcRows() as Integer for i as Integer = 0 to mb.Size-1 step 4 srcRows.Append mb.Int32Value(i) next ListBoxTV(me).SelectRows srcRows ' makes sure the moved rows are selected because the DragReorderRows event expects that if not RaiseEvent DragReorderRows (row, -1) then mDataSource.DataSource_MoveRows (srcRows, row) mTableView.reloadData // this call is needed as well, or the following redraw might keep showing wrong cell content self._needsReload // so that every cell's WillDisplayCell event is called in case they moved end if done = true ' -> drop was successful end if end if if not done then // Send it to the DropObject event handler dim dragItems as new DragItemTV (pb) RaiseEvent DropObject (dragItems, operationMask, row, info) ' we should pass "Action as Integer" here, but I don't know how that translation from the operationMask is supposed to be done. end #if DebugBuild self._DebugCheck #endif return true End EventHandler
EventHandler Function childOfItem(index as Integer, item as NSOutlineViewItemMBS) As NSOutlineViewItemMBS return mDataSource.DataSource_ChildItem (ListRowTV(item), index) End EventHandler
EventHandler Function dataCell(tableColumn as NSTableColumnMBS, item as NSOutlineViewItemMBS) As NSCellMBS if tableColumn = nil then return nil end if dim tcol as ListColumnTV = ListColumnTV(tableColumn) dim col as Integer = tcol.ColumnIndex dim myCell as ListCellTV = _cell(item, col) dim t as Integer = myCell.Type if t = ListBox.TypeCheckbox or (t = ListBox.TypeDefault and tcol.Type = ListBox.TypeCheckbox) then // A checkbox cell static bc as NSButtonCellMBS if bc = nil then ' we only need to create this once bc = new NSButtonCellMBS ("x") declare sub setButtonType lib "Cocoa" selector "setButtonType:" (h as Integer, t as Integer) setButtonType (bc.Handle, NSButtonMBS.NSSwitchButton) bc.wraps = false bc.lineBreakMode = NSParagraphStyleMBS.NSLineBreakByTruncatingTail end if return bc end if // We return a custom NSTextFieldCell, in order to perform some custom painting in its drawWithFrame event handler. // We are lazy and re-use the same cell, because we keep getting this event called for each drawn cell, sequentially. static cc as ListBoxTV_Support.ListDrawCellHandler if cc = nil then cc = new ListBoxTV_Support.ListDrawCellHandler (mSelfRef) cc.editable = true cc.wraps = false cc.lineBreakMode = NSParagraphStyleMBS.NSLineBreakByTruncatingTail end if return cc End EventHandler
EventHandler Sub didClickTableColumn(tableColumn as NSTableColumnMBS) mInsideHeaderDrag = false mIgnoreTileEvents = false dim column as Integer = ListColumnTV(tableColumn).ColumnIndex if RaiseEvent HeaderPressed (column) then // handled return end if ListBoxTV(me).SortedColumn = column _sort End EventHandler
EventHandler Sub didDragTableColumn(tableColumn as NSTableColumnMBS) mInsideHeaderDrag = false mIgnoreTileEvents = false End EventHandler
EventHandler Sub didTile() RaiseEvent didTile if not mIgnoreTileEvents then if mMaintainColumnWidths then _recalcColumnWidths() end if end if End EventHandler
EventHandler Function isItemExpandable(item as NSOutlineViewItemMBS) As Boolean if mTableView.indentationPerLevel = 0 then ' non-hierarchical return false end return mDataSource.DataSource_IsExpandable (ListRowTV (item)) End EventHandler
EventHandler Sub mouseDownInHeaderOfTableColumn(tableColumn as NSTableColumnMBS) if mMaintainColumnWidths then mIgnoreTileEvents = true mInsideHeaderDrag = true end if End EventHandler
EventHandler Function numberOfChildrenOfItem(item as NSOutlineViewItemMBS) As Integer return mDataSource.DataSource_ChildCount (ListRowTV(item)) End EventHandler
EventHandler Function objectValue(tableColumn as NSTableColumnMBS, item as NSOutlineViewItemMBS) As Variant dim col as Integer = ListColumnTV(tableColumn).ColumnIndex dim myCell as ListCellTV = _cell (item, col, true) dim t as Integer = myCell.Type if t = ListBox.TypeCheckbox or (t = ListBox.TypeDefault and ListColumnTV(tableColumn).Type = ListBox.TypeCheckbox) then // This is a checkbox - we return the Checkbox value, not its title text return myCell.Checked end return myCell.Text End EventHandler
EventHandler Sub setObjectValue(tableColumn as NSTableColumnMBS, item as NSOutlineViewItemMBS, value as Variant) dim col as Integer = ListColumnTV(tableColumn).ColumnIndex dim row as Integer = mTableView.rowForItem (item) dim myCell as ListCellTV = _cell (item, col) dim isCheckbox as Boolean dim t as Integer = myCell.Type if t = ListBox.TypeCheckbox or (t = ListBox.TypeDefault and ListColumnTV(tableColumn).Type = ListBox.TypeCheckbox) then isCheckbox = true end mDataSource.DataSource_CellWillUpdate (myCell, ListRowTV(item), col, not isCheckbox, isCheckbox) if isCheckbox then myCell.Checked = value.BooleanValue elseif value isA NSAttributedStringMBS then myCell.Text = NSAttributedStringMBS(value).text else myCell.Text = value.StringValue end mDataSource.DataSource_CellDidUpdate (myCell, ListRowTV(item), col, not isCheckbox, isCheckbox) RaiseEvent CellAction (row, col) End EventHandler
EventHandler Function shouldEdit(tableColumn as NSTableColumnMBS, item as NSOutlineViewItemMBS) As Boolean dim t as Integer = ListColumnTV(tableColumn).EffectiveType(item) return t = ListBox.TypeEditable or t = ListBox.TypeCheckbox End EventHandler
EventHandler Function shouldReorderColumn(columnIndex as Integer, newColumnIndex as Integer) As Boolean return not RaiseEvent DenyReorderColumn (columnIndex, newColumnIndex) End EventHandler
EventHandler Function validateDrop(info as NSDraggingInfoMBS, proposedItem as NSOutlineViewItemMBS, proposedChildIndex as Integer) As Integer // Determines whether the current row is accepted as a drop target if mEnableDragReorder then dim operationMask as Integer = info.draggingSourceOperationMask // dragging a folder into one of its children is not allowed! if info.draggingSource = mTableView then dim pb as NSPasteboardMBS = info.draggingPasteboard dim mb as MemoryBlock = pb.dataForType(DragReorderPBType) if mb <> nil then dim srcRows() as Integer for i as Integer = 0 to mb.Size-1 step 4 srcRows.Append mb.Int32Value(i) next for each row as Integer in srcRows dim item as NSOutlineViewItemMBS = _rowItemForRow(row) if itemIsSameOrParentOf (item, proposedItem) then // disallow this drop operationMask = 0 exit end if next end if end if return operationMask end if End EventHandler
EventHandler Sub willDisplayCell(cell as NSCellMBS, tableColumn as NSTableColumnMBS, item as NSOutlineViewItemMBS) #if Support_OSX_10_6 // We're using outdated NSCells instead of NSViews so that we can keep using this control in OSX 10.6. // If you prefer to use custom NSViews (e.g. in order to add buttons, input fields, checkboxes or more labels), // implement the ListBoxTV.view event - if there's code in that event, then this ListBoxTV.willDisplayCell won't // be invoked, i.e. you end up with a modern NSView based TableView. dim col as Integer = ListColumnTV(tableColumn).ColumnIndex dim myCell as ListCellTV = _cell (item, col) dim tfc as NSTextFieldCellMBS dim bc as NSButtonCellMBS if cell isA NSButtonCellMBS then bc = NSButtonCellMBS(cell) else tfc = NSTextFieldCellMBS(cell) end if // Preset font if mTextFont <> "" and mTextFont <> "System" then cell.font = NSFontMBS.fontWithName (mTextFont, mTextSize) elseif mTextSize > 0 then cell.font = NSFontMBS.systemFontOfSize (mTextSize) end if dim row as Integer = myCell._internalUse.Left if col <> myCell._internalUse.Right then break _suppressReload = true ' in case the event changes some properties dim handled as Boolean = RaiseEvent WillDisplayCell (row, col, myCell, cell, tableColumn) _suppressReload = false if not handled then // Button title if bc <> nil then bc.title = myCell.text end if // Cell text color if tfc <> nil then if myCell.TextColor <> &c00000000 then dim xc as Color = myCell.TextColor dim c as NSColorMBS = NSColorMBS.colorWithCalibratedRGB (xc.Red/255, xc.Green/255, xc.Blue/255, 1-xc.Alpha/255) tfc.textColor = c else tfc.textColor = nil end if end if // Cell background color if myCell.BackgroundColor <> &cFFFFFFFF then dim xc as Color = myCell.BackgroundColor dim c as NSColorMBS = NSColorMBS.colorWithCalibratedRGB (xc.Red/255, xc.Green/255, xc.Blue/255, 1-xc.Alpha/255) if bc <> nil then bc.backgroundColor = c else tfc.backgroundColor = c #if false ' we handle this in ListDrawCellHandler.drawWithFrame so that it also works with indented text tfc.drawsBackground = true #endif end if elseif bc <> nil then bc.backgroundColor = nil else tfc.backgroundColor = nil tfc.drawsBackground = false end if // Cell text alignment dim align as Integer if myCell.alignment <> ListBox.AlignDefault then align = myCell.alignment else align = ListColumnTV(tableColumn).Alignment end if select case align case ListBox.AlignDefault align = NSTextMBS.NSNaturalTextAlignment case ListBox.AlignLeft align = NSTextMBS.NSLeftTextAlignment case ListBox.AlignRight align = NSTextMBS.NSRightTextAlignment case ListBox.AlignCenter align = NSTextMBS.NSCenterTextAlignment case ListBox.AlignDecimal align = NSTextMBS.NSJustifiedTextAlignment end select cell.alignment = align // Cell text indentation and background drawing (handled by ListDrawCellHandler.drawWithFrame) if tfc <> nil then if tfc isA ListBoxTV_Support.ListDrawCellHandler then ListBoxTV_Support.ListDrawCellHandler(tfc).cell = myCell end if else ' unfortunately, we cannot indent checkbox cells end if end if #endif End EventHandler
EventHandler Function writeItems(items() as NSOutlineViewItemMBS, pasteboard as NSPasteboardMBS) As Boolean dim didAdd as Boolean call pasteboard.clearContents dim rows() as Integer = rowsFromItems (items) if rows.Ubound < 0 then return false if mEnableDrag then ListBoxTV(me).SelectRows rows ' makes sure the dragged rows are selected because the DragRow event expects that dim drag as new DragItemTV (pasteboard) dim operationMask as Integer = NSDraggingInfoMBS.NSDragOperationEvery // Xojo uses NSDragOperationEvery, Real Studio used NSDragOperationAll_Obsolete dim locally as Boolean = false if RaiseEvent DragRow (drag, rows(0), operationMask, locally) then drag.FinishAddedItems mTableView.setDraggingSourceOperationMask (operationMask, locally) didAdd = true end if end if if mEnableDragReorder then // Put the moved rows into a MemoryBlock and pass that as a private data item in the pasteboard dim mb as new MemoryBlock (4 * (rows.Ubound+1)) for i as Integer = 0 to rows.Ubound mb.Int32Value (4*i) = rows(i) next pasteboard.dataForType (DragReorderPBType) = mb if not didAdd then me.View.setDraggingSourceOperationMask (NSDraggingInfoMBS.NSDragOperationMove, true) end didAdd = true end if return didAdd End EventHandler
Sub AcceptFileDrop(type As String) mTableView.registerForDraggedTypes Array (NSPasteboardMBS.NSFilenamesPboardType, "public.file-url") End Sub
Sub AcceptMacDataDrop(type As String) me.AcceptRawDataDrop (type) End Sub
Sub AcceptPictureDrop() break ' missing (needs methods in DragItemTV, too) End Sub
Sub AcceptRawDataDrop(type As String) mTableView.registerForDraggedTypes Array (ListBoxTV_Support.convertOSType(type)) End Sub
Sub AcceptTextDrop() mTableView.registerForDraggedTypes Array (NSPasteboardMBS.NSStringPboardType, NSPasteboardMBS.NSPasteboardTypeString) End Sub
Sub AddFolder(txt as String) addItem txt, true End Sub
Sub AddRow(items() as String) addItems items End Sub
Sub AddRow(ParamArray items as String) addItems items End Sub
Sub BeginUpdates() mTableView.beginUpdates End Sub
Function Cell(row as Integer, column as Integer) As String return _cell(row,column).text End Function
Sub Cell(row as Integer, column as Integer, assigns v as String) _cell(row,column).text = v _needsReload() End Sub
Function CellAlignment(row as Integer, column as Integer) As Integer dim cell as ListCellTV = _cell(row, column) return cell.alignment End Function
Sub CellAlignment(row as Integer, column as Integer, assigns v as Integer) dim cell as ListCellTV = _cell(row, column) if cell.alignment <> v then cell.alignment = v _needsReload() end if End Sub
Function CellAlignmentOffset(row as Integer, column as Integer) As Integer dim cell as ListCellTV = _cell(row, column) return cell.Indentation End Function
Sub CellAlignmentOffset(row as Integer, column as Integer, assigns v as Integer) dim cell as ListCellTV = _cell(row, column) if cell.Indentation <> v then cell.Indentation = v _needsReload() end if End Sub
Function CellBold(row as Integer, column as Integer) As Boolean return _cell(row, column).Bold End Function
Sub CellBold(row as Integer, column as Integer, assigns v as Boolean) if _cell(row, column).Bold <> v then _cell(row, column).Bold = v _needsReload() end if End Sub
Function CellCheck(row as Integer, column as Integer) As Boolean return _cell(row, column).Checked End Function
Sub CellCheck(row as Integer, column as Integer, assigns v as Boolean) if _cell(row, column).Checked <> v then _cell(row, column).Checked = v _needsReload() end if End Sub
Function CellHelpTag(row as Integer, column as Integer) As String return _cell(row, column).ToolTip End Function
Sub CellHelpTag(row as Integer, column as Integer, assigns v as String) _cell(row, column).ToolTip = v End Sub
Function CellItalic(row as Integer, column as Integer) As Boolean return _cell(row, column).Italic End Function
Sub CellItalic(row as Integer, column as Integer, assigns v as Boolean) if _cell(row, column).Italic <> v then _cell(row, column).Italic = v _needsReload() end if End Sub
Function CellStyle(row as Integer, column as Integer) As ListCellTV return _cell(row,column) End Function
Function CellTag(row as Integer, column as Integer) As Variant return _cell(row,column).tag End Function
Sub CellTag(row as Integer, column as Integer, assigns v as Variant) _cell(row,column).tag = v End Sub
Function CellType(row as Integer, column as Integer) As Integer dim cell as ListCellTV = _cell(row, column) return cell.Type End Function
Sub CellType(row as Integer, column as Integer, assigns v as Integer) dim cell as ListCellTV = _cell(row, column) if cell.Type <> v then cell.Type = v _needsReload() end if End Sub
Function Column(col as Integer) As ListColumnTV return mCols(col) End Function
Function ColumnAlignment(column as Integer) As Integer return mCols(column).Alignment End Function
Sub ColumnAlignment(column as Integer, assigns v as Integer) mCols(column).Alignment = v End Sub
Function ColumnAlignmentOffset(column as Integer) As Integer return mCols(column).AlignmentOffset End Function
Sub ColumnAlignmentOffset(column as Integer, assigns v as Integer) mCols(column).AlignmentOffset = v End Sub
Function ColumnFromXY(x as Integer, y as Integer) As Integer dim loc0 as new NSPointMBS (x, y) dim loc as NSPointMBS = mTableView.convertPointFromView (loc0, me.ScrollView) return mTableView.columnAtPoint (loc) End Function
Function ColumnSortDirection(column as Integer) As Integer return mCols(column).SortDirection End Function
Sub ColumnSortDirection(column as Integer, assigns dir as Integer) mCols(column).SortDirection = dir End Sub
Function ColumnType(column as Integer) As Integer return mCols(column).Type End Function
Sub ColumnType(column as Integer, assigns v as Integer) mCols(column).Type = v End Sub
Sub Constructor() // We create a weak ref to ourselves so that we can pass that to our chiild classes instead of them creating more WeakRefs (this is a performance optimization) mSelfRef = new WeakRef (me) // Our separate class for handling listbox content (can be replaced by user) mDataSource = new ListBoxTV_Support.ListBoxTVStaticOutlineDataSource (mSelfRef) // Set up some local properties for cleaner access to some tableview properties mTableView = me.View mHeaderView = mTableView.headerView // Context Menu preparation for Cells mContextMenu = new ListBoxTV_Support.ListBoxTVContextMenu (mSelfRef) // Context Menu preparation for the Header, see http://stackoverflow.com/a/3850215/43615 mTableView.headerView.menu = mContextMenu // Let's preset the columns to automatic resizing - can be overwritten by code in Open() event or later, or disabled by setting ColumnWidths to a non-empty string. mDefaultColumnAutoresizingStyle = NSTableViewMBS.NSTableViewUniformColumnAutoresizingStyle mTableView.columnAutoresizingStyle = mDefaultColumnAutoresizingStyle // We cache the selection for performance reasons (not sure if that's necessary, though) mSelectionCache = NSIndexSetMBS.indexSet() mOrigIndentationPerLevel = mTableView.indentationPerLevel mTableView.indentationPerLevel = 0 ' set this to zero for non-hierarchical lists mTableView.autoresizesOutlineColumn = false super.Constructor End Sub
Sub DeleteAllRows() mDataSource.DataSource_DeleteAllRows me.ScrollPosition = 0 mCurrentRootItem = nil mLastAddedIndex = -1 me.Reload() ' needs to reload without delay, or rowCount won't be inquired before a redraw may occur End Sub
Sub DeselectAll() mTableView.deselectAll End Sub
Sub EndUpdates() mTableView.endUpdates End Sub
Function Expanded(row as Integer) As Boolean dim rowItem as ListRowTV = _rowItemForRow (row) return mTableView.isItemExpanded (rowItem) End Function
Sub Expanded(row as Integer, assigns expand as Boolean) dim rowItem as ListRowTV = _rowItemForRow (row) if mTableView.isExpandable (rowItem) then if expand then expand rowItem else mTableView.collapseItem rowItem, false end if end if End Sub
Function HeaderHeight() As Integer return mTableView.headerView.frame.Height End Function
Function Heading(col as Integer) As String return mCols(col).headerCell.stringValue End Function
Sub Heading(col as Integer, assigns txt as String) mCols(col).headerCell.stringValue = txt End Sub
Sub InvalidateCell(row as Integer, column as Integer) // We're lazy and simply reload the entire view's cells. // Ideally, we're determine the rect of the row/col and then call "setNeedsDisplayInRect:" instead. _needsReload() End Sub
Function List(row as Integer) As String return me.Cell (row, 0) End Function
Sub List(row as Integer, assigns v as String) me.Cell (row, 0) = v End Sub
Sub PressHeader(col as Integer) // This method, contrary to just setting SortedColumn and calling Sort, should also depress the header, whereas SortedColumn should not. Doesn't work right yet, though me.SortedColumn = col me.Sort End Sub
Sub RecalculateColumnWidths() // Call this after a column or the entire control got resized and you want to keep the "*" or "%" column widths re-applied _recalcColumnWidths() End Sub
Sub Reload() _reloadNow(nil) End Sub
Sub RemoveRow(row as Integer, animate as Boolean = false) if mDataSource isA ListBoxTV_Support.ListBoxTVStaticOutlineDataSource then dim animationOptions as Integer if animate then const NSTableViewAnimationEffectFade = 1 const NSTableViewAnimationSlideUp = 16 animationOptions = NSTableViewAnimationEffectFade or NSTableViewAnimationSlideUp end if mTableView.beginUpdates ListBoxTV_Support.ListBoxTVStaticOutlineDataSource(mDataSource).RemoveRow (row, animationOptions) mTableView.endUpdates mLastAddedIndex = -1 else break // If you have assigned your own DataSource, then you cannot use RemoveRow() as that makes little sense end if _needsReload ' we reload lazily so that multiple calls to AddRow won't cause many reloads End Sub
Function RowForItem(rowItem as ListRowTV) As Integer return mTableView.rowForItem (rowItem) End Function
Function RowFromXY(x as Integer, y as Integer) As Integer dim loc0 as new NSPointMBS (x, y) dim loc as NSPointMBS = mTableView.convertPointFromView (loc0, me.ScrollView) return mTableView.rowAtPoint (loc) End Function
Function RowHeight() As Integer return Round (mTableView.rowHeight) End Function
Function RowPicture(row as Integer) As Picture return _cell(row,0).picture End Function
Sub RowPicture(row as Integer, assigns pic as Picture) _cell(row,0).picture = pic End Sub
Function RowTag(row as Integer) As Variant return ListRowTV(_rowItemForRow(row)).tag End Function
Sub RowTag(row as Integer, assigns tag as Variant) ListRowTV(_rowItemForRow(row)).tag = tag End Sub
Function SelCount() As Integer return mSelectionCache.count End Function
Sub SelectRows(rows() as Integer) dim set as new NSMutableIndexSetMBS for each row as Integer in rows set.addIndex row next mSelectionCache = set mTableView.selectRowIndexes mSelectionCache, false End Sub
Function Selected(row as Integer) As Boolean return mSelectionCache.containsIndex (row) End Function
Sub Selected(row as Integer, assigns sel as Boolean) dim isSelected as Boolean = mSelectionCache.containsIndex (row) if isSelected <> sel then mSelectionCache = mSelectionCache.mutableCopy if sel then NSMutableIndexSetMBS(mSelectionCache).addIndex row else NSMutableIndexSetMBS(mSelectionCache).removeIndex row end if mTableView.selectRowIndexes mSelectionCache, false end if End Sub
Function SelectedRows() As Integer() return mSelectionCache.Values End Function
Sub Sort() _sort End Sub
Function TableView() As NSOutlineViewMBS return mTableView End Function
Sub UpdateColumnWidthExpressions() // Takes the current column widths and adjusts the ListColumnTV.WidthExpression values accordingly _suppressReload = true dim colSpacing as Integer = mTableView.intercellSpacing.Width // extra space that every visible column occupies // Determine total width first dim totalWidth as Integer for col as Integer = 0 to mCols.Ubound dim c as ListColumnTV = mCols(col) dim w as Integer = Round (c.width) if w > 0 then w = w + colSpacing totalWidth = totalWidth + w next // Now update the width expressions (absolute and percentage) dim remainWidth as Integer = totalWidth dim asteriskValues() as Double dim lowestAsteriskWidth as Integer = totalWidth for col as Integer = 0 to mCols.Ubound dim c as ListColumnTV = mCols(col) dim w as Integer = Round (c.width) dim expr as String = mCols(col).WidthExpression dim v as Double dim t as columnWidthTypes = determineColumnWidthType (columnWidthCalcTypes.actual, expr, v) if t = columnWidthTypes.absolute or (t = columnWidthTypes.remain and w = 0) then c.WidthExpression = Str(w,"-#") asteriskValues.Append 0 if w > 0 then w = w + colSpacing remainWidth = remainWidth - w elseif t = columnWidthTypes.percentage then c.WidthExpression = ListBoxTV_Support.trimPeriod (Str (w / totalWidth * 100,"-#.#####")) + "%" asteriskValues.Append 0 if w > 0 then w = w + colSpacing remainWidth = remainWidth - w else asteriskValues.Append w remainWidth = remainWidth - colSpacing if w < lowestAsteriskWidth then lowestAsteriskWidth = w end next // Finally, update the asterisk width expressions dim scale as Double = remainWidth / lowestAsteriskWidth // so that we don't have any asterisk values below 1 for col as Integer = 0 to mCols.Ubound dim w as Integer = asteriskValues(col) if w <> 0 then dim c as ListColumnTV = mCols(col) c.WidthExpression = ListBoxTV_Support.trimPeriod (Str (Max (1, w / remainWidth * scale),"-#.########")) + "*" end if next finally _suppressReload = false End Sub
Sub _DebugCheck() // For debugging only. // Checks the integrity of the data #if DebugBuild if mDataSource isA ListBoxTV_Support.ListBoxTVStaticOutlineDataSource then ListBoxTV_Support.ListBoxTVStaticOutlineDataSource(mDataSource)._DebugCheck end if #endif End Sub
Function _cell(row as Integer, column as Integer, ignoreCache as Boolean = false) As ListCellTV return _cell (_rowItemForRow (row), column, ignoreCache, row) End Function
Function _cell(rowItem as NSOutlineViewItemMBS, column as Integer, ignoreCache as Boolean = false, row as Integer = -1) As ListCellTV if rowItem = nil then return nil end if if not ignoreCache then if mPreviousCell <> nil and mPreviousItem = rowItem and mPreviousCell._internalUse.Right = column then // Usually, we get three calls for the same cell in sequence - 1st from objectValue, 2nd from dataCellForTableColumn, 3rd from willDisplayCell // We cache the cell here for performance reasons return mPreviousCell end if end if if row < 0 then if mPreviousItem = rowItem then row = mPreviousItemRow else row = mTableView.rowForItem (rowItem) end if if row < 0 then break end if dim cell as ListCellTV = mDataSource.DataSource_Cell (ListRowTV(rowItem), column) if cell = nil then // create an empty one cell = new ListCellTV () end if cell._internalUse = row : column mPreviousCell = cell mPreviousItem = rowItem mPreviousItemRow = row return cell End Function
Function _cellBackgroundPaint(g As Graphics, row As Integer, column As Integer, textFrame as NSRectMBS, controlView as NSViewMBS) As Boolean return RaiseEvent CellBackgroundPaint (g, row, column, textFrame, controlView) End Function
Function _cellTextPaint(g As Graphics, row As Integer, column As Integer, x as Integer, y as Integer, textFrame as NSRectMBS, controlView as NSViewMBS) As Boolean return RaiseEvent CellTextPaint (g, row, column, x, y, textFrame, controlView) End Function
Function _clickedColumn() As Integer return mTableView.clickedColumn End Function
Function _clickedRow() As Integer return mTableView.clickedRow End Function
Function _compareRows(c1 as ListCellTV, c2 as ListCellTV, ByRef result as Integer) As Boolean return RaiseEvent CompareRows (c1, c2, c1._internalUse.Left, c2._internalUse.Left, c1._internalUse.Right, result) End Function
Function _constructContextualMenu(base as MenuItem, x as Integer, y as Integer, row as Integer, column as Integer, menu as NSMenuMBS) As Boolean dim res as Boolean mSelectionCache = mTableView.selectedRowIndexes() if row < 0 then // Header click res = RaiseEvent ConstructContextualMenuForHeader (base, x, y, column, menu) else // Cell click res = RaiseEvent ConstructContextualMenu (base, x, y, row, column, menu) end if return res End Function
Sub _contextualMenuAction(hitItem as MenuItem, row as Integer, column as Integer) call RaiseEvent ContextualMenuAction (hitItem, row, column) End Sub
Private Function _hasFocus() As Boolean return me.Window.Focus = me End Function
Sub _needsReload(needsRecalcColumnWidths as Boolean = false, enableXojoColumnWidths as Boolean = false) if _suppressReload then return end if clearRowCache if needsRecalcColumnWidths then // A column's WidthActual or WidthExpression was set mColumnWidthsDirty = true end if if enableXojoColumnWidths then // This means a WidthExpression (or ColumnWidths) property was set. This means the user // may want to use the dynamic width modifiers ("%" and "*") from Xojo instead of the default // NSTableView's columnAutoresizingStyle modes. mHasDynamicColumnWidths = true mTableView.columnAutoresizingStyle = NSTableViewMBS.NSTableViewLastColumnOnlyAutoresizingStyle end if if mReloadTimer = nil then mReloadTimer = new Timer AddHandler mReloadTimer.Action, AddressOf _reloadNow mReloadTimer.Period = 0 end if mReloadTimer.Mode = Timer.ModeSingle mReloadTimer.Reset End Sub
Private Sub _recalcColumnWidths() #pragma DisableBackgroundTasks mColumnWidthsDirty = false mTableView.columnAutoresizingStyle = NSTableViewMBS.NSTableViewLastColumnOnlyAutoresizingStyle dim totalWidth as Integer = me.ScrollView.documentVisibleRect.Width dim remainingWidth as Integer = totalWidth dim remainingMinWidth as Integer = totalWidth dim remainingMaxWidth as Integer = totalWidth dim asteriskCount, asteriskCountMin, asteriskCountMax as Double, asteriskColumnCount as Integer dim actualWidth(), minWidth(), maxWidth() as Integer dim colSpacing as Integer = mTableView.intercellSpacing.Width // extra space that every visible column occupies // first pass - determine the space occupied by non-asterisk columns for col as Integer = 0 to mCols.Ubound dim minW, maxW, actW as Integer, type as columnWidthTypes type = calcColumnWidth (columnWidthCalcTypes.actual, mCols(col).WidthExpression, totalWidth, mCols.Ubound-col, colSpacing, asteriskCount, remainingWidth, actW) call calcColumnWidth (columnWidthCalcTypes.minimum, mCols(col).MinWidthExpression, totalWidth, mCols.Ubound-col, colSpacing, asteriskCountMin, remainingMinWidth, minW) call calcColumnWidth (columnWidthCalcTypes.maximum, mCols(col).MaxWidthExpression, totalWidth, mCols.Ubound-col, colSpacing, asteriskCountMax, remainingMaxWidth, maxW) actualWidth.Append actW minWidth.Append minW maxWidth.Append maxW if type = columnWidthTypes.remain then asteriskColumnCount = asteriskColumnCount + 1 end if type = columnWidthTypes.absolute and actW <= 0 and minW <= 0 then // hide this column if not mCols(col).Hidden then mCols(col).Hidden = true end if else // unhide this column if mCols(col).Hidden then mCols(col).Hidden = false end if end if next // second pass - determine the space occupied by the remaining asterisk columns if asteriskCount > 0 then remainingWidth = Round (Max (0, remainingWidth - asteriskColumnCount * colSpacing)) dim asteriskWidth as Double = remainingWidth / asteriskCount dim lastAsteriskCol as Integer = -1 for col as Integer = 0 to mCols.Ubound if remainingWidth < 0 then remainingWidth = 0 dim value as Double dim type as columnWidthTypes = determineColumnWidthType (columnWidthCalcTypes.actual, mCols(col).WidthExpression, value) if type = columnWidthTypes.remain then dim columnWidth as Double = Round (value * asteriskWidth) actualWidth(col) = columnWidth lastAsteriskCol = col remainingWidth = remainingWidth - columnWidth asteriskCount = asteriskCount - value if asteriskCount <= 0 then exit end if next if lastAsteriskCol >= 0 then actualWidth(lastAsteriskCol) = actualWidth(lastAsteriskCol) + remainingWidth end end if // finally, set the actual widths in the table view mIgnoreTileEvents = true for col as Integer = 0 to mCols.Ubound dim c as ListColumnTV = mCols(col) c.minWidth = minWidth(col) c.maxWidth = maxWidth(col) c.width = actualWidth(col) next RaiseEvent DidResizeCells() finally mIgnoreTileEvents = false End Sub
Private Sub _reloadNow(t as Timer = nil) if mReloadTimer <> nil then mReloadTimer.Mode = Timer.ModeOff mIgnoreTileEvents = false mInsideHeaderDrag = false if mColumnWidthsDirty then mColumnWidthsDirty = false if mHasDynamicColumnWidths then // A column's WidthExpression had been set, so we want to re-adjust the columns accordingly, once _recalcColumnWidths() end if end if mReloadCount = mReloadCount + 1 clearRowCache mTableView.reloadData if not mMaintainColumnWidths then mTableView.columnAutoresizingStyle = mDefaultColumnAutoresizingStyle end End Sub
Private Function _rowItemForRow(row as Integer) As ListRowTV dim rowItem as ListRowTV = mDataSource.DataSource_RowItem (row) return rowItem End Function
Function _selectedRow() As Integer return mTableView.selectedRow End Function
Sub _sort() dim sorters() as NSSortDescriptorMBS = mTableView.sortDescriptors if sorters.Ubound < 0 then return dim firstSorter as NSSortDescriptorMBS = sorters(0) dim col as Integer = mTableView.columnWithIdentifier(firstSorter.key) if RaiseEvent SortColumn (col) then // user's event code handled the sorting return end if dim c as Integer = mReloadCount mDataSource.DataSource_SortRows // shall use me.TableView.sortDescriptors if c = mReloadCount then _reloadNow End Sub
Private Sub addItem(txt as String, asFolder as Boolean) if mDataSource isA ListBoxTV_Support.ListBoxTVStaticOutlineDataSource then dim idx as Integer if asFolder then idx = ListBoxTV_Support.ListBoxTVStaticOutlineDataSource(mDataSource).AddFolder (mCurrentRootItem, txt) else idx = ListBoxTV_Support.ListBoxTVStaticOutlineDataSource(mDataSource).AddRow (mCurrentRootItem, txt) end if mLastAddedIndex = idx else break // If you have assigned your own DataSource, then you cannot use AddRow() as that makes little sense end if _needsReload ' we reload lazily so that multiple calls to AddRow won't cause many reloads End Sub
Private Sub addItems(items() as String) dim col0 as String if items.Ubound >= 0 then col0 = items(0) end if addItem col0, false if mDataSource isA ListBoxTV_Support.ListBoxTVStaticOutlineDataSource then for column as Integer = 1 to items.Ubound _cell(mLastAddedIndex,column).text = items(column) next end if End Sub
Private Function calcColumnWidth(calcType as columnWidthCalcTypes, expr as String, totalWidth as Integer, colsLeft as Integer, extraSpace as Integer, ByRef asterisksInOut as Double, ByRef remainWidthInOut as Integer, ByRef widthOut as Integer) As columnWidthTypes dim space as Double dim value as Double dim type as columnWidthTypes = determineColumnWidthType (calcType, expr, value) if type = columnWidthTypes.absolute then space = Round (value) elseif type = columnWidthTypes.percentage then space = Round (totalWidth * value / 100) else asterisksInOut = asterisksInOut + Max (1, value) end if remainWidthInOut = remainWidthInOut - space if space > 0 then // if this column occupies some space, the Cell will actually add some more pixels, which we need to remove from the remaining space remainWidthInOut = remainWidthInOut - extraSpace if remainWidthInOut < 0 and space > -remainWidthInOut then space = space + remainWidthInOut remainWidthInOut = 0 end if end if widthOut = space return type End Function
Private Sub clearRowCache() mPreviousCell = nil mPreviousItem = nil End Sub
Private Function determineColumnWidthType(calcType as columnWidthCalcTypes, expr as String, ByRef value as Double) As columnWidthTypes #pragma DisableBackgroundTasks expr = expr.Trim if expr = "" then if calcType = columnWidthCalcTypes.actual then value = 1 return columnWidthTypes.remain elseif calcType = columnWidthCalcTypes.maximum then value = 100 return columnWidthTypes.percentage else value = 0 return columnWidthTypes.absolute end if end if dim number as Double, pos as Integer for pos = 1 to expr.Len dim ch as Integer = expr.Mid(pos,1).Asc if (ch < 48 or ch > 57) and ch <> 46 then ' not a digit nor a period (.) exit end if next number = expr.Left(pos-1).Val expr = expr.Mid(pos).Trim if expr = "" then ' absolute value value = number return columnWidthTypes.absolute elseif expr = "%" then ' percentage value = number return columnWidthTypes.percentage else ' remaining space value = Max (1, number) return columnWidthTypes.remain end if End Function
Private Sub didCollapse(rowItem as ListRowTV) RaiseEvent CollapseRow (mTableView.rowForItem (rowItem)) mDataSource.DataSource_Collapse (rowItem) End Sub
Private Sub expand(rowItem as ListRowTV) if mTableView.isItemExpanded(rowItem) then return end if dim c as Integer = mExpandCounter mTableView.expandItem rowItem, false if c = mExpandCounter then // Did not work - we need to reload the list first if mExpanding then // We have to postpone the Expand because we reloading the list during an expansion messes up the TableView' state mPendingExpands.Append rowItem else _reloadNow mTableView.expandItem rowItem, false if c = mExpandCounter then break ' something went wrong end if end if end if End Sub
Private Sub expandBegin(rowItem as ListRowTV) mExpanding = true mExpandCounter = mExpandCounter + 1 mExpandLevel.Append mCurrentRootItem mCurrentRootItem = rowItem dim currentRow as Integer = mTableView.rowForItem (rowItem) mLastAddedIndex = currentRow mDataSource.DataSource_Expand (rowItem) RaiseEvent ExpandRow (currentRow) End Sub
Private Sub expandEnd(rowItem as ListRowTV) if rowItem <> mCurrentRootItem then break ' huh?! end if mCurrentRootItem = mExpandLevel.Pop mExpanding = false while mPendingExpands.Ubound >= 0 dim item as ListRowTV = mPendingExpands(0) mPendingExpands.Remove 0 me.expand item wend End Sub
Private Function handleMouseDown(x as Integer, y as Integer, modifiers as Integer) As Boolean if RaiseEvent MouseDown (x, y) then return true end #if Target64Bit declare function frameOfCellAtColumn_Row lib "Cocoa" selector "frameOfCellAtColumn:row:" (o as Integer, col as Integer, row as Integer) as NSRect64 #else declare function frameOfCellAtColumn_Row lib "Cocoa" selector "frameOfCellAtColumn:row:" (o as Integer, col as Integer, row as Integer) as NSRect32 #endif dim loc0 as new NSPointMBS (x, y) dim loc as NSPointMBS = mTableView.convertPointFromView (loc0, me.ScrollView) dim row as Integer = mTableView.rowAtPoint (loc) dim col as Integer = mTableView.columnAtPoint (loc) dim cellFrame as NSRectMBS = mTableView.frameOfCellAtColumnRow (col, row) return RaiseEvent CellClick (row, col, loc.x - cellFrame.x, loc.y - cellFrame.y) End Function
Private Function itemIsSameOrParentOf(item as NSOutlineViewItemMBS, child as NSOutlineViewItemMBS) As Boolean while child <> nil if item = child then return true end if child = mTableView.parentForItem (child) wend End Function
Private Function rowsFromItems(items() as NSOutlineViewItemMBS) As Integer() dim res() as Integer for each item as NSOutlineViewItemMBS in items dim row as Integer = mTableView.rowForItem (item) res.Append row next return res End Function
Private Sub setDelayedProperties() // Handle some delayed settings (appears to be necessary with Real Studio 2012 but not with Xojo 2016) mHadOpenEvent = true me.ColumnCount = mDelayedColumnCount me.AutoHideScrollBars = mDelayedHideScrollers me.ColumnsResizable = mDelayedColumnResizing me.RequiresSelection = mDelayedRequiresSelection me.ScrollbarHorizontal = mDelayedHorScroller me.ScrollbarVertical = mDelayedVerScroller me.SelectionType = mDelayedSelectionType me.ColumnWidths = mDelayedColumnWidths End Sub
Private Sub setupFromInitialValue() dim rows() as String = ReplaceLineEndings(me.InitialValue, EndOfLine).Split(EndOfLine) if me.HasHeading and rows.Ubound >= 0 then dim cols() as String = rows(0).Split(Chr(9)) for i as Integer = 0 to Min (cols.Ubound, me.ColumnCount-1) me.Heading(i) = cols(i) next rows.Remove 0 end if for each row as String in rows dim cols() as String = row.Split(Chr(9)) me.AddRow cols(0) for i as Integer = 1 to Min (cols.Ubound, me.ColumnCount-1) me.Cell(mLastAddedIndex, i) = cols(i) next next End Sub
Note "About"
This is a (nearly complete) replacement for Xojo's ListBox control, for Mac OS X. It requires MBS Plugins 17.1 (March 9, 2017 or later) Advantages over Xojo's ListBox: • Uses Apple's native NSTableView control, with its proper appearance, hence... • Looks better in a Mac app. • Column resizing works better (observing minimum widths and showing a horizontal scrollbar when necessary). • Support column reordering. • Supports right-clicks (CMM clicks) on the headers. • Correct handling of multiple selected rows, including right-clicks. • Offers more customization options (e.g., you get extra Events from the NSTableView). • ConstructContextualMenu works as it should (i.e. for right clicks and ctrl-clicks). • Faster. • Open Source. Disadvantages: • Mac-only. • No custom drawing in cells via ...Paint events (but you can instead use NSGraphicsMBS functions). • No support for Database Binding. • Limited grid line options. • A few functions where a -1 parameter designates ALL rows or columns do not work (leading to an OutOfRangeException). To use it, copy the entire "ListBox_NSOutlineView" folder into your project, then change the Super class of your ListBox controls to ListBoxTV, and see if your project still compiles and runs. If you're using the CellBackgroundPaint or CellTextPaint events, see if you're only using it to change the color or font of the cells and text. See the note "Handling CellBackgroundPaint and CellTextPaint" for a solution.
Note "Attn sortDescriptorsDidChange"
The sortDescriptorsDidChange event is not implemented here because we handle sorting in the tableView's didClickTableColumn event, which is called after this event.
Note "Authors and Copyright"
Initial author (Feb 2017): Thomas Tempelmann, tempelmann@gmail.com This code is free, i.e. no contributing author may claim rights (or copyright) on it. If you add new features, please send them back to Thomas for redistribution. Adding a test case for the new feature would be nice, too.
Note "Handling CellBackgroundPaint and CellTextPaint"
If you implement either of these events to draw custom text or images, they won't work, because the "g as Graphics" parameter will be nil (that's a limitation caused by Xojo Inc. because their Plugin API does not supply the needed code for making use of the Graphics class). If you only want to have a particular background or text color, you can instead preset them like this: myListbox.CellStyle (row, col).TextColor = ... myListbox.CellStyle (row, col).BackgroundColor = ...
Note "Handling contextual clicks (right click; ctrl-click)"
Since the Xojo ListBox's ConstructContextualMenu event does not handle right clicks (or ctrl-clicks) correctly with multiple selections, you may have tried to ignore the event and instead handle such clicks in the MouseDown event by checking "IsContextualClick". You may still do that. But if you plan to use the ConstructContextualMenu, you need to set the ListBoxTV's control's UseContextualClickEvent property to True, e.g. right in the Window layout editor for the control, so that ConstructContextualMenu gets called as desired. You should do that (i.e. set UseContextualClickEvent=true and use ConstructContextualMenu) over checking for IsContextualClick in the MouseDown event in order to have proper behavior, especially when you allow multi-selection in the Listbox.
Note "NSView vs. NSCell based TableView cells"
By default, the TableView is in NSCell mode, which works with a limited set of predefined layout choices. The code handling this layout is found in the ListBoxTV.willDisplayCell() event. Alternatively, you can supply your own layout by returning a custom NSViewMBS from the ListBoxTV.view() event. If the view() event is added, the willDisplayCell() event becomes unused automatically. So, if you experiment with returning an NSView and are not happy with it, you need to remove the view() event entirely again to revert to using the NSCell mode. As a simple example, here's how to put a button into every cell. Place the following code into the view() event: dim button as new NSButtonMBS button.Title = "Button" return button To add multiple views, see the forum: https://forum.xojo.com/49354-listboxtv-mbs-plugins
Note "On-demand Cell values (custom DataSource)"
If you have a lot (thousands) of rows, the performance of Xojo's ListBox will notably decrease. With this ListBoxTV, you can prevent this. The solution is to not add your rows and cells beforehand, by using AddRow(), but instead just tell the Listbox how many rows you have. The TableView class will then show an adequate scrollbar for the entire number of rows, but will only ask you to provide the data for the rows and cells it currently shows in the window, which will be usually only a few dozen values. If the user scrolls the list, your code will then be asked again for only those new cells, and so on. To accomplish that, implement the ListBoxTVDataSource interface. Either implement them in a new class, or add them to your window or other class that you already maintain for your data. You do not need to add code for all its functions, which all begin with "DataSource_". Most of them you may leave empty, unless you want to support sorting, row dragging, column dragging or editing. Then set the listbox's DataSource property to your class that implements the ListBoxTVDataSource interface. The minimum you have to add code to are: • DataSource_ChildCount (rowItem as ListRowTV) as Integer Here you need to return the total number of rows you want to be able to show. If the list is hierarchical, then check the rowItem parameter - if it's nil, it means the root (top) level, so you'd return the number of rows at that level, otherwise the child count of the given ListRowTV node. • DataSource_ChildItem (rowItem as ListRowTV, index as Integer) as ListRowTV Here you need to return a ListRowTV object (or a subclass of it). Again, if rowItem is nil, you're at the root level. Otherwise, it's the node for one of your previously returned ListRowTV objects. This lets you build a tree of your hierarchical list. • DataSource_Cell (row as Integer, column as Integer) as ListCellTV This returns an object of type ListCellTV, which contains the Text property for the cell's display, as well as other properties such as Indentation, Alignment etc. If you want to keep it simple, return this: return new ListCellTV ("here goes the cell's text") • DataSource_RowItem (row as Integer) as ListBoxTV This has to return the row item for the currently visible listbox row. This means you have to be able to tell which ListRowTV appears at which row. If you have a flat list, this is equal to calling DataSource_ChildItem(nil, row). For a hierarchical list, you need to iterate over all shown rows, counting the visible children, until you reach the row in question. Now, whenever you know the listbox needs to be refreshed, i.e. it should call your DataSource_ChildCount(), DataSource_ChildItem() and DataSource_Cell() functions again, call the listbox's Reload() function.
Note "To Do"
Not working, yet: • EnableDragReorder does not work right, yet: DataSource_MoveRows can't distinguish whether one wants to insert into the last row of a folder or into the row following the folder's end. To fix that, not the dest row but the parent node and child index needs to be passed to it. • Row Insertion. • Font attributes (bold, italic) for entire list defaults and for individual cells. Anything else? Let me (TT) know!
Note "Version History"
Released Versions (oldest first) 1 - (16 Mar 2017) • Fixes Sort operation. • Makes all DataSource functions atomic, by using "#pragma DisableBackgroundTasks". 2 - (14 Aug 2018) • Fixes a bug in ListBoxTV.ListCount(), avoiding potentially outdated values. • Improvements to "row drag reorder" handling. • New methods BeginUpdates() and EndUpdates() for more efficient updating. • New: ListBoxTV.RemoveRow(). • Custom sorting implemented. • Various bug fixes. • New Note in ListBoxTV about NSView vs NSCell based TableView cells. 3 - (16 Aug 2018) • Should now work in 64 bit builds (by fixing NSRect struct usage) 4 - (18 Aug 2018) • Fixes the vertical scrollbar gap no matter what the System Preferences "General" / "Show scroll bars" is set to. 5 - (20 Aug 2018) • Fixes placement of the ContainerControl (in some circumstance, its original would get moved to negative offsets). • Can now add multiple columns in one AddRow() call. • Renames interface function DataSource_ItemCount() to DataSource_ChildCount().
Property DefaultRowHeight As Integer
Property InitialValue As String
Property _suppressReload As Boolean
Property Private mCols() As ListColumnTV
Property Private mColumnWidthsDirty As Boolean
Property Private mContextMenu As ListBoxTV_Support.ListBoxTVContextMenu
Property Private mCurrentRootItem As ListRowTV
Property Private mDataSource As ListBoxTVDataSource
Property Private mDebugState As Integer
Property Private mDefaultColumnAutoresizingStyle As Integer
Property Private mDelayedColumnCount As Integer
Property Private mDelayedColumnResizing As Boolean
Property Private mDelayedColumnWidths As String
Property Private mDelayedHideScrollers As Boolean
Property Private mDelayedHorScroller As Boolean
Property Private mDelayedRequiresSelection As Boolean
Property Private mDelayedSelectionType As Integer
Property Private mDelayedVerScroller As Boolean
Property Private mEnableDrag As Boolean
Property Private mEnableDragReorder As Boolean
Property Private mExpandCounter As Integer
Property Private mExpandLevel() As ListRowTV
Property Private mExpanding As Boolean
Property Private mHadOpenEvent As Boolean
Property Private mHasDynamicColumnWidths As Boolean
Property Private mHeaderView As NSTableHeaderViewMBS
Property Private mIgnoreTileEvents As Boolean
Property Private mInsideHeaderDrag As Boolean
Property Private mLastAddedIndex As Integer
Property Private mMaintainColumnWidths As Boolean
Property Private mOrigIndentationPerLevel As Integer
Property Private mPendingExpands() As ListRowTV
Property Private mPreviousCell As ListCellTV
Property Private mPreviousItem As Object
Property Private mPreviousItemRow As Integer
Property Private mReloadCount As Integer
Property Private mReloadTimer As Timer
Property Private mSelectionCache As NSIndexSetMBS
Property Private mSelectionType As Integer
Property Private mSelfRef As WeakRef
Points to itself. This is an optimization so that we don't have to create multiple WeakRefs in each child object
Property Private mTableView As NSOutlineViewMBS
Property Private mTextFont As String
Property Private mTextSize As Integer
Property Private mUseContextualClickEvent As Boolean
Structure NSRect32 x as Single y as Single w as Single h as Single End Structure
Structure NSRect64 x as Double y as Double w as Double h as Double End Structure
End Class
Module ListBoxTV_Support
Protected Function convertOSType(t as String) As String if t.Len = 4 then // convert OSType to UTI t = UTTypeMBS.CreatePreferredIdentifierForTag (UTTypeMBS.kUTTagClassOSType, t, UTTypeMBS.kUTTypeData) end if return t End Function
Protected Function trimPeriod(s as String) As String if s.Right(1) = "." then return s.Left (s.Len-1) else return s end if End Function
End Module
Class ListColumnTV Inherits NSTableColumnMBS
ComputedProperty Alignment As Integer
Sub Set() if mAlignment <> value then mAlignment = value owner._needsReload() end if End Set
Sub Get() return mAlignment End Get
End ComputedProperty
ComputedProperty AlignmentOffset As Integer
Sub Set() if mAlignmentOffset <> value then mAlignmentOffset = value owner._needsReload() end if End Set
Sub Get() return mAlignmentOffset End Get
End ComputedProperty
ComputedProperty ColumnIndex As Integer
Sub Get() return mColumnIndex End Get
End ComputedProperty
ComputedProperty MaxWidthActual As Integer
Sub Set() mMaxWidthExpr = Str(value,"#") owner._needsReload(true) me.maxWidth = value End Set
Sub Get() return Round (me.maxWidth) End Get
End ComputedProperty
ComputedProperty MaxWidthExpression As String
Sub Set() mMaxWidthExpr = value owner._needsReload(true, true) End Set
Sub Get() return mMaxWidthExpr End Get
End ComputedProperty
ComputedProperty MinWidthActual As Integer
Sub Set() mMinWidthExpr = Str(value,"#") owner._needsReload(true) me.minWidth = value End Set
Sub Get() return Round (me.minWidth) End Get
End ComputedProperty
ComputedProperty MinWidthExpression As String
Sub Set() mMinWidthExpr = value owner._needsReload(true, true) End Set
Sub Get() return mMinWidthExpr End Get
End ComputedProperty
ComputedProperty SortDirection As Integer
Sub Set() if mSortDirection <> value then mSortDirection = value owner._needsReload() end if End Set
Sub Get() return mSortDirection End Get
End ComputedProperty
ComputedProperty UserResizable As Boolean
Sub Set() me.Resizable = value End Set
Sub Get() return me.Resizable End Get
End ComputedProperty
ComputedProperty WidthActual As Integer
Sub Set() mWidthExpr = Str(value,"#") owner._needsReload(true) me.width = value End Set
Sub Get() return Round (me.width) End Get
End ComputedProperty
ComputedProperty WidthExpression As String
Sub Set() mWidthExpr = value owner._needsReload(true, true) End Set
Sub Get() return mWidthExpr End Get
End ComputedProperty
Sub Constructor(ownerRef as WeakRef, colNum as Integer) mOwner = ownerRef mColumnIndex = colNum static lastID as Integer lastID = lastID + 1 dim id as String = Str(lastID,"#") ' Since columns can be reordered, id can't identify the column index, but only ColumnIndex can. super.Constructor (id) const NSCaseInsensitiveSearch = 1 const NSLiteralSearch = 2 const NSBackwardsSearch = 4 const NSAnchoredSearch = 8 const NSNumericSearch = 64 ' -> smart sort where numbers get ordered properly const NSDiacriticInsensitiveSearch = 128 const NSWidthInsensitiveSearch = 256 const NSForcedOrderingSearch = 512 // Set up default sort handling, for when the ListboxTV.SortColumn and ListboxTV.CompareRows events are not used dim opts as Integer = NSCaseInsensitiveSearch + NSDiacriticInsensitiveSearch + NSNumericSearch // Install the primary and secondary sort descriptors dim sorter as NSSortDescriptorMBS = NSSortDescriptorMBS.sortDescriptorWithKeyWithCompare ("", true, opts) ' this sorter is used when the CompareRows() event returns false mSortHandler = new ListRowComparator (mOwner, me, sorter) ' this is our internal sorter that invokes CompareRows() and falls back to the above sorter me.sortDescriptorPrototype = mSortHandler // Set some UI options dim cell as NSCellMBS = me.headerCell cell.wraps = false cell.lineBreakMode = NSParagraphStyleMBS.NSLineBreakByTruncatingTail End Sub
Function EffectiveType(rowItem as NSOutlineViewItemMBS) As Integer dim myCell as ListCellTV = owner._cell(rowItem, ColumnIndex) dim t as Integer = myCell.Type if t = ListBox.TypeDefault then return me.Type else return t end End Function
Sub SetFontAndSize(fontName as String, size as Double = 0) if fontName = "" or fontName = "System" or fontName = "SmallSystem" then me.headerCell.font = NSFontMBS.systemFontOfSize (size) else me.headerCell.font = NSFontMBS.fontWithName (fontName, size) end if End Sub
Sub _updateOwnIndex() mColumnIndex = me.tableView.columnWithIdentifier (me.identifier) End Sub
Private Function owner() As ListBoxTV return ListBoxTV (mOwner.Value) End Function
Property Tag As Variant
You may set this to anything you like - if the user drags the column to another position, this Tag will move along.
Property Type As Integer
Property Private mAlignment As Integer
Property Private mAlignmentOffset As Integer
Property Private mColumnIndex As Integer
Property Private mMaxWidthExpr As String
Property Private mMinWidthExpr As String
Property Private mOwner As WeakRef
ListBoxTV
Property Private mSortDirection As Integer
Property Private mSortHandler As NSSortDescriptorMBS
Property Private mWidthExpr As String
End Class
Class ListCellTV
ComputedProperty Picture As Picture
Sub Set() mPicture = value me.nsImage = new NSImageMBS (value, value.Mask(false)) End Set
Sub Get() return mPicture End Get
End ComputedProperty
Sub Constructor(text as String = "", checked as Boolean = false) me.Text = text me.Checked = checked End Sub
Property Alignment As Integer
Use ListBox.Align... constants here
Property BackgroundColor As Color = &cFFFFFFFF
Property Bold As Boolean
Property Checked As Boolean
Property Indentation As Integer
Property Italic As Boolean
Property Tag As Variant
Property Text As String
Property TextColor As Color = &c00000000
Property ToolTip As String
Property Type As Integer
Use ListBox.Type... constants here
Property _internalUse As Pair
This will be modified internally during Sort and Draw operations
Property Private mPicture As Picture
Property nsImage As NSImageMBS
End Class
Class ListRowTV Inherits NSOutlineViewItemMBS
Property tag As Variant
End Class
Class DragItemTV
Sub AddItem(x as Integer, y as Integer, w as Integer, h as Integer) mItems.Append new NSPasteboardItemMBS mCurrentItemIdx = mItems.Ubound End Sub
Private Function BestType(paramarray t() as String) As String return mItems(mCurrentItemIdx).availableTypeFromArray(t) End Function
Sub Constructor(pb as NSPasteboardMBS) mPboard = pb mItems = pb.pasteboardItems if mItems.Ubound < 0 then AddItem(0,0,0,0) end if Reset() End Sub
Sub FinishAddedItems() call mPboard.SetPasteboardItems mItems End Sub
Function FolderItem() As FolderItem dim f as FolderItem dim item as NSPasteboardItemMBS = mItems(mCurrentItemIdx) dim s as String = item.stringForType(NSPasteboardMBS.NSFilenamesPboardType).DefineEncoding(Encodings.UTF8) if s <> "" then f = PathToFolderItemMBS (s) else s = item.stringForType("public.file-url").DefineEncoding(Encodings.UTF8) f = new FolderItem (s, FolderItem.PathTypeURL) end if return f End Function
Sub FolderItem(assigns f as FolderItem) mItems(mCurrentItemIdx).stringForType("public.file-url") = f.URLPath.ConvertEncoding(Encodings.UTF8) End Sub
Function FolderItemAvailable() As Boolean return HasType(NSPasteboardMBS.NSFilenamesPboardType, "public.file-url") End Function
Function HasType(paramarray t() as String) As Boolean return mItems(mCurrentItemIdx).availableTypeFromArray(t) <> "" End Function
Function NextItem() As Boolean dim res as Boolean = true mCurrentItemIdx = mCurrentItemIdx + 1 if mCurrentItemIdx > mItems.Ubound then mCurrentItemIdx = 0 res = false end mTypes = mItems(mCurrentItemIdx).types return res End Function
Function Pboard() As NSPasteboardMBS return mPboard End Function
Function RawData(type as String) As String type = BestType (ListBoxTV_Support.convertOSType(type)) dim s as String = mItems(mCurrentItemIdx).stringForType(type) return s End Function
Sub RawData(type as String, assigns s as String) mItems(mCurrentItemIdx).stringForType(ListBoxTV_Support.convertOSType(type)) = s End Sub
Function RawDataAvailable(type as String) As Boolean return HasType (ListBoxTV_Support.convertOSType(type)) End Function
Private Sub Reset() mCurrentItemIdx = -1 call NextItem End Sub
Function Text() As String dim t as String = BestType (NSPasteboardMBS.NSPasteboardTypeString, NSPasteboardMBS.NSStringPboardType) dim s as String = mItems(mCurrentItemIdx).stringForType(t).DefineEncoding(Encodings.UTF8) return s End Function
Sub Text(assigns s as String) mItems(mCurrentItemIdx).stringForType(NSPasteboardMBS.NSPasteboardTypeString) = s.ConvertEncoding(Encodings.UTF8) End Sub
Function TextAvailable() As Boolean return HasType(NSPasteboardMBS.NSPasteboardTypeString, NSPasteboardMBS.NSStringPboardType) End Function
Property Private mCurrentItemIdx As Integer
Property Private mItems() As NSPasteboardItemMBS
Property Private mPboard As NSPasteboardMBS
Property Private mTypes() As String
End Class
Interface ListBoxTVDataSource
Function DataSource_Cell(rowItem as ListRowTV, column as Integer) As ListCellTV
Sub DataSource_CellDidUpdate(cell as ListCellTV, rowItem as ListRowTV, column as Integer, updatesText as Boolean, updatesChecked as Boolean)
Sub DataSource_CellWillUpdate(cell as ListCellTV, rowItem as ListRowTV, column as Integer, updatesText as Boolean, updatesChecked as Boolean)
Function DataSource_ChildCount(rowItem as ListRowTV) As Integer
Function DataSource_ChildItem(rowItem as ListRowTV, index as Integer) As ListRowTV
Sub DataSource_Collapse(rowItem as ListRowTV)
Function DataSource_CurrentRowCount() As Integer
Sub DataSource_DeleteAllRows()
Sub DataSource_Expand(rowItem as ListRowTV)
Function DataSource_IsExpandable(rowItem as ListRowTV) As Boolean
Sub DataSource_MoveColumn(fromIdx as Integer, toIdx as Integer)
Sub DataSource_MoveRows(fromRows() as Integer, toAboveRow as Integer)
Function DataSource_RowItem(row as Integer) As ListRowTV
Sub DataSource_SetColumnCount(colCount as Integer)
Sub DataSource_SortRows()
End Interface
Class ListDrawCellHandler Inherits CustomNSTextFieldCellMBS
Const ImageInset = 3
EventHandler Function drawWithFrame(cellFrame as NSRectMBS, controlView as NSViewMBS) As boolean dim textFrame as NSRectMBS = titleRectForBounds (cellFrame) dim lb as ListBoxTV = ListBoxTV(mOwner.Value) lb._suppressReload = true dim row as Integer = me.cell._internalUse.Left dim col as Integer = me.cell._internalUse.Right if not lb._cellBackgroundPaint (nil, row, col, textFrame, controlView) then // draw background for entire cell, even if the text is indented if me.backgroundColor <> nil then dim g as new NSGraphicsMBS g.setFillColor me.backgroundColor g.fillRect cellFrame end if end if if cell.nsImage <> nil then #if Target64Bit declare sub drawInRect lib "Cocoa" selector "drawInRect:fromRect:operation:fraction:respectFlipped:hints:" ( _ h as Integer, dst as NSRect64, src as NSRect64, operation as Integer, fraction as Double, respectFlipped as Boolean, hints as Ptr) dim dr, zeroRect as NSRect64 #else declare sub drawInRect lib "Cocoa" selector "drawInRect:fromRect:operation:fraction:respectFlipped:hints:" ( _ h as Integer, dst as NSRect32, src as NSRect32, operation as Integer, fraction as Single, respectFlipped as Boolean, hints as Ptr) dim dr, zeroRect as NSRect32 #endif dim img as NSImageMBS = cell.nsImage dr.w = img.width dr.h = img.height dr.x = cellFrame.x + ImageInset dr.y = cellFrame.y + (cellFrame.Height - dr.h) \ 2 drawInRect (img.Handle, dr, zeroRect, NSGraphicsMBS.NSCompositeSourceOver, 1, controlView.isFlipped, nil) end if if not lb._cellTextPaint (nil, row, col, 0, 0, textFrame, controlView) then superDrawWithFrame textFrame, controlView end if lb._suppressReload = false return true End EventHandler
EventHandler Function titleRectForBounds(rect as NSRectMBS) As NSRectMBS // Let's indent the text (based on the cell's or column's AlignmentOffset, set in ListBoxTV.dataCellForTableColumn) dim ofs as Integer = me.cell.Indentation if me.cell.nsImage <> nil then ofs = ofs + me.cell.nsImage.width + (2 * ImageInset) end if rect.X = rect.X + ofs rect.Width = rect.Width - ofs return rect End EventHandler
Sub Constructor(owner as WeakRef) mOwner = owner super.Constructor End Sub
Note "About"
This handles custom drawing for cells. Note: This only works for "normal" cells, not for Checkbox cells - they would need a different NSCell subclass implementation from MBS, which isn't available (yet).
Property cell As ListCellTV
Property Private mOwner As WeakRef
ListBoxTV
Structure NSRect32 x as Single y as Single w as Single h as Single End Structure
Structure NSRect64 x as Double y as Double w as Double h as Double End Structure
End Class
Class ListRowComparator Inherits NSSortDescriptorMBS
EventHandler Function Comparator(obj1 as variant, obj2 as variant) As Integer dim c1 as ListCellTV = obj1 dim c2 as ListCellTV = obj2 #if DebugBuild if c1._internalUse.Right <> column.ColumnIndex or c1._internalUse.Right <> c2._internalUse.Right then raise new RuntimeException ' is this happens, we forgot up update the Column somewhere #endif dim res as integer if owner._compareRows (c1, c2, res) then return res end declare function compareObjectTo lib "Cocoa" selector "compareObject:toObject:" (h as Integer, o1 as CFStringRef, o2 as CFStringRef) as Integer try res = compareObjectTo (mSorter.Handle, c1.Text, c2.Text) catch exc as ObjCException break end try return res End EventHandler
Sub Constructor(ownerRef as WeakRef, column as ListColumnTV, sorter as NSSortDescriptorMBS) super.Constructor (column.identifier, true) mOwner = ownerRef mColumn = new WeakRef(column) mSorter = sorter End Sub
Function column() As ListColumnTV return ListColumnTV (mColumn.Value) End Function
Private Function owner() As ListBoxTV return ListBoxTV (mOwner.Value) End Function
Property Private mColumn As WeakRef
ListColumnTV
Property Private mOwner As WeakRef
ListBoxTV
Property Private mSorter As NSSortDescriptorMBS
End Class
Class ListBoxTVContextMenuItem Inherits NSMenuItemMBS
EventHandler Sub Action() owner.MenuItemAction (me) End EventHandler
Sub Constructor(menu as ListBoxTVContextMenu, item as MenuItem) super.Constructor (item.Text, "") mOwner = new WeakRef (menu) me.Item = item End Sub
Private Function owner() As ListBoxTVContextMenu return ListBoxTVContextMenu (mOwner.Value) End Function
Property Item As MenuItem
Property Private mOwner As WeakRef
ListBoxTVContextMenu
End Class
Class ListBoxTVContextMenu Inherits NSMenuMBS
EventHandler Sub EnableMenuItems() me.Clear dim row as Integer = owner._clickedRow dim col as Integer = owner._clickedColumn if row < 0 then // Click in Header // clickedColumn is always -1 in this case, so we need to calculate it manually dim ev as NSEventMBS = NSApplicationMBS.sharedApplication.currentEvent dim globalLocation as NSPointMBS = ev.locationInWindow dim localLocation as NSPointMBS = owner.TableView.convertPointFromView (globalLocation, nil) localLocation.Y = 0 col = owner.TableView.columnAtPoint (localLocation) else // Click in row / cell if not owner.Selected (row) then // deselect all others if the right-click was not inside the current selection - this is to avoid confusion for the user owner.ListIndex = row end if end if dim base as new MenuItem if owner._constructContextualMenu (base, System.MouseX, System.MouseY, row, col, me) then for i as Integer = 1 to base.Count dim item as ListBoxTVContextMenuItem = new ListBoxTVContextMenuItem (me, base.Item(i-1)) mItems.Append item super.addItem item next end if mMenu = base mRow = row mCol = col End EventHandler
Sub Clear() super.removeAllItems redim mItems(-1) End Sub
Sub Constructor(ownerRef as WeakRef) super.Constructor mOwner = ownerRef End Sub
Sub MenuItemAction(sender as ListBoxTVContextMenuItem) dim item as MenuItem = sender.Item owner._contextualMenuAction item, mRow, mCol me.Clear End Sub
Private Function owner() As ListBoxTV return ListBoxTV (mOwner.Value) End Function
Property Private mCol As Integer
Property Private mItems() As ListBoxTVContextMenuItem
Property Private mMenu As MenuItem
Property Private mOwner As WeakRef
ListBoxTV
Property Private mRow As Integer
End Class
Class ListBoxTVStaticOutlineDataSource
Function AddFolder(rowItem as ListRowTV, txt as String) As Integer return insertItem (StaticListFolderTV(rowItem), -1, new StaticListFolderTV (mOwner, mColumnCount), txt) End Function
Function AddRow(rowItem as ListRowTV, txt as String) As Integer return insertItem (StaticListFolderTV(rowItem), -1, new StaticListRowTV (mOwner, mColumnCount), txt) End Function
Sub Constructor(ownerRef as WeakRef) mOwner = ownerRef mDataRoot = new StaticListFolderTV (mOwner, 0) End Sub
Function DataSource_Cell(rowItem as ListRowTV, column as Integer) As ListCellTV dim res as ListCellTV = StaticListRowTV(rowItem).cells(column) if res is nil then // This cell has not been accessed before - create it now res = new ListCellTV StaticListRowTV(rowItem).cells(column) = res end if return res End Function
Sub DataSource_CellDidUpdate(cell as ListCellTV, rowItem as ListRowTV, column as Integer, updatesText as Boolean, updatesChecked as Boolean) // Nothing to do here - it exists for use with custom implementations of the ListBoxTVDataSource interface End Sub
Sub DataSource_CellWillUpdate(cell as ListCellTV, rowItem as ListRowTV, column as Integer, updatesText as Boolean, updatesChecked as Boolean) // Nothing to do here - it exists for use with custom implementations of the ListBoxTVDataSource interface End Sub
Function DataSource_ChildCount(rowItem as ListRowTV) As Integer if rowItem = nil then rowItem = mDataRoot return StaticListFolderTV(rowItem).children.Ubound+1 End Function
Function DataSource_ChildItem(rowItem as ListRowTV, index as Integer) As ListRowTV if rowItem = nil then rowItem = mDataRoot if index <= StaticListFolderTV(rowItem).children.Ubound then return StaticListFolderTV(rowItem).children(index) end if End Function
Sub DataSource_Collapse(rowItem as ListRowTV) #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) invalidateRowsPast (rowItem) redim StaticListFolderTV(rowItem).children(-1) End Sub
Function DataSource_CurrentRowCount() As Integer enumerateRowsUpTo -1, nil return mRowItems.Ubound + 1 End Function
Sub DataSource_DeleteAllRows() #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) invalidateRowsStartingAt 0 redim mDataRoot.children(-1) End Sub
Sub DataSource_Expand(rowItem as ListRowTV) invalidateRowsPast (rowItem) End Sub
Function DataSource_IsExpandable(rowItem as ListRowTV) As Boolean return rowItem isA StaticListFolderTV End Function
Sub DataSource_MoveColumn(fromIdx as Integer, toIdx as Integer) mDataRoot.MoveColumn fromIdx, toIdx End Sub
Sub DataSource_MoveRows(fromRows() as Integer, toAboveRow as Integer) #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) if fromRows.Ubound < 0 then return fromRows.Sort dim firstAffectedRow as Integer = Min (fromRows(0), toAboveRow) dim items() as StaticListRowTV for each row as Integer in fromRows items.Append mRowItems(row).Right next dim destParent as StaticListFolderTV = mRowItems(toAboveRow).Left dim destItem as StaticListRowTV = mRowItems(toAboveRow).Right dim destIndex as Integer = destParent.children.IndexOf (destItem) for each item as StaticListRowTV in items dim itemParent as StaticListFolderTV = parentItem (item) dim itemIndex as Integer = itemParent.children.IndexOf (item) if itemParent = destParent then if itemIndex < destIndex then destIndex = destIndex - 1 end end if itemParent.children.Remove itemIndex destParent.children.Insert destIndex, item destIndex = destIndex + 1 next invalidateRowsStartingAt firstAffectedRow // Select the moved rows dim firstItem as StaticListRowTV = items(0) enumerateRowsUpTo -1, firstItem dim destStartRow as Integer = firstItem.row dim newRowNumbers() as Integer for i as Integer = 0 to fromRows.Ubound newRowNumbers.Append destStartRow + i next owner.SelectRows newRowNumbers #if DebugBuild self._DebugCheck #endif End Sub
Function DataSource_RowItem(row as Integer) As ListRowTV enumerateRowsUpTo (row, nil) if row < 0 or row > mRowItems.Ubound then return nil end if dim item as StaticListRowTV = mRowItems(row).Right if item.row < 0 then // Something went wrong internally. // Let's check this in the debugger by repeating the operation: break enumerateRowsUpTo (row, nil) end if return item End Function
Sub DataSource_SetColumnCount(colCount as Integer) mColumnCount = colCount dim newUbound as Integer = colCount-1 mDataRoot.SetColumnUbound newUbound End Sub
Sub DataSource_SortRows() #pragma DisableBackgroundTasks // so that the loop and redim remain atomic enumerateRowsUpTo -1, nil // enumerates all rows mDataRoot.Sort invalidateRowsStartingAt 0 // invalidates all rows End Sub
Sub RemoveRow(row as Integer, animOptions as Integer) #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) // update mRowItems to include the row enumerateRowsUpTo row, nil // get the item we want to delete dim item as StaticListRowTV = mRowItems (row).Right dim parent as StaticListFolderTV = mRowItems (row).Left dim indexInParent as Integer = parent.children.IndexOf (item) // remove it from the TableView dim tv as NSOutlineViewMBS = owner.TableView dim tvParent as NSOutlineViewItemMBS = parent if tvParent = mDataRoot then tvParent = nil tv.removeItemsAtIndexes NSIndexSetMBS.indexSetWithIndex (indexInParent), tvParent, animOptions // Invalidate mRowItems past the deleted item invalidateRowsPast item dim p as Pair = mRowItems.Pop item = p.Right item.row = -1 // remove the item from the parent container parent.children.Remove indexInParent End Sub
Sub _DebugCheck() // For debugging only. // Checks the integrity of the data #if DebugBuild 'dim row as Integer 'for each p as Pair in mRowItems 'dim item as StaticListRowTV = p.Right 'if item.row <> row then 'break 'return 'end if 'row = row + 1 'next #endif End Sub
Private Sub enumerateRowsUpTo(untilRow as Integer, untilItem as StaticListRowTV) #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) if untilRow >= 0 and mRowItems.Ubound >= untilRow then return end if dim parent as StaticListFolderTV dim item as StaticListRowTV if mRowItems.Ubound < 0 then parent = mDataRoot else dim p as Pair = mRowItems(mRowItems.Ubound) parent = p.Left item = p.Right #if DebugBuild if mRowItems.Ubound <> item.row then break ' this would mean we have a bug in the algorithm #endif end if do dim startIndex as Integer if item <> nil then startIndex = parent.children.IndexOf (item) + 1 end if if parent.EnumerateRows (mRowItems, startIndex, untilRow, untilItem) then return end if // continue with parent of last item in mRowItems if parent = mDataRoot then // we're through exit end if dim row as Integer = parent.row if row < 0 then break ' that would indicate a bug item = parent parent = mRowItems(row).Left loop End Sub
Private Sub fillUpToExactly(parent as StaticListFolderTV, prevItem as StaticListRowTV) #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) dim lastRow as Integer = mRowItems.Ubound dim lastItem as StaticListRowTV if lastRow >= 0 then lastItem = mRowItems(lastRow).Right end if if prevItem = mDataRoot then // We're adding the first item at the root invalidateRowsStartingAt 0 return end if lastItem = prevItem then // The perviously added item is the last child of this parent return end // The last item in mRowItems may still be the last item of the parent, but of a child of the parent, // so let's check that, too while lastRow >= 0 dim lastItemParent as StaticListFolderTV = mRowItems(lastRow).Left if lastItemParent = parent then return end if lastRow = lastItemParent.row wend // invalidate items past the one we're going to add to mRowItems if prevItem.row >= 0 then invalidateRowsPast prevItem end if // mRowItems needs to be filled up to this item enumerateRowsUpTo (-1, prevItem) End Sub
Private Function insertItem(parent as StaticListFolderTV, atIndex as Integer, newItem as StaticListRowTV, txt as String) As Integer #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) dim cell as ListCellTV = me.DataSource_Cell (newItem, 0) ' -> allocates the cell in the row object cell.Text = txt if parent = nil then parent = mDataRoot // To keep mRowItems, our array of known row-to-item mappings, intact, // we need to cut it down to the item/row that comes before the item we're // adding now. That means we'll locate the previous item of this parent, and // invalidate the items past it. We also need to advance mRowItems to that // previous item in case it's not in the array, yet. // Then we can add this new item to the mRowItems savely. dim prevItem as StaticListRowTV = parent.ChildAt (atIndex) if prevItem = nil then // The parent has no children yet -> the parent will thus be the previous item prevItem = parent end fillUpToExactly parent, prevItem mRowItems.Append parent : newItem dim row as Integer = mRowItems.Ubound parent.children.Append newItem newItem.row = row return row End Function
Private Sub invalidateRowsPast(rowItem as ListRowTV) #pragma DisableBackgroundTasks // so that the loop and redim remain atomic dim newUbound as Integer = StaticListRowTV(rowItem).row if rowItem <> mDataRoot then if newUbound < 0 then ' already invalidated return elseif newUbound > mRowItems.Ubound then break ' should never happen return elseif newUbound = mRowItems.Ubound then ' okay, no change return end if end if ' invalidate all the items past newUbound for i as Integer = newUbound+1 to mRowItems.Ubound dim item as StaticListRowTV = mRowItems(i).Right item.row = -1 next redim mRowItems (newUbound) End Sub
Private Sub invalidateRowsStartingAt(row as Integer) #pragma DisableBackgroundTasks // so that the loop and redim remain atomic dim newUbound as Integer = row - 1 if newUbound < mRowItems.Ubound then ' invalidate all the items past newUbound for i as Integer = row to mRowItems.Ubound dim item as StaticListRowTV = mRowItems(i).Right item.row = -1 next redim mRowItems (newUbound) end if End Sub
Private Function owner() As ListBoxTV return ListBoxTV (mOwner.Value) End Function
Private Function parentItem(rowItem as StaticListRowTV) As StaticListFolderTV return mRowItems (rowItem.row).Left End Function
Property Private mColumnCount As Integer
Property Private mDataRoot As StaticListFolderTV
Property Private mOwner As WeakRef
ListBoxTV
Property Private mRowItems() As Pair
Pair (parent StaticListFolderTV : item as StaticListRowTV) This is our cache of known Rows and their Items. We need this to determine the next Row number and also to look up a RowItem's row number and parent. Only invalidateRowsPast() may redim this array to make sure the invalidated rows get their row set to -1!
End Class
Class StaticListRowTV Inherits ListRowTV
EventHandler Function Description() As String '+++ temporarily disabled because it interferes with debugging during setup '// Lets return all cells' values, comma-separated 'dim res() as String 'for each c as ListCellTV in cells 'dim s as String = c.text 'if s.Len > 20 then 's = s.Left(20)+"…" 'end 'res.Append s 'next 'return "#"+Str(row)+" ["+Join(res, ", ")+"]" End EventHandler
EventHandler Function valueForKey(key as string) As Variant // This is only called when performing a sort using sort descriptors // (see ListBoxTVDataSource.sortDescriptorsDidChange) if me isA StaticListFolderTV then // we need to sort the children as well StaticListFolderTV (me).Sort end if dim col as Integer = owner.TableView.columnWithIdentifier(key) dim cell as ListCellTV = cells(col) cell._internalUse = me.row : col return cell End EventHandler
Sub Constructor(ownerRef as WeakRef, columnCount as Integer) mOwner = ownerRef me.row = -1 // -> unknown redim me.cells(columnCount-1) super.Constructor End Sub
Sub MoveColumn(fromIdx as Integer, toIdx as Integer) dim c as ListCellTV = me.cells(fromIdx) me.cells.Remove fromIdx me.cells.Insert toIdx, c End Sub
Sub SetColumnUbound(ub as Integer) redim me.cells(ub) End Sub
Protected Function owner() As ListBoxTV return ListBoxTV (mOwner.Value) End Function
Property cells() As ListCellTV
Property Private mOwner As WeakRef
ListBoxTV
Property row As Integer
End Class
Class StaticListFolderTV Inherits StaticListRowTV
Function ChildAt(idx as integer) As StaticListRowTV if children.Ubound >= 0 then if idx < 0 or idx > children.Ubound then ' last child return children (children.Ubound) else return children (idx) end if end if End Function
Function EnumerateRows(rowItems() as Pair, startIndex as Integer, stopAtRow as Integer, stopAfterItem as StaticListRowTV) As Boolean #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) // Returns true if specified row or item has been reached for idx as Integer = startIndex to me.children.Ubound dim child as StaticListRowTV = me.children(idx) rowItems.Append me : child child.row = rowItems.Ubound if child isA StaticListFolderTV then if StaticListFolderTV(child).EnumerateRows (rowItems, 0, stopAtRow, stopAfterItem) then return true end if end if if rowItems.Ubound = stopAtRow then // Do not move this before the recursion call, or it'll cause errors. // That's because enumerateRowsUpTo() starts with the next child, and therefore all this child's children need to have been enumerated already return true end if if child = stopAfterItem then return true end if next End Function
Function LastChild() As StaticListRowTV if children.Ubound >= 0 then return children (children.Ubound) end if End Function
Sub MoveColumn(fromIdx as Integer, toIdx as Integer) #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) super.MoveColumn fromIdx, toIdx for each child as StaticListRowTV in me.children child.MoveColumn fromIdx, toIdx next End Sub
Sub SetColumnUbound(ub as Integer) #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) super.SetColumnUbound ub for each child as StaticListRowTV in me.children child.SetColumnUbound ub next End Sub
Sub Sort() #pragma DisableBackgroundTasks // so that this code remains atomic (otherwise, we'd have to use a Semaphore) dim newRows() as NSOutlineViewItemMBS = NSOutlineViewItemMBS.sortedArrayUsingDescriptors (me.children, owner.TableView.sortDescriptors) for i as Integer = 0 to newRows.Ubound dim row as StaticListRowTV = StaticListRowTV(newRows(i)) me.children(i) = row next End Sub
Property children() As StaticListRowTV
End Class
End Project

See also:

Feedback, Comments & Corrections

The items on this page are in the following plugins: MBS MacControls Plugin.




Links
MBS FileMaker Plugins