Solved

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

Posted on 2012-12-26
33
492 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 17
  • 8
  • 7
  • +1
33 Comments
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 38722464
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
 

Author Comment

by:shawn857
ID: 38722570
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
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 17

Expert Comment

by:TheRealLoki
ID: 38725469
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
ID: 38725664
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
ID: 38731375
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
ID: 38731383
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 27

Expert Comment

by:Sinisa Vuk
ID: 38731492
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
ID: 38735405
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
ID: 38739628
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
ID: 38742853
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 27

Expert Comment

by:Sinisa Vuk
ID: 38743038
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
ID: 38746347
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
ID: 38749250
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
ID: 38764687
0
 

Author Comment

by:shawn857
ID: 38788795
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
 
LVL 17

Expert Comment

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

Author Comment

by:shawn857
ID: 38812803
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
ID: 38897127
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
ID: 38921861
Hi Loki... are you still with me?  :-)

Cheers
    Shawn
0
 

Author Comment

by:shawn857
ID: 38954972
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 27

Expert Comment

by:Sinisa Vuk
ID: 38975409
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
ID: 38975824
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
ID: 38993845
Are you still with me Sinisav...?

Cheers
   Shawn
0
 
LVL 27

Expert Comment

by:Sinisa Vuk
ID: 38994110
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
ID: 38997499
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
ID: 39001764
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 27

Assisted Solution

by:Sinisa Vuk
Sinisa Vuk earned 250 total points
ID: 39004674
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
ID: 39005253
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 27

Expert Comment

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

Author Comment

by:shawn857
ID: 39005338
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 27

Expert Comment

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

Author Closing Comment

by:shawn857
ID: 39005364
Excellent work guys, thanks so much!

Cheers
    Shawn
0

Featured Post

Free Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
In this brief tutorial Pawel from AdRem Software explains how you can quickly find out which services are running on your network, or what are the IP addresses of servers responsible for each service. Software used is freeware NetCrunch Tools (https…
Do you want to know how to make a graph with Microsoft Access? First, create a query with the data for the chart. Then make a blank form and add a chart control. This video also shows how to change what data is displayed on the graph as well as form…

724 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