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

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
shawn857Asked:
Who is Participating?
 
TheRealLokiSenior DeveloperCommented:
0
 
Geert GOracle dbaCommented:
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

0
Cloud Class® Course: Microsoft Azure 2017

Azure has a changed a lot since it was originally introduce by adding new services and features. Do you know everything you need to about Azure? This course will teach you about the Azure App Service, monitoring and application insights, DevOps, and Team Services.

 
shawn857Author 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
0
 
TheRealLokiSenior DeveloperCommented:
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)
0
 
shawn857Author 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
0
 
TheRealLokiSenior DeveloperCommented:
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
0
 
shawn857Author 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
0
 
Sinisa VukCommented:
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

0
 
shawn857Author 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
0
 
TheRealLokiSenior DeveloperCommented:
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
0
 
shawn857Author 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
0
 
Sinisa VukCommented:
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.
0
 
TheRealLokiSenior DeveloperCommented:
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...
0
 
shawn857Author 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
0
 
shawn857Author 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
0
 
TheRealLokiSenior DeveloperCommented:
You have the colunm widths, so you could just make a bunch of TCombobox'es instead if you wanted.
0
 
shawn857Author 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
0
 
shawn857Author 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
0
 
shawn857Author Commented:
Hi Loki... are you still with me?  :-)

Cheers
    Shawn
0
 
shawn857Author 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
0
 
Sinisa VukCommented:
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
0
 
shawn857Author 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
0
 
shawn857Author Commented:
Are you still with me Sinisav...?

Cheers
   Shawn
0
 
Sinisa VukCommented:
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.
0
 
shawn857Author 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
0
 
TheRealLokiSenior DeveloperCommented:
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 ;-)
0
 
Sinisa VukCommented:
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

0
 
shawn857Author 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
0
 
Sinisa VukCommented:
because of few sub-questions more on top of main one - I think that should be total of 500 :-)
0
 
shawn857Author 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
0
 
Sinisa VukCommented:
You can post/contact moderator to change total points with explanation.
0
 
shawn857Author Commented:
Excellent work guys, thanks so much!

Cheers
    Shawn
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.