ListBox Column truncation

  1. 3 months ago

    Tim J

    Jun 3 Pre-Release Testers, Xojo Pro Dehydrating in AZ

    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:

    // 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

    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.

    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

    Why would it?

    How about this variant:

    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
  2. Tim J

    Jun 3 Pre-Release Testers, Xojo Pro Dehydrating in AZ

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

    Feedback Case #55882

  3. Jeff T

    Jun 3 Pre-Release Testers Answer Midlands of England, Europe

    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

    Why would it?

    How about this variant:

    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
  4. Kevin G

    Jun 4 Pre-Release Testers, Xojo Pro Gatesheed, England

    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?

  5. Tim P

    Jun 4 Pre-Release Testers Rochester, NY
    Edited 3 months ago

    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.

  6. Tim J

    Jun 4 Pre-Release Testers, Xojo Pro Dehydrating in AZ

    @Kevin G 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?

    That's mostly what I was doing.

    @Jeff T How about this variant:

    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.

  7. Tim J

    Jun 4 Pre-Release Testers, Xojo Pro Dehydrating in AZ

    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.

  8. Tim P

    Jun 4 Pre-Release Testers Rochester, NY

    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.

  9. Tim J

    Jun 4 Pre-Release Testers, Xojo Pro Dehydrating in AZ

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

  10. Norman P

    Jun 4 Pre-Release Testers, Xojo Pro great-white-software.com/blog
    Edited 3 months ago

    @Kevin G 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?

    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

  11. Tim J

    Jun 4 Pre-Release Testers, Xojo Pro Dehydrating in AZ

    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.

  12. Tanner L

    is not verified Jun 4 Pre-Release Testers Toronto, Canada

    @Tim J This form allows the user to differentiate between those 20 different files.

    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.

  13. Tim J

    Jun 4 Pre-Release Testers, Xojo Pro Dehydrating in AZ

    @Tim J I'll also give that a look-see and determine which is more effective.

    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.

or Sign Up to reply!