Recent

Author Topic: Multicolumn combobox [SOLVED]  (Read 2516 times)

Vodnik

  • Full Member
  • ***
  • Posts: 184
Multicolumn combobox [SOLVED]
« on: December 18, 2020, 09:29:20 am »
Hello,
I'm looking for multi-column combobox.
I have found a nice package with columncombo component by howardpc (2013). 
https://forum.lazarus.freepascal.org/index.php?topic=23035.0
When using it in Lazarus 2.0.10 under Windows, I got a couple of problems.
I'm not good enough to solve them myself.
Attached is an example project together with columncombo package.
After installing columncombo.lpk, I had to add LazUtf8 module to uses section of colcombo.pp. I had also added LazUtils package to the project. (Otherwise UTF8Copy function would not be found).
Being compiled successfully, columncombo discovered a couple of bugs:
1. If ItemIndex property is set different to -1, then an exception class ''EListError' reported with message: List index (0) out of bounds.
2. If text of column 1 is not of the same length in different rows, then an overlapping occur (see screenshot)
I guess these bugs did not occur in 2013; they are the result of Lazarus upgrade.
Can somebody good in modern LCL have a look at the code, please?
« Last Edit: January 02, 2021, 10:37:30 pm by Vodnik »

jamie

  • Hero Member
  • *****
  • Posts: 4210
Re: Multicolumn combobox
« Reply #1 on: December 19, 2020, 02:45:02 am »
you can achieve what you are looking for using a secondary form as a modal stripped of its borders and icons.

Then drop a tstringgrid on it and also turn off a few things to remove the fixed columns leaving only the center line of the grid.

have it automatically close when you click on it and have the selection you made stored or simply reference the COL, and ROW values.

I think you get the idea..

I do things like this all the time, I normally make a single form where as I can reuse it many times throughout  the app by simply loading its data before displaying it.
The only true wisdom is knowing you know nothing

lucamar

  • Hero Member
  • *****
  • Posts: 3607
Re: Multicolumn combobox
« Reply #2 on: December 19, 2020, 06:57:04 am »
you can achieve what you are looking for using a secondary form as a modal stripped of its borders and icons.

Modal? Shouldn't it rather be modeless so that when clicking outside it closes?

About the original question, it should be asked to either the original contributor (howardpc?) or the current maintainer (if any) of the package. He (or they) should know better what's the deal with it :-\
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.10/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

Vodnik

  • Full Member
  • ***
  • Posts: 184
Re: Multicolumn combobox
« Reply #3 on: December 19, 2020, 12:38:04 pm »
Quote
About the original question, it should be asked to either the original contributor (howardpc?) or the current maintainer (if any) of the package. He (or they) should know better what's the deal with it
This was my first idea. But author recommended to public the problem here.

Vodnik

  • Full Member
  • ***
  • Posts: 184
Re: Multicolumn combobox
« Reply #4 on: December 19, 2020, 01:28:04 pm »
Well, I would like to fix the problem myself, but there are problems that kicks me off...
If I set ColumnCombo1.ItemIndex=0, then even opening the project causes a message "List index (0) out of bounds." Under some conditions Lazarus IDE crashes with this project...
How to troubleshoot this error?

wp

  • Hero Member
  • *****
  • Posts: 8119
Re: Multicolumn combobox
« Reply #5 on: December 19, 2020, 03:03:32 pm »
Important hint for working with unfinished/problematic packages: Do not install this package, create the components needed from it at runtime. This avoids the crash of the IDE which is very likely in case of faulty packages because an installed packaged is part of the IDE.

So, I took your code, loaded the unit2.pas into an external editor and modified it like this:
Code: Pascal  [Select][+][-]
  1. type
  2.   TForm1 = class(TForm)
  3.     procedure FormCreate(Sender: TObject);  // <-- added
  4.   private
  5.     ColumnCombo1: TColumnCombo;    // <--- moved to "private" section
  6.   end;
  7.  
  8. procedure TForm1.FormCreate(Sender: TObject);  // <-- added
  9. begin
  10.   ColumnCombo1 := TColumnCombo.Create(self);
  11.   with ColumnCombo1 do
  12.   begin
  13.     Parent := self;                     <---- IMPORTANT: every control must have a parent
  14.     Left := 64;                            <--- all these property values are taken from the lfm file.
  15.     Top := 40;
  16.     Width := 225;                          
  17.     ShowColSeparators := True;
  18.     Items.Add('row1col1'#9'row1col2');
  19.     Items.Add('row2col1'#9'row2col2');
  20.     Items.Add('row3col1longname'#9'row3col2');
  21.     Text := 'row1col1'#9'row1col2';
  22.   end;
  23. end;

Then I loaded the lfm file and deleted the ColomnCombo1 part and added a line for the OnCreate handler. This is my lfm file afterwards:
Code: [Select]
object Form1: TForm1
  Left = 977
  Height = 177
  Top = 467
  Width = 320
  Caption = 'Form1'
  OnCreate = FormCreate
  LCLVersion = '2.1.0.0'
end

Then you can load the project into Lazarus and test without risk of crashing the IDE.

When running the project I get a crash in the for loop of TColumnCombo.DrawItemEvent as already marked by you:
Code: Pascal  [Select][+][-]
  1.     xl:=ARect.Left + FOffsets[i]*FCharWidth;

A List-out-of-bounds error happens when the index of a list etc is equal to or larger than the count of list items. In the problematic statement there a list FOffsets. Set a breakpoint on this line, run the project again, and when the program stops, move the mouse over the "FOffsets". In the popup window you can see, among other lines, that the "FCOUNT = 0". This means that the list is empty, and thus you cannot access any of its elements.

The easiest way to resolve this issue is to add another condition to the introductory "if ... then exit" of the DrawitemEvent procedure in which the crash happens.
Code: Pascal  [Select][+][-]
  1.   if (Index < 0) or not (Control is TCustomComboBox) or (FOffsets.Count = 0) {added}  then
  2.     Exit;  

Exiting this routine immediately can be too drastic, but here it happens that it is called again when the FOffsets have been set.

« Last Edit: December 19, 2020, 03:16:39 pm by wp »
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

wp

  • Hero Member
  • *****
  • Posts: 8119
Re: Multicolumn combobox
« Reply #6 on: December 19, 2020, 03:15:50 pm »
From the component writer's view it is not a good idea to use the OnDrawItem event for custom drawing because now the event is "occupied" and it is very difficult for the application writer to do his own custom drawing.

A better place to add the functionality of the current event handler is the DrawItem method from which the OnDrawItem event is fired. This method is "virtual" and thus replaced by the code implemented in TColumnCombo. I would prefer this

Code: Pascal  [Select][+][-]
  1.   TColumnCombo = class(TCustomComboBox)
  2.     ...
  3.   protected
  4.     procedure DrawItem(Index: Integer; ARect: TRect; State: TOwnerDrawState); override;
  5.     ..
  6.  
  7. constructor TColumnCombo.Create(TheOwner: TComponent);
  8. begin
  9.   inherited Create(TheOwner);
  10.   ...
  11.   //OnDrawItem:=@DrawItemEvent;   // <----- Remove this, we don't need it any more.
  12.   OnExit:=@TruncateText;  
  13. end;
  14.  
  15. procedure TColumnCombo.DrawItem(Index: Integer; ARect: TRect;
  16.   State: TOwnerDrawState);
  17. var
  18.   i, xl, xr: integer;
  19.   // NOTE: The cb variable is not needed any more
  20. begin
  21.   if (Index < 0) or (FOffsets.Count = 0) then
  22.   begin
  23.     inherited;
  24.     Exit;
  25.   end;
  26.  
  27.   FParser.Clear;
  28.   FParser.DelimitedText:= Items[Index];
  29.   if FCharWidth=0 then
  30.     FCharWidth := GetCharWidth;
  31.   case (odSelected in State) of
  32.     False: begin
  33.              Canvas.Brush.Color:=Color;
  34.              Canvas.FillRect(ARect); end;
  35.     True:  begin
  36.              Canvas.Brush.Color:=clHighlight;
  37.              Canvas.FillRect(ARect); end;
  38.   end;
  39.   for i := 0 to FParser.Count-1 do begin
  40.     xl:=ARect.Left + FOffsets[i]*FCharWidth;      ////!!!!
  41.     Canvas.TextOut(xl, ARect.Top, FParser[i]);
  42.     if FShowColSeparators then begin
  43.       xr:=xl - ColSeparatorMargin;
  44.       Canvas.MoveTo(xr, ARect.Top);
  45.       Canvas.LineTo(xr, ARect.Bottom);
  46.     end;
  47.   end;
  48. end;

« Last Edit: December 19, 2020, 03:22:26 pm by wp »
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

wp

  • Hero Member
  • *****
  • Posts: 8119
Re: Multicolumn combobox
« Reply #7 on: December 19, 2020, 05:36:18 pm »
Overlapping of long text into its neighbour column can be fixed by using clipping. The easiest way to do this it to draw the text by calling Canvas.TextRect() instead of .TextOut. TextRect by default clips the text at the border of the rectangle passed as first parameter. So, for every text to be drawn you must define a rectangle which extends from one column delimiter to the next one - this way the text is cut off automatically if it is too long.

In this context it is maybe a good idea to show an "end ellipsis" (...) at the end of these truncated texts. You must use the TextStyle property record of the Canvas which has an EndEllipsis field. But note that the TextStyle is a record and  you must use an intermediate variable. In the attachement, there is a small demonstration of text clipping an end ellipsis.
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

Vodnik

  • Full Member
  • ***
  • Posts: 184
Re: Multicolumn combobox
« Reply #8 on: December 19, 2020, 07:31:55 pm »
Thank you for the guided tour to Lazarus, wp!
I followed your steps and cleared the problem with "List index (0) out of bounds."
What's to text overlapping problem - I didn't want to clip the text, but to adjust the column width to show the longest text. From the author's code it seemed to me that he tried to do the same (at least the FColumnWidths are calculated correctly). But he uses Generics that breaks my mind...

wp

  • Hero Member
  • *****
  • Posts: 8119
Re: Multicolumn combobox
« Reply #9 on: December 19, 2020, 09:53:59 pm »
What's to text overlapping problem - I didn't want to clip the text, but to adjust the column width to show the longest text. From the author's code it seemed to me that he tried to do the same (at least the FColumnWidths are calculated correctly). But he uses Generics that breaks my mind...
I think I misunderstood your note #2 in the first post...
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

Vodnik

  • Full Member
  • ***
  • Posts: 184
Re: Multicolumn combobox
« Reply #10 on: December 19, 2020, 10:36:23 pm »
Yes, I mean that texts exceeds the column width.
Your idea to clip the text which is too long (e.g. longer than ColumnCombo1.Width) is nice, but I didn't worried about it because standard TComboBox do not bother about it, too.

wp

  • Hero Member
  • *****
  • Posts: 8119
Re: Multicolumn combobox
« Reply #11 on: December 19, 2020, 10:42:48 pm »
Yes, my understanding was that the tab widths must be set by the user and therefore the issue of too long texts may occur. I did not find a place where the tab widths are entered, but did not dig any further. Your note made it clear: tab positions are calculated automatically depending on the width of the longest text.
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

howardpc

  • Hero Member
  • *****
  • Posts: 3646
Re: Multicolumn combobox
« Reply #12 on: December 20, 2020, 03:31:21 pm »
This was a component I dashed off one evening 7 years ago and had completely forgotten about (I never use it myself).
I've reworked it, taking on board wp's valid criticisms, and I hope I've eliminated the bugs.
I've changed some things:
  • removed the feature that truncated text to one word, which seemed pretty useless to me now.
  • removed the feature that drew vertical grid lines. These are ugly, and really add nothing. Also on Linux (at least) the lines are left as an unwelcome artefact in the closed-up combobox, offset by a pixel or two, and I found no easy way to remove them. However, I've left the code in place, commented out, so you can reinstate this feature easily if you want it.
  • made the addition of delimited items more robust: you can have a mixture of lines with any number of delimiters, and the control will handle it. While you probably don't want in practice to have such dissimilar items, it might happen inadvertently, and the control should not crash just on account of slipshod data entry.
  • removed the need for linking in a generics library by using dynamic arrays.
  • removed the overlapping display bug by using more sophisticated length-determining code, rather than the original hack based on average character widths.
  • made the default delimiter a comma (rather than a tab). This is because I  could not find a way to get the Lazarus Items editor to accept #9 as a literal character, but someone may know of a way to do this.
I have not bothered to provide 3 icons for the control (the original used the old .lrs resource format). If you ever want to stick this on your component palette you can put up with the default icon, or make a better one yourself.
If I ever used this control, I think I would create it dynamically (as in the test program attached) not clutter up an already overcrowded palette.
« Last Edit: December 20, 2020, 05:14:41 pm by howardpc »

wp

  • Hero Member
  • *****
  • Posts: 8119
Re: Multicolumn combobox
« Reply #13 on: December 20, 2020, 07:41:26 pm »
Howard, this is a quite useful component. But I am afraid that it will be forgotten again in the forum. As you know I am maintaining a package ExCtrls which contains extended versions of the standard controls. I think it would fit in there well.

Would you allow me to add this component to this package? In order to have consistent naming, I'd change its name to TColumnComboboxEx, and for a consistent license I prefer to replace GPL by LGPL2 with linking exception (like LCL). Would this be ok for you?
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

Vodnik

  • Full Member
  • ***
  • Posts: 184
Re: Multicolumn combobox
« Reply #14 on: December 20, 2020, 09:49:10 pm »
I have tested the new code and have found some bugs.
Just try the following:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   cc := TColumnCombo.Create(Self);
  4.   cc.SetInitialBounds(40, 50, 200, 30);
  5.   cc.Items.Text := 'John Lennon,Liverpool'#10'Lady Gaga,New York';
  6.   cc.ShowColSeparators := False;
  7.   cc.ItemIndex:=0;
  8.   cc.Parent := Self;
  9. end;

Running it under Windows raises an exception class 'ERangeError' with message:
Code: Pascal  [Select][+][-]
  1. Range check error
  2. In file 'columncombo.pas' at line 207:
  3. xl := ARect.Left + FOffsets[i]

Also I can't achieve that when setting ItemIndex>-1 ColumnCombo should show the selected item text inside the box (as standard ComboBox do). It is always drawn blank. Appropriate item is selected, but not shown initially.

Quote
removed the feature that drew vertical grid lines. These are ugly, and really add nothing.

Can't agree with this. I like column separators, especially when column text consists of few words:

John Lennon| Liverpool
Lady Gaga| New York

Those who do not like separators can just switch them off...

Quote
This is because I  could not find a way to get the Lazarus Items editor to accept #9 as a literal character, but someone may know of a way to do this.

Under Windows <Ctrl><Tab> do the work.

I agree with WP, this is quite a useful component! I couldn't find any suitable replacement to use in my application.
« Last Edit: December 20, 2020, 09:52:27 pm by Vodnik »

 

TinyPortal © 2005-2018