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/Flat Only/ListBoxTV TableView
Class ListboxDemoWin Inherits Window
Const DynamicListCount = 25
Const StaticListCount = 20
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.NativePath
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 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
me.AddRow Format(row, "#.0")
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 (row, col) = Chr(col+65)+Str(Rnd*30,"#")
else
me.Cell (row, col) = Chr(col+65)+Str(row)
end if
next
next
for row as Integer = 1 to 8
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(6) = mSampleImage
me.CellType(6,0) = ListBox.TypeEditable
me.Cell(6,0) = "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
// Set up the dynamic DataSource
me.DataSource = new DynamicListBoxTVDataSource (me.DataSource, DynamicListCount)
me.Reload ' this loads the listbox immediately - otherwise there may be a little delay (due to the Timer in _Private_ListBoxTV._needsReload)
End EventHandler
EventHandler Function SortColumn(column as Integer) As Boolean
log "cocoa.SortColumn "+Str(column)
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
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 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.NativePath
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 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
me.AddRow Format(row, "#.0")
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 (row, col) = Chr(col+65)+Str(Rnd*30,"#")
else
me.Cell (row, col) = Chr(col+65)+Str(row)
end if
next
next
for row as Integer = 1 to 8
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(6) = mSampleImage
me.CellType(6,0) = ListBox.TypeEditable
me.Cell(6,0) = "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")
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()
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
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
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 ListBoxTV Inherits NSTableControlMBS
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
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.ListBoxTVStaticDataSource (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 IntercellSpacing As Integer
Sub Set()
dim ss as new NSSizeMBS
ss.Width = value
ss.Height = value
self.TableView.intercellSpacing = ss
End Set
End ComputedProperty
ComputedProperty ListCount As Integer
Sub Get()
return mDataSource.DataSource_RowCount
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
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
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 Sub DropObject(obj As DragItem, action As Integer)
break
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()
RaiseEvent Open()
End EventHandler
EventHandler Sub SelectionDidChange(notification as NSNotificationMBS)
mSelectionCache = mTableView.selectedRowIndexes()
RaiseEvent Change()
End EventHandler
EventHandler Function acceptDrop(info as NSDraggingInfoMBS, row as Integer, dropOperation as Integer) As Boolean
// Finishes the drop
dim pb as NSPasteboardMBS = info.draggingPasteboard
dim operationMask as Integer = info.draggingSourceOperationMask
// Is this a moved row?
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
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)
end if
return true ' -> drop was successful
end if
end if
// 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 EventHandler
EventHandler Function dataCell(tableColumn as NSTableColumnMBS, row as Int64) 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(row, 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
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 Sub mouseDownInHeaderOfTableColumn(tableColumn as NSTableColumnMBS)
if mMaintainColumnWidths then
mIgnoreTileEvents = true
mInsideHeaderDrag = true
end if
End EventHandler
EventHandler Function numberOfRowsInTableView() As Integer
return mDataSource.DataSource_RowCount()
End EventHandler
EventHandler Function objectValue(column as NSTableColumnMBS, row as Integer) As Variant
dim col as Integer = ListColumnTV(column).ColumnIndex
dim myCell as ListCellTV = _cell (row, col, true)
dim t as Integer = myCell.Type
if t = ListBox.TypeCheckbox or (t = ListBox.TypeDefault and ListColumnTV(column).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(value as Variant, column as NSTableColumnMBS, row as Integer)
dim col as Integer = ListColumnTV(column).ColumnIndex
dim myCell as ListCellTV = _cell (row, col)
dim isCheckbox as Boolean
dim t as Integer = myCell.Type
if t = ListBox.TypeCheckbox or (t = ListBox.TypeDefault and ListColumnTV(column).Type = ListBox.TypeCheckbox) then
isCheckbox = true
end
mDataSource.DataSource_CellWillUpdate (row, col, myCell, 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 (row, col, myCell, not isCheckbox, isCheckbox)
RaiseEvent CellAction (row, col)
End EventHandler
EventHandler Function shouldEditTableColumn(tableColumn as NSTableColumnMBS, row as Int64) As Boolean
dim t as Integer = ListColumnTV(tableColumn).EffectiveType(row)
return t = ListBox.TypeEditable or t = ListBox.TypeCheckbox
End EventHandler
EventHandler Function shouldReorderColumn(columnIndex as Int64, newColumnIndex as Int64) As Boolean
return not RaiseEvent DenyReorderColumn (columnIndex, newColumnIndex)
End EventHandler
EventHandler Function validateDrop(info as NSDraggingInfoMBS, proposedRow as Integer, dropOperation as Integer) As Integer
// Determines whether the current row is accepted as a drop target
if mEnableDragReorder then
if dropOperation = NSTableViewMBS.NSTableViewDropAbove then
dim operationMask as Integer = info.draggingSourceOperationMask
return operationMask
end if
end if
End EventHandler
EventHandler Sub willDisplayCell(cell as NSCellMBS, tableColumn as NSTableColumnMBS, row as Int64)
#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 (row, 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
if not RaiseEvent WillDisplayCell (row, col, myCell, cell, tableColumn) 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 writeRowsWithIndexes(rowIndexes as NSIndexSetMBS, pboard as NSPasteboardMBS) As Boolean
dim didAdd as Boolean
call pboard.clearContents
if mEnableDrag then
me.SelectRows rowIndexes.Values ' makes sure the dragged rows are selected because the DragRow event expects that
dim drag as new DragItemTV (pboard)
dim operationMask as Integer = NSDraggingInfoMBS.NSDragOperationEvery // Xojo uses NSDragOperationEvery, Real Studio used NSDragOperationAll_Obsolete
dim locally as Boolean = false
if RaiseEvent DragRow (drag, rowIndexes.firstIndex, 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 rows() as Integer = rowIndexes.Values()
dim mb as new MemoryBlock (4 * (rows.Ubound+1))
for i as Integer = 0 to rows.Ubound
mb.Int32Value (4*i) = rows(i)
next
pboard.dataForType (DragReorderPBType) = mb
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 AddRow(items() as String)
addItems items
End Sub
Sub AddRow(ParamArray items as String)
addItems items
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.ListBoxTVStaticDataSource (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()
super.Constructor
End Sub
Sub DeleteAllRows()
mDataSource.DataSource_DeleteAllRows
me.ScrollPosition = 0
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
Function Expanded(row as Integer) As Boolean
break ' this is not supported here - use the code from "ListBox TableView (Hierarchical).rbp" instead
End Function
Sub Expanded(row as Integer, assigns exp as Boolean)
break ' this is not supported here - use the code from "ListBox TableView (Hierarchical).rbp" instead
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 LastIndex() As Integer
return mLastAddedIndex
End Function
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
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 mDataSource.DataSource_RowTag(row)
End Function
Sub RowTag(row as Integer, assigns tag as Variant)
mDataSource.DataSource_RowTag(row) = 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 NSTableViewMBS
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
Function _cell(row as Integer, column as Integer, ignoreCache as Boolean = false) As ListCellTV
if not ignoreCache then
if mPreviousCell <> nil and mPreviousCell._internalUse.Left = row 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
dim cell as ListCellTV = mDataSource.DataSource_Cell (row, column)
if cell = nil then
// create an empty one
cell = new ListCellTV ()
end if
cell._internalUse = row : column
mPreviousCell = cell
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
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)
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
mTableView.reloadData
if not mMaintainColumnWidths then
mTableView.columnAutoresizingStyle = mDefaultColumnAutoresizingStyle
end
End Sub
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
mDataSource.DataSource_SortRows sorters
mTableView.reloadData
End Sub
Private Sub addItems(items() as String)
if mDataSource isA ListBoxTV_Support.ListBoxTVStaticDataSource then
dim col0 as String
if items.Ubound >= 0 then
col0 = items(0)
end if
ListBoxTV_Support.ListBoxTVStaticDataSource(mDataSource).AddRow col0
mLastAddedIndex = mDataSource.DataSource_RowCount-1
for column as Integer = 1 to items.Ubound
_cell(mLastAddedIndex,column).text = items(column)
next
else
break // If you have assigned your own DataSource, then you cannot use AddRow() as that makes little sense
end if
_needsReload
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 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 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 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
if me.Hierarchical then
break ' this is not supported here - use the code from "ListBox OutlineView.rbp" instead
end if
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"
Note "Attn sortDescriptorsDidChange"
Note "Authors and Copyright"
Note "Handling CellBackgroundPaint and CellTextPaint"
Note "Handling contextual clicks (right click; ctrl-click)"
Note "NSView vs. NSCell based TableView cells"
Note "On-demand Cell values (custom DataSource)"
Note "To Do"
Note "Version History"
Property DefaultRowHeight As Integer
Property Hierarchical As Boolean
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 mDataSource As ListBoxTVDataSource
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 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 mPreviousCell As ListCellTV
Property Private mReloadTimer As Timer
Property Private mSelectionCache As NSIndexSetMBS
Property Private mSelectionType As Integer
Property Private mSelfRef As WeakRef
Property Private mTableView As NSTableViewMBS
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