ListBox Column truncation

Back in the olden days of RB, if you wanted to truncate your text in a ListBox cell in the middle instead of at the end, you needed to write your own truncate routine and call it from the CellTextPaint event. However, I just discovered that using modern Xojo and doing this on Windows and Linux result in a forever recursive loop That sucks the CPU until you close the containing window. Here’s the code that I believe came from someone on the old NUG way back there.

In the CellTextPaint:

If column = 1 Then // Get text from the Cell TAG in col 0 Dim s As String = Me.CellTag(row, column) // Truncate text at center instead of the default at the end Me.Cell(row,column) = TruncateString(s, g.Width - 8, Me.TextFont, Me.TextSize, True) End If
The TruncateString method:

[code]// Truncates a string to the passed width (IN PIXELS), text font and size
// adds an ellipsis (…) at end or middle

If s = “” Then
Return s
End If

// Get string width in pixels
Dim sWidth As Integer = StringWidth( s, textFont, textSize )

// If string width is less or equal than the passed width, then return the string
If sWidth <= width Then Return s

Dim chars() As String = SplitB( s, “” ) // split s into chars
Dim iMax As Integer = Ubound( chars )
Dim newString As String

// Get width of ellipsis “…”
Dim eWidth As Integer = StringWidth( “…”, textFont, textSize )

// Get halfWidth
Dim halfWidth As Integer = width / 2

For i As Integer = 0 To iMax
newString = newString + chars( i )
Dim newWidth As Integer = StringWidth( newString, textFont, textSize )
If not middle Then
If newWidth + eWidth >= width Then
Return newString + “…”
End If
Else
If newWidth + eWidth >= halfWidth Then
newString = newString + “…”
Exit // exit loop
End If
End If
Next

If middle Then
// Get the end of the string and append it to new string
Dim endString As String
Dim leftWidth As Integer = StringWidth( newString, textFont, textSize )
For i As Integer = iMax DownTo 0
endString = chars( i ) + endString
Dim endWidth As Integer = StringWidth( endString, textFont, textSize )
If endWidth + leftWidth >= width Then
Return newString + endString
End If
Next
End If

Return s[/code]

A: Is this still required? Has an option been added to set the truncation to the middle?
2: Since the CellTextPaint is now a no-way option, does anyone know of a proper solution?

Or, if neither, does anyone know of a feature request for setting the truncate location? I don’t find anything, but I no longer assume that Feedback’s search is honest with me :D.

Since nothing that I searched for turned up anything, I’ve created a Feature Request:

<https://xojo.com/issue/55882>

Why would it?

How about this variant:

[code]
static col1width as integer

If column = 1 Then
if g.width <> col1width then // only if the column has been changed since last time
col1width = g.width

// Get text from the Cell TAG in col 0
Dim s As String = Me.CellTag(row, column)
// Truncate text at center instead of the default at the end
Me.Cell(row,column) = TruncateString(s, g.Width - 8, Me.TextFont, Me.TextSize, True)
end if
End If[/code]

Won’t setting the cell content in the CellTextPaint event trigger additional paint events?

A better solution might be to store the entire string in the cell and in the CellTextPaint event calculate and store the truncated text in the cell tag and then use DrawString to draw the contents of the cell tag?

I am not surprised at all. You’re telling it to change the Cell value during a Paint event. That tells the Listbox to draw again. You have no protection to prevent the Cell value from being set again so it happens infinitely. You need to implement that protection, or implement drawing the way you’re supposed to:

The only thing you should be doing in a Paint event is drawing. Calculate things that affect what gets drawn elsewhere.

That’s mostly what I was doing.

And that is my problem - thanks. I was so focused on the truncation operation that I missed the obvious :S .

As both you and Tim point out, the act of updating the Cell content is causing the CellTextPaint event to be called. I don’t know why that’s not reared it’s ugly head before now, since that code dates back to 2005.

The feature request for a truncation point choice - middle or end - is still a good idea.

So, Jeff’s idea seemed right, but the result is the same when the user is resizing the window (which is when TruncateString is called).

To map my needs case better, here is what I face and what I am trying to resolve:

By default, if the content of a cell exceeds the width of the cell, Xojo truncates the end of the string. While this is okay in some cases, in the case of displaying path information when components in the path may be identical up to or beyond the truncation makes it impossible for the user to tell multiple entries from one another. For example, this long path:

/Volumes/Linux Primary Disk/usr/share/libreoffice/share/config/images_tango.zip
Currently gets truncated to something like:

/Volumes/Linux Primary Disk/usr/share/libreoffic...
Unfortunately, if there are 20 files in the “config” folder, the user will just see that libreoffic… line 20 times. By truncating from the middle, that would become:

/Volumes/Linux Primary D.../config/images_tango.zip
This form allows the user to differentiate between those 20 different files.

My need to to modify the CellTextPaint so that I can externally call the TruncateString when the window (and thus the cell width) is changed. As implied above, I am storing the complete path in the cell’s CellTag property, so I can refer to its unchanged value.

You can figure out the width you need in CellTextPaint, just don’t re-set the Cell value if the truncated string is the same as the existing cell value.

That’s what I thought Jeff’s small change above would accomplish. I’ll look at it more closely.

[quote=439854:@Kevin Gale]Won’t setting the cell content in the CellTextPaint event trigger additional paint events?

A better solution might be to store the entire string in the cell and in the CellTextPaint event calculate and store the truncated text in the cell tag and then use DrawString to draw the contents of the cell tag?[/quote]

Or do this and just draw the string truncated instead of actually truncating it
That way if you retrieve the cell contents you cant get back “foo…bar” but the original unaltered string
ie

me.cell(1) = "somereallylongstringthatgetstruncated"

and somewhere later AFTER one redraw

if me.cell(1) = "somereallylongstringthatgetstruncated" then // and this is no longer true ???????

Just the appearance is changed

Okay - revisiting Jeff’s change is doing what it should and the CellTextPaint is only firing one extra time per call to TruncateString.

However, using DrawString instead of changing the cell content is not something that I’d considered because I’d been so intent on the Cell’s Text “content” rather than the info displayed (which, I just learned, don’t need to be the same thing). I’ll also give that a look-see and determine which is more effective.

Just a note to say that’s an excellent addition for your user! I don’t know how many times I’ve been bit by that same… say interface oversight in various programs (and invariably end up cursing the programmer for stuffing it into a non-expandable box. M$ I’m looking at you here).

Added your FR.

Considering the level of refactoring of a number of other places when the content of this listbox can be affected, the current mechanism with Jeff’s Static change makes the most sense. My CPU hovers around 3% on both Windows and Linux when the window containing the listbox is resized.