Save/Restore LIST Column Widths

Guys, has anyone seen a template which would save and restore column widths of browses or lists in a window? So that a user can set a preferred widths and things stay that way?

Thanks

Bostjan

There is one included, Global Properties / Actions … / App Settings / Enable List Format Manager

I had a function that did that at my last gig.

Can’t post the code, but it was not complicated. Basically looped through the PROPList:Widths and concatenated the values using @N05. And read the data back using deformat.

Probably 8-10 lines of code total.

1 Like

Rather than save just the widths, I save the whole format string and then restore it.
One thing to watch out for, whatever method you use.
If you add or change columns, then you should reset the saved info.
If you are saving/restoring the whole format, like I do, then restoring an format for an older version of the list control is pretty bad.
If you are just saving the widths, like @jslarve suggests, it won’t be as bad restoring a saved format to a new version of the list control. But the widths will go to the wrong columns.
I save the original development time format. Then when the procedure opens, I compare the saved original dev format with the current dev format. If it has changed, I do not restore the user’s saved format.

My template is originally based on the one available from the ABC Free template/classes. The SaveControlFormats template.
https://www.authord.com/products/Clarion/

1 Like

Groups are often left as Resizable so should also do List [Groups] with the same code loop saving PROPLIST:Group + PROPList:Widths in a separate line of lengths.

As Rick suggested a way to spot changes is to also save the “Original Widths” from the LIST. Then before setting them check that the current “Original Widths” from the LIST match the ones you saved.

This is some code to give you an idea how to grab widths

ListWidthsConcatN05 PROCEDURE(LONG ListFEQ, BOOL bGroupFlag=False),STRING
Widths ANY
ColNo LONG,AUTO
Prop_Width LONG,AUTO
    CODE
    Prop_Width=PROPLIST:Width + CHOOSE(~bGroupFlag,0,PROPLIST:Group) 
    LOOP ColNo=1 TO ListFEQ{PROPLIST:Exists,0}
        Widths=Widths & FORMAT(ListFEQ{Prop_Width,ColNo},@n05)
    END
    RETURN Widths
ListWidths_Col_Original = ListWidthsConcatN05(?List1)
ListWidths_Grp_Original = ListWidthsConcatN05(?List1,True)

Before setting List to saved widths be sure saved "Originals" match ListWidths_Col_Original and ListWidths_Grp_Original

1 Like

Hi @Bostjan_Laba , have you tried with putini.
Something like this:
On CloseWindow embed
putini(‘Tab_Art’,‘SELECTEDTAB’,?CurrentTab{prop:selected},‘app.ini’) or
PUTINI(‘Tab_ART’,‘ARTIKLI’,?Browse:1{PROP:FORMAT},‘GRIDS.INI’)

And then try to read the .ini file with getini on OpenWindow procedure. This way every user can set the width of columns as they prefer.

I never liked the idea of saving PROP:Format. It is too easy for someone (user or developer) to mess it up. Also, it can provide a way for bored people to look at queue fields that you don’t want them looking at.

This width info can be saved with the window resize data. With a “reset window” menu choice that kills that ini section.

I can see 2 other problems with saving the entire FORMAT:

  1. If saved to an INI file that file format has a 1024 byte limit per line which the Format() length can exceed 1024. You could truncate the Format and not know it. I think 50 columns should be no problem so not a common issue. The code should protect against this checking for 1024.

  2. If your logic to reset relies on spotting the Original LIST Format() changed then you would reset the saved column widths on Any Format Change no matter how minor. So if you just changed the Heading, Tool Tip, Resized Columns, etc… the user would lose his saved widths. I make a lot of cosmetic LIST changes … using my tool to get columns sized just right …

Edit 4/7/23

C. The Format() can contain line breaks as binary 13,10 (e.g. in a Heading ~ Line1 <13,10> Line2 ~). If saving the entire Format() to an INI it’s important to use QUOTE() to convert that to text <13,10> because the 13,10 mid-line will mess up an INI and the read GETINI will not work. Use UNQUOTE() on the GETINI.


I’ll take a moment to plug my List Format Parser that allows looking at the FORMAT columns easily by splitting them into rows. Handy for source compare. You can Preview any Format pasted from Code. It will generate a Format() from a Queue, and more:

There is also the template RunScreen from Capesoft. I use since years without problems.
https://www.capesoft.com/accessories/Runscreensp.htm

Thank you for all the ideas, very helpful. I might go with RunScreen but I must be sure it will work with my resizing template which also influences lists. If not, I’ll go with width saving.

Found this simple template example:

Performed some minor tweaks to the Icetips example.

ColumnWidthSave.tpl (1.5 KB)

I made a template

https://clarionhub.com/uploads/default/original/1X/e30b0d4a1bd8c7bdfe74efe9d824b3512f251e03.zip

Sorry. It’s not a template but a generic procedure

That was small enough to also paste into your message so it easy for any reader to quickly see:

#!=============================================================================================================
#TEMPLATE(ColumnWidthSaveTPL,'Browse Column Width Save'),FAMILY('Clarion', 'CW20','ABC')
#!=============================================================================================================
#!----------------------------------
#EXTENSION(ColumnWidthSave,'Save and Restore List Column Widths - cjr'),WINDOW,MULTI
#DISPLAY('Saves and restores the user chosen column widths')
#DISPLAY('for a specified list box')
#PROMPT('List:',    FROM(%Control,%ControlType='LIST')),%ListControl
#DISPLAY('Enter these with quotes')
#PROMPT('Ini File',@s20),%IniFile
#PROMPT('Ini Section',@s20),%IniSection
#LOCALDATA
ListColumn  SHORT
#ENDLOCALDATA
#AT(%WindowManagerMethodCodeSection, 'Open', '()'),PRIORITY(5001)
!Retrieve colum widths for list box
LOOP ListColumn = 1 to 255
  IF %ListControl{PROPLIST:Exists,ListColumn} = 1
    %ListControl{PROPLIST:Width,ListColumn} =| 
      GETINI(%IniSection,%ListControl&ListColumn,%ListControl{PROPLIST:Width,ListColumn},%IniFile)
  ELSE
    BREAK
  END
END
#ENDAT
#AT(%WindowManagerMethodCodeSection, 'Kill', '(),BYTE'),PRIORITY(8500)
!Save column widths of list box
LOOP ListColumn = 1 to 255
  IF %ListControl{PROPLIST:Exists,ListColumn} = 1
    PUTINI(%IniSection,%ListControl&ListColumn,%ListControl{PROPLIST:Width,ListColumn},%IniFile)
  ELSE
    BREAK
  END
END
#ENDAT
#!-----------------------------------------------

I noticed this LOOP 1 To 999 and IF Exists code twice (Open and Kill):

LOOP ListColumn = 1 to 255
  IF %ListControl{PROPLIST:Exists,ListColumn} = 1 THEN 
      ... etc

You can specify column as Zero i.e. {PROPLIST:Exists,0}
to return the Number of Columns in the List.
Use that for the Loop TO value and remove the IF Exists:

LOOP ListColumn = 1 to %ListControl{PROPLIST:Exists, 0}  ! ",0" is Column Count
      ... etc ...

If the LIST design changes (e.g. a new column inserted, deleted or added) this code will almost certainly MESS UP the List. You must protect against that somehow.

You need some kind of Signature to ID the List Design. The Save would have to Store that ID in the INI. Then check that Signature ID before setting the Widths to those in the INI.

You could use the original Format() for the ID, but that could exceed the 1024 max of an INI file. Edit: The bigger problem is the Format can contain <13,10> in ~Headings~ which would Not work with INI files.

I would think a concatenated list of all the Original Column Widths would be enough. Plus the Column Count from {Exists,0} … because why not. If that ID changes then the Saved Widths are not used at Open, the user must reformat.

ListColSaveID ANY   !Concat Column Widths to know if List changed design
...
#AT(%WindowManagerMethodCodeSection, 'Open', '()'),PRIORITY(5001)
ListColSaveID = %ListControl{PROPLIST:Exists, 0} 
LOOP ListColumn = 1 to %ListControl{PROPLIST:Exists, 0}  !Column Count
   ListColSaveID = ListColSaveID  &','& %ListControl{PROPLIST:Width, ListColumn } 
END 
IF ListColSaveID = GETINI(%IniSection,%ListControl & 'ID', '',%IniFile) THEN 
   ... ID Matches so  Loop and GetIni column widths
END 

#AT(%WindowManagerMethodCodeSection, 'Kill', '(),BYTE'),PRIORITY(8500)
  PUTINI(%IniSection,%ListControl & 'ID', ListColSaveID ,%IniFile)   !Save ID to check next Open
  LOOP and PUTINI Widths to INI
1 Like

You must consider if a Column is Not Resizable (no “M” in the format). I would add an “n” to my Signature ID and Not Resize them.

Another thing are Hidden columns which have a Width of Zero. If code displayed those (by changing the width) you would not want to do that at open.

I would code the Open to skip those fields.

ListColSaveID ANY   !Concat Column Widths to know if List changed design
...
#AT(%WindowManagerMethodCodeSection, 'Open', '()'),PRIORITY(5001)
ListColSaveID = %ListControl{PROPLIST:Exists, 0} 
LOOP ListColumn = 1 to %ListControl{PROPLIST:Exists, 0}  !Column Count is first
   ListColSaveID = ListColSaveID  &','& %ListControl{PROPLIST:Width, ListColumn } & |
                        CHOOSE(~%ListControl{PROPLIST:Resize},'n','')  !'n' Notes Not Resizable
END 
IF ListColSaveID = GETINI(%IniSection,%ListControl & 'ID', '',%IniFile) THEN 
   !ID Matches so  Loop and GetIni column widths
   LOOP ListColumn = 1 to %ListControl{PROPLIST:Exists, 0}  ! ",0" is Column Count
        IF ~%ListControl{PROPLIST:Resize} THEN CYCLE.       !Only change Resizable Columns
        IF %ListControl{PROPLIST:Width} <=0 THEN CYCLE.     !Skip Hidden Columns
        %ListControl{PROPLIST:Width,ListColumn} =| 
                GETINI(%IniSection,%ListControl&ListColumn,%ListControl{PROPLIST:Width,ListColumn},%IniFile)
   END
END 

#AT(%WindowManagerMethodCodeSection, 'Kill', '(),BYTE'),PRIORITY(8500)
  PUTINI(%IniSection,%ListControl & 'ID', ListColSaveID ,%IniFile)   !Save ID to check next Open
  LOOP and PUTINI Widths to INI  

I just used a generic function to read the saved column widths. And another one to fix it according to other criteria.

No need to generate all of that Clarion code for every listbox.

1 Like