Solved

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

Posted on 2012-12-26
33
479 Views
Last Modified: 2016-08-29
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
0
Comment
Question by:shawn857
  • 17
  • 8
  • 7
  • +1
33 Comments
 
LVL 36

Expert Comment

by:Geert Gruwez
Comment Utility
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
 
LVL 25

Expert Comment

by:Sinisa Vuk
Comment Utility
0
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 25

Expert Comment

by:Sinisa Vuk
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 25

Expert Comment

by:Sinisa Vuk
Comment Utility
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
 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 17

Accepted Solution

by:
TheRealLoki earned 250 total points
Comment Utility
0
 

Author Comment

by:shawn857
Comment Utility
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
Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
You have the colunm widths, so you could just make a bunch of TCombobox'es instead if you wanted.
0
 

Author Comment

by:shawn857
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
Hi Loki... are you still with me?  :-)

Cheers
    Shawn
0
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 25

Expert Comment

by:Sinisa Vuk
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
Are you still with me Sinisav...?

Cheers
   Shawn
0
 
LVL 25

Expert Comment

by:Sinisa Vuk
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
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
 
LVL 25

Assisted Solution

by:Sinisa Vuk
Sinisa Vuk earned 250 total points
Comment Utility
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
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 25

Expert Comment

by:Sinisa Vuk
Comment Utility
because of few sub-questions more on top of main one - I think that should be total of 500 :-)
0
 

Author Comment

by:shawn857
Comment Utility
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
 
LVL 25

Expert Comment

by:Sinisa Vuk
Comment Utility
You can post/contact moderator to change total points with explanation.
0
 

Author Closing Comment

by:shawn857
Comment Utility
Excellent work guys, thanks so much!

Cheers
    Shawn
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now