A "dynamic sort" of TListView (or something similar)?

shawn857
shawn857 used Ask the Experts™
on
Dear Experts, I want to create something sort of similar to the environment when you open up a CSV file in Microsoft Excel (where it asks for field delimiter, then shows explicit field divisions in the file snippet shown).
  What I'd like to be able to do exactly is have something like a TListView (or similar) whereas I could open up a text file, read in say 50 or so records, then write them to this TListView. Then have the user select what his "field delimiter" is via RadioButton, and then dynamically while he is making his selection, it will show "vertical lines" in the TListView separating each field according to the field separator the user chooses. The "headers" for each field of the TListView should be clickable and should be the exact same size as each field as designated by the user's choice of field delimiter. I would then like the user to be able to click on the header above each field and a drop-down menu gets displayed - which allows the user to assign a pre-set value to that field, or assign a custom value which he manually types in. Please see my attached screenshot - dynamically-sized clickable headers are drawn in black. Just clicking one of them (ie. my red arrow) would bring up the dropdown menu.
   A lot, I know! But is there some component that can allow me to do this?

Thanks!
  Shawn
pic1.jpg
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Geert GOracle dba
Top Expert 2009

Commented:
devexpress quantumgrid is capable of what you ask
it has sizeable columns

off course you'll need an interpreter for the lines of text
a parser is the way for complex code
or even a regular expression parser for really complex

or just stick with the posex function, which returns the position of the substring looked for

like this you could find all the delimiters
x := 1;
repeat
  n := posex(delimiter, line_of_text, x);
  >> n = position of delimiter
  x := n+1;
until n <= 0;

Open in new window


or use
var S: TStringList;
begin
  S.Delimiter := ',':
  S.StrictDelimiter := True;
  S.DelimitedText := Line_of_text;
  >> for I := 0 to S.Count-1 do 
  >> each value is in S[I];

Open in new window

Sinisa VukSoftware architect
Top Expert 2012

Commented:

Author

Commented:
Thanks Geert, but Devexpress Quantumgrid looks to be like $900. I can't afford that. Is there a cheaper, or (preferably) free alternative?

And thanks also Sinisav, but I wasn't really asking about how to sort using TListView - I already know that.

Thanks!
   Shawn
Bootstrap 4: Exploring New Features

Learn how to use and navigate the new features included in Bootstrap 4, the most popular HTML, CSS, and JavaScript framework for developing responsive, mobile-first websites.

TheRealLokiSenior Developer

Commented:
I write a lot of EDI solutions, so I've come across this before
for 100 points I won't be writing it all for you though :-)

Warnings
You need to take quotes (") into account for CSV files.
You'll need an alogorithm that detects them and keeps track of your delimiter
ALso, you need to check for double double quotes
e.g.
Name,Age,Address
John Smith,20,123 Some Street
"Bob ""The Builder""",30,"Unit 1, 456 Some Street"

To "simply" do what you want, you can use a TListView
although I prefer
Soft Gems Virtual Treeview components, but they can be tricky to jump right into
http://soft-gems.net/index.php/controls/virtual-treeview-gallery

If I were to do it myself, I'd do it all manually
I'd paint the text and lines manually, onto a TBitmap, and then into a TImage/TScrollbox
or TGraphic/Custom control

That way, you can highlight the delimeters, show lines (with a nice space between the delimiters) etc
For the header, you could perhaps use a Theader control perhaps?, but just as easily with a bunch of dynamically created TPanel's/buttons thrown onto the TScrollbox)
You can determine the required width of each "header" easily with "canvas.textwidth('blah')"

or I'd use a richedit, and show the delimiters with a background colour to distinguish them ( I think I had to use a non-standard richedit so I could have a bakground and foreground colour the way I wanted it though)

Author

Commented:
Thanks Loki, for the well-thought out ideas. I would *certainly* give you more than 100 points for your additional help! I just put 100 in there cause I didn't really know what to properly put.
   I didn't think I could do this with a regular TreeView - would it really do everything I outlined (showing the "vertical lines", etc) ? If it does, then hey, simple is good! Barring that, your suggestion of Soft Gems Virtual Treeview looks very good too.

Thanks!
    Shawn
TheRealLokiSenior Developer

Commented:
You can set "listview1.GridLines := true;"
This may not be as nice as you'd like, but
If you want anything fancier, you'd have to use another control

Author

Commented:
Thanks Loki, but using "listview1.GridLines := true;", how do I set where I want the vertical gridlines to go at run-time? Also, will using a TListView provide me with everything I described in my original query?

Thanks!
   Shawn
Sinisa VukSoftware architect
Top Expert 2012

Commented:
You must set grid columns width in runtime. Width is in pixels.

ListView1.Columns.Items[0].Width := 20;
ListView1.Columns.Items[1].Width := 40;
ListView1.Columns.Items[2].Width := 50;
...

Open in new window



... if you want to set columns to same size depending on size of listview and number of columns:

ListView1.Columns.Items[0].Width := (ListView1.Width-Xmargin) div ListView1.Columns.Count;
ListView1.Columns.Items[1].Width := (ListView1.Width-Xmargin) div ListView1.Columns.Count;
ListView1.Columns.Items[2].Width := (ListView1.Width-Xmargin) div ListView1.Columns.Count;
...

Open in new window

Author

Commented:
Thanks Sinisav, but how to calculate exactly pixel-width when the user selects the type of delimiter. I mean, if he chooses a "comma" as delimiter, how does one calculate *exactly* in pixel-width, where the comma occurs in the record. If column.width could be somehow expressed in "number of characters", then it would be a piece of cake.

Thanks
    Shawn
TheRealLokiSenior Developer

Commented:
you can take the greatest width for that column
e.g. math.max(a,b)
and
canvas.textwidth(s)
of the text up to the comma

Author

Commented:
Thanks Loki, I've never played around with "canvas" before... I'll need to brush up on that. Do you reckon this is they do it when they draw the vertical lines in Microsoft Excel when using the "text Import Wizard"? (see screenshot). Is the component used to display the records, a TListView, or a TMemo, or something else?

Thanks!
   Shawn
pic2.JPG
Sinisa VukSoftware architect
Top Expert 2012

Commented:
TListView is a good option here. Have a most capabilities for your needs. Support OnDraw events, and with using them you can extend visual experience.
TheRealLokiSenior Developer

Commented:
My guess is that would be a TScrollbox with an image drawn directly on it (e.g. TBitmap)
as I hinted at earlier
it's just 1 way of doing it of course.

Here's a quick demo I wrote for you to illustrate how to do something like that

https://filedb.experts-exchange.com/incoming/ee-stuff/8293-CSVBasicExample.zip

I included some basic CSV field grabbing based on passing a function what the delimiter, quote and end of record indicators are
hope this helps...

Author

Commented:
That looks awesome Loki, thank you! I'm just digging through it now and studying it. Loki, as far as the clickable headers go that I'd like, I guess I would just use a THeader or TheaderControl in conjunction with this TScrollbox?

Thanks!
    Shawn
Senior Developer
Commented:

Author

Commented:
Thanks Loki, that new update with the header capability looks great. Just a couple of questions please:

(1) You use a THeaderControl property called "NoSizing" that doesn't seem to be available in my version of Delphi (old dinosaur Delphi 7). I just commented that line out for now, but is there a workaround, do you know?

(2) When I click on the headers to bring up the menu, can I bring up a ComboBox instead of a Popupmenu? I need to have the user select from a list of preset options, but also allow him to input his own custom value.

Thanks!
    Shawn
TheRealLokiSenior Developer

Commented:
You have the colunm widths, so you could just make a bunch of TCombobox'es instead if you wanted.

Author

Commented:
Tnanks Loki.. and that "NoSizing" property for THeaderControl? No need for it? It appears that the columns are non-sizable by default in my Delphi 7, so that's good.

Thanks
   Shawn

Author

Commented:
Hi Loki, sorry for the long delay in responding - my free trial ran out some weeks ago and I got caught up on other jobs. I just signed up for a full premium membership today and now I'm ready to continue. I didn't forget you!
   Your downloadable code worked excellent Loki - just the solution I was looking for. Can you make a suggestion as to a suitable amount of points I should give your answer?
  Just a couple other minor issues with the solution Loki please, before we close it out:

(1) You use a THeaderControl property called "NoSizing" that doesn't seem to be available in my version of Delphi (old dinosaur Delphi 7). I just commented that line out for now, but is there a workaround, do you know? ideally, I'd like the header sections to *not* be sizable by the user... as of right now, the user can re-size them. Not a major big deal, but I'd really like them to be static.

(2) Is there a way to make the dynamic headercontrol "stay in place" and not disappear when the user scrolls the TScrollbox down?

Thanks!
   Shawn

Author

Commented:
Hi Loki... are you still with me?  :-)

Cheers
    Shawn

Author

Commented:
Hello Loki, are you still there..? I hope you are still with me... I'd like to get this question closed out and award you your points!

Cheers
   Shawn
Sinisa VukSoftware architect
Top Expert 2012

Commented:
As I want to clean up some old questions....
I made extended example build on TheRealLoki's. You ask:

(1) You use a THeaderControl property called "NoSizing" that doesn't seem to be available in my version of Delphi (old dinosaur Delphi 7). I just commented that line out for now, but is there a workaround, do you know? ideally, I'd like the header sections to *not* be sizable by the user... as of right now, the user can re-size them. Not a major big deal, but I'd really like them to be static.

My suggest is to set:
hdrSection.MinWidth := hdrSection.Width;
hdrSection.MaxWidth := hdrSection.Width;

Open in new window

... for each section to disable resizing. If you allow resizing then your procedure must recreate image to follow columns.

(2) Is there a way to make the dynamic headercontrol "stay in place" and not disappear when the user scrolls the TScrollbox down?

To stay in place I set new panel and put scrollbox on it, dynamically create headercontrol
(TheRealLoki) and set somproperties on scroll box to be without border - use panel border instead. this way header has parent as panel and it is outside of scrollbox.
CSVBasicExampleWIthHeaders.zip

Author

Commented:
Sinisav, thank you so much for that - it works great. Sinisav, do you know if it's possible to assign a specific "hint" to each headersection?

Also, how many points do you think should I award for this question, and how should I divide the points between you and Loki?

Thanks!
   Shawn

Author

Commented:
Are you still with me Sinisav...?

Cheers
   Shawn
Sinisa VukSoftware architect
Top Expert 2012

Commented:
I'm here. Here is little example. Because sections doesn't have hint - I made over-around solution. Attach dynamically MouseMove event to headercontrol and change hint on run time:

procedure TForm1.HeaderControlMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
var
  i: Integer;
  sHint: String;
begin
  sHint := 'outside of sections';
  //find section where mouse is over
  for i := 1 to HeaderControl1.Sections.Count do
  begin
    if (HeaderControl1.Sections.Items[i-1].Left<=x) and (x<=HeaderControl1.Sections.Items[i-1].Right) then
    begin
      sHint := 'over section ' + IntToStr(i);
      Break;
    end;
  end;

  if HeaderControl1.Hint <> sHint then
    HeaderControl1.Hint := sHint;
end;

Open in new window


... for point breakdown ... as I extend Loki's solution ... think 50:50 will be fine. Loki can put his thoughts here too.

Author

Commented:
Thanks Sinisav! That works *almost* flawlessly (.. I know, I'm fussy). When you keep mouse cursor on row of headersections and move to next section, the hint does not properly change. One must come off the row of header sections completely, and go back again to next header section for the correct hint to display. Could this be easy to fix? if it's a big deal, don't worry!

Thanks
   Shawn
TheRealLokiSenior Developer

Commented:
Hi, back (I have a heavy work load...)

You can do your own hints (search EE for my example on THintWindow), but that's tricky to get perfect.

Just split the points evenly, I'm only here to help, got the "guru" t-shirt already, and that's the coolest one ;-)
Sinisa VukSoftware architect
Top Expert 2012
Commented:
So... little correction to show hint always:

DynamicHeaderControl.OnMouseMove := HeaderControlMouseMove;
DynamicHeaderControl.ShowHint:=True;
....
procedure TForm1.HeaderControlMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
var
  i: Integer;
  sHint: String;
begin
  sHint := 'outside of sections';
  //find section where mouse is over
  for i := 1 to THeaderControl(Sender).Sections.Count do
  begin
    if (THeaderControl(Sender).Sections.Items[i-1].Left<=x) and (x<=THeaderControl(Sender).Sections.Items[i-1].Right) then
    begin
      sHint := 'over section ' + IntToStr(i);
      Break;
    end;
  end;

  if THeaderControl(Sender).Hint <> sHint then
    THeaderControl(Sender).Hint := sHint;

  Application.ActivateHint(THeaderControl(Sender).ClientToScreen(Point(X,Y)));
end;

Open in new window

Author

Commented:
Fantastic Sinisav, that worked great, thank you so much. Take the rest of the day off!! ;-)

OK, so I will split the points 50/50 between you and Loki, no problem. How much do you recommend should be the total point value?

Thanks!
   Shawn
Sinisa VukSoftware architect
Top Expert 2012

Commented:
because of few sub-questions more on top of main one - I think that should be total of 500 :-)

Author

Commented:
It won't let me award more than 100 points... I guess I need to first submit a comment and change the points total to 500....

Shawn
Sinisa VukSoftware architect
Top Expert 2012

Commented:
You can post/contact moderator to change total points with explanation.

Author

Commented:
Excellent work guys, thanks so much!

Cheers
    Shawn

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial