Paint icon in NSTableView

I’m struggling a bit to paint an icon in NSTableview because everything is upside down or whacky.

CellTextpaint:

const IconSize as Integer = 24
dim nsg as new NSGraphicsMBS
nsg.drawPicture(folder_full_light, 0, row * me.rowHeight + (me.rowHeight - IconSize/2)/ 2, IconSize, IconSize, 0, 0, folder_full_light.Width, folder_full_light.Height, NSGraphicsMBS.NSCompositeSourceOver, 1)

Results in:

Also notice the weird vertical alignment I had to do.

So I just rotate the icon:

const IconSize as Integer = 24
dim p as new Picture(IconSize * 2, IconSize * 2)
p.Graphics.DrawPicture(folder_full_light, 0, 0, IconSize * 2, IconSize * 2, 0, 0, folder_full_light.Width, folder_full_light.Height)
p = p.RotateImageAndMaskMBS(180)
dim nsg as new NSGraphicsMBS
nsg.drawPicture(p, 0, row * me.rowHeight + (me.rowHeight - IconSize/2)/ 2, IconSize, IconSize, 0, 0, p.Width, p.Height, NSGraphicsMBS.NSCompositeSourceOver, 1)

This results in a proper icon:

But the text isn’t vertically aligned. There I use intercellSpacing:

me.TableView.rowHeight = Fontsize * 1.5
dim n as NSSizeMBS = NSMakeSizeMBS(4, 15)
me.TableView.intercellSpacing = n

By trial and error I came up with a really odd formula for vertical offset because the intercellSpacing moved all the icons up:

const IconSize as Integer = 24
dim p as new Picture(IconSize * 2, IconSize * 2)
p.Graphics.DrawPicture(folder_full_light, 0, 0, IconSize * 2, IconSize * 2, 0, 0, folder_full_light.Width, folder_full_light.Height)
p = p.RotateImageAndMaskMBS(180)
dim nsg as new NSGraphicsMBS
dim VerticalOffset as double = (row * (me.rowHeight + 15)) + me.RowHeight/2
nsg.drawPicture(p, 0, VerticalOffset, IconSize, IconSize, 0, 0, p.Width, p.Height, NSGraphicsMBS.NSCompositeSourceOver, 1)

Is this normal or a bug for both the rotation of the icon and the weird vertical alignment formulas?

You are using NSGraphics to paint. In macOS, y coordinates are reversed, compared to Xojo graphics. I could imagine this has something to do with it.
But you are doing this on a NSTableView. Did you try to output the image in objectValue event handler? Works perfectly for me.

1 Like

What is the “objectValue event handler”? I just took some example code from the forum here and played around.

Found the events ObjecValue and SetObjectValue. But the docs for them are as clear as mud.

If the NSTableView is anything like the List control in SwiftUI, it’s designed to use views.

To provide a simple file list, I’d use a container view of sorts, inside would a NSImageView or Image (SwiftUI) and then next to it would be a NSTextField or Text (SwiftUI). SwiftUI has a Label view, which takes text and a SFSymbol name, I’ve not checked to see App Kit has something similar, but it probably does.

List( folders, selection: $multiSelection ) { folder in
    Label( folder.localizedFileName, systemImage: "folder.fill" )
}

If you wanted to make the list editable, it takes a little bit more code, but this will allow the customer to rearrange the list (and the array is auto updated to match), or delete items from the list, and even rename a folder.

List( $folders, editActions: [ .all ], selection: $multiSelection ) { $folder in
    HStack( alignment: .center, spacing: 4 ) {
        Image( systemName: "folder.fill" )
        TextField( "Folder name", $folder.localizedFileName )
    }
}

Thanks, Sam. But this is like Taiwanese to me. AFAIK it’s possible to use views but here I want a simple icon and not even a system one.

I got a little bit further with ObjectValue, SetObjectValue and WillDisplayCell. I made a column with icons. But I don’t know how to display the icon. The following code in WillDisplayCell gives an IllegalCastException:

if tableColumn isA TableColumnText then
  cell.title = TableColumnText(tableColumn).GetValue(row)
ElseIf tableColumn isA TableColumnIcon then
  'cell.image = NSImageMBS(TableColumnIcon(tableColumn).getValue(row))
end if

Project is here: https://www.mothsoftware.com/downloads/TableControl.zip

Maybe this helps: Some excerpts from a project I am working on, in this case with an NSOutlineView. But code is pretty the same for TableView.
From objectValue event handler:

Select Case tableColumn.identifier
Case kcap_Name
  var nsa As NSmutableAttributedStringMBS = NSAttributedStringMBS.attributedStringWithString(node.Name).mutableCopy
  // Usually coloring some characters here, otherwise a normal string can be returned.
  Return nsa
Case kcap_type
  var img As NSImageMBS
  If item IsA DataNode Then
    img = NSImageMBS.imageNamed("waveform.path.ecg.rectangle.fill")
  If img = Nil Then
    img = NSImageMBS.imageWithSystemSymbolName("waveform.path.ecg.rectangle.fill")
    img = img.imageWithTintColor(NSColorMBS.systemTealColor)
    Call img.setName "waveform.path.ecg.rectangle.fill"
  End If

 // ... and so on
  Return img
End Select

Which then looks like

1 Like

The setup might be important too. From the OutlineView.open event handler:

var col, ARcol As NSTableColumnMBS
col = New NSTableColumnMBS(kcap_type)
col.title = kcap_type
col.width = 35
col.resizingMask = col.NSTableColumnNoResizing
col.dataCell = New NSImageCellMBS(Nil) 
// So this column will display images.

Me.View.addTableColumn col

ARcol = New NSTableColumnMBS(kcap_NAme)
ARcol.title = kcap_Name
ARcol.Width = Me.Width - 90 - 90 - 52 - 35 - Me.View.intercellSpacing.Width * 5
ARcol.minWidth = 80
ARcol.maxWidth = 20000
ARcol.resizingMask = col.NSTableColumnAutoresizingMask
Me.View.addTableColumn ARcol
Me.View.outlineTableColumn = ARcol

...

Me.view.style = NSOutlineViewMBS.NSTableViewStylePlain
Me.View.usesAlternatingRowBackgroundColors = True

You don’t need to use SetObjectValue unless you want to modify the data. Basically both events are enough to display the view this way.

I didn’t see that cell.image needs an NSImageMBS. But this only crashes.

Function objectValue(column as NSTableColumnMBS, row as Integer) Handles objectValue as Variant
  if column = TextColumn then
    Return TextColumn.getValue(row)
  elseif column = IconColumn then
    Return new NSImageMBS(IconColumn.getValue(row))
  end if
End Function


Sub setObjectValue(value as Variant, column as NSTableColumnMBS, row as Integer) Handles setObjectValue
  if column = TextColumn then
    TextColumn.setValue(row, value)
  elseif column = IconColumn then
    IconColumn.setValue(row, value)
  end if
End Sub


Sub willDisplayCell(cell as NSCellMBS, tableColumn as NSTableColumnMBS, row as Int64) Handles willDisplayCell
  
  if tableColumn isA TableColumnText then
    cell.title = TableColumnText(tableColumn).GetValue(row)
  ElseIf tableColumn isA TableColumnIcon then
    cell.image = new NSImageMBS(TableColumnIcon(tableColumn).getValue(row))
  end if
End Sub

The setup of the NSTableview is not the problem.

But this piece is important:

2 Likes

Wahnsinn!!! This was what I was missing.

Reason 1001 why I like the abstraction of Xojo and hate the abstraction of anything in Objective C or Swift.

1 Like

Understandable. Took me some time to get behind it too. And now I love the performance which Xojo’s model cannot compare with. Is it one row or millions of it you want to display? Does not matter, will not delay anything.

This is meant to display in things we are not supposed to mention. I want a modern type of window with a modern listbox.

And I need to optimize the vertical alignment a bit.

1 Like

Thank you Ulrich for helping out.

2 Likes

FYI, this is changing, largely thanks to the iOSification of the macOS (which uses co-ordinates in the same direction as Xojo).

AppKit and Core Graphics. The default coordinate system has its origin at the lower left of the drawing area; positive values extend up and to the right from it. AppKit provides programmatic support for “flipping” a view’s coordinate system.

UIKit and SwiftUI. The default coordinate system has its origin at the upper left of the drawing area, and positive values extend down and to the right from it. Although UIKit provides no programmatic support for “flipping” a coordinate system, you can accomplish this using Core Graphics functions.

SwiftUI doesn’t use a fixed co-ordinate system for control layout (it can be overridden, but there’s a reason why it is this way).

1 Like

Effing heck! There is one big problem using NSTableControl: the inset style is really tricky. The old version of my simple table control showed the inset style always. Now with the icon the selection is square again:


After resizing the window manually I get the inset style. But this doesn’t work in code. Back to the drawing board.

It doesn’t happen often, but I’ve run into the odd problem where something doesn’t draw properly, until the window is resized. So I’ve put a hack in the Window Opening event, like so:

// jiggle the window a little
Self.Width = (Self.Width + 1)
Self.Height = (Self.Height + 1)
Self.Width = (Self.Width - 1)
Self.Height = (Self.Height - 1)

I know it’s not a proper solution, but at least it usually happens so fast that you can’t see it.

1 Like

Are you setting the tableViewStyle?

I dunno what the property is called in App Kit, sorry.

It’s NSTableViewStyleInset:

if SystemInformationMBS.isMonterey then
  me.View.style = NSTableViewMBS.NSTableViewStyleInset
end if

The problem is that Xojo and the MBS plugin play tug with doing the selection for the listbox. In many cases the selection is done by Xojo and is then in the old style. After resizing the window the MBS plugin does the correct selection in the inset style.

Jiggling one pixel does not work. But jiggling 30 pixels does the trick.

1 Like