Solved

TListView and bitmaps

Posted on 2000-04-13
24
589 Views
Last Modified: 2010-04-04
I'm trying to set up a scrollable list that has text and graphics.  I haven't figured out how to do this yet.  One one the problems is that the images are all differnt sizes.  Basicly I'm trying to do something like WindowBlinds personality selector.  I would have a description/name and a visual preview.  I dont know is tlistview can do this.  Is there any other components out there that could?  I've tried a few, but have encountered many problems (such as flickering when the selction bar moves).
0
Comment
Question by:felonious
  • 11
  • 5
  • 4
  • +2
24 Comments
 
LVL 13

Expert Comment

by:Epsylon
ID: 2713331
Maybe TAdvListView from

http://www.tmssoftware.com

can do it...
0
 

Author Comment

by:felonious
ID: 2713620
tried it.  it has flick problems that i couldn't correct
0
 

Author Comment

by:felonious
ID: 2713626
sorry, i mean 'flicker' problems
0
 
LVL 2

Expert Comment

by:Serega
ID: 2715187
Try to use StringGrid and draw images yourself. (with OnDrawCell method)

insert into this method procedure like this:

  with StringGrid1.Canvas do
  begin
    {...}
    Draw(Rect.Left, Rect.Top, Image1.Picture.Graphic);
    {...}
  end;
0
 
LVL 4

Accepted Solution

by:
jeurk earned 150 total points
ID: 2715226
Hello,
You should make it simple and use a tlistbox or a tchecklistbox if you need to check stuff.

You items will have differents sizes, so you have to set the style to lbOwnerDrawVariable

first use the event ListBox1MeasureItem to set the height of your items, big enought to let you display your picture.
after that draw the item is the ListBox1DrawItem event. something like that :
procedure TFRM_CCM_checkout.cxlbObjectDrawItem(Control: TWinControl;
  Index: Integer; Rect: TRect; State: TOwnerDrawState);
begin
  with (Control as TListBox) do { draw on control canvas, not on the form }
  begin
    Canvas.FillRect(Rect); { clear the rectangle }
//draw you image
      Canvas.Draw(Rect.Left + 2, Rect.Top, Image1.Picture.Graphic);

    if pos(uppercase(edtLocate.text), uppercase(Items[Index])) <> 0 then
      Canvas.font.Color := $00FF0080;
    Canvas.TextOut(Rect.Left + 18 + Image1.Width, Rect.Top + 2, 'the text of image1'); { display the text }
  end;
end;

This should do the work.
0
 

Author Comment

by:felonious
ID: 2715292
wonderful.  thank you jeurk and serega.  i haven't tried it yet, and might not use it (i've devised a system that is working so far with panels, images, labels and a scrollbar), but it seems like you have answered my question.  I appreciate it.  

If no one minds i would like to ask a few related questions.  

I've noticed that sometimes flickering occures when you do the drawing yourself, so far i have been able to fix this problem by invalidating or just outright updating.  I know that I should be able to use LockWindowUpdate(handle), but it generates an exception everytime i try.  Is there a Delphi wrapper for this?  Or some other way to perform major updates to the form/visible components without flicker?

Another question:  I assume that i have to have timages or tbitmaps (or twhatevers) for all the images i would like to display in a listview.  Is there anyway to dyncamicaly allocate timages (and reference them)?  i mean, i dont know exactly how many images i will be dealing with.  I thought maybe there would be some way to set up a array of handles (to the timages), but so far i cant figure out how to do this.  Or could i just declare a huge array (like 100 or more) of timages and then create and destroy them as i go?  hmm.  And if so would they still gobble up memory if not in use?  

I'm not sure how this all works, do i need to post these all as seperate questions with values?  Both Serega and Jeurk answered my question;  is there a way I can *give* points to both of them?  Sorry, i'm new to this site/forum.
0
 
LVL 4

Expert Comment

by:jeurk
ID: 2715373
About the flicker :

do a
  try
    LockWindowUpdate(Handle); //to avoid flickering in button state update
//draw things

  finally
    LockWindowUpdate(0);
  end;

the handle should be the one of the control you want to lock like here listbox.handle, or the handle of the form...
only one lock at a time can be done...

About the images:
allocate an image is done like that :
var
  Image1: TImage;

  Image1 := TImage.Create(Self);
  with Image1 do
  begin
    Name := 'Image1';
    Parent := Self;
    Left := 296;
    Top := 80;
    Width := 105;
    Height := 105;
  end;

But you should use a TBitmap, I think.
Because you want to do some offscreen
stuff...Use loadfromfile to load the image. If you use a listbox, you can add the image as an object to the list, don't forget to free things...
look at the help for TStrings.AddObject
they is a sample...
But yes it's memory eating stuff...
Loading them dinamically will take more time...

About the questions.
If you want some precisions about a questions you can ask them in the same thread, if it's related and does not take to much more time for the experts to solve.
If more then one person answers yor question, you can choose which one deserves the points, sometimes you better like an answer then another. What you can do, is say, that person gets the points if everybody agree.
The first person who answers gets the points. But sometime the answers in not complete, or what commes after has much more details and helps you more. It's on you to choose. to give points to more then one person is not possible. You have to ask a question for that persons just to give them some points.
So here we are.
You're welcome if you have some others quetsions.
Regards...
-John.
0
 
LVL 2

Expert Comment

by:mullet_attack
ID: 2715377
If you draw your visual stuff in the onPaint event you will usually get flicker, especially if u call inherited paint. Somewhere in Tcontrol (i think) paint does a fillrect to blank the canvas, usually white in colour then your paint method draws your image over the top, hence that little flicker of white.

To overcome it, don't call inherited paint. However, sometimes i derive my class from TCustompanel, not TGraphicControl. That way I get all the 3d borders etc for free. In this case I must call inherited paint. (and get the flicker)

To avoid flicker altogether, The usual technique is to use double-buffering.
For example, create an in-memory canvas where you draw all the visual aspects of your component or whatever, and then bitblt or copyrect that canvas to the visible canvas (eg TCustomPanel's canvas)

Here's a pseudo-code snippet of what I mean..

TMyVisualThing = class (TCustomPanel)
..
Construtor Create;override;
Destructor Destroy;override;
procedure OnPaint;override;
procedure DrawStuff
procedure UpdateImage
..
end;

Construtor Create;
begin
  inherited create;
  InMemoryBitmap := TBitmap.create;
  (set size of Bitmap here)
end;

Destructor Destroy;
begin
  InMemoryBitmap.free;
end;

procedure OnPaint;
begin
  inherited Paint; //draw borders etc...
  UpdateImage;
end;

procedure DrawStuff
begin
  with InMemoryBimap.Canvas do
    begin
     line...
     circle..
     etc...
    end;
  UpdateImage;
end;

procedure UpdateImage
begin
  CopyRect (InMemoryBitmap.Canvas to Canvas)
end;

If Windows need to paint the control, then Paint will call UpdateImage, and it gets drawn.

If you want to change image call DrawStuff (no flicker !)

This code is obviously missing a lot, but it should give you the idea.

BTW, I have used LockWindowUpdate without generating any errors, however I have yet to see _any_ visual benefit by using it. ie things still flicker...

Mark


0
 
LVL 2

Expert Comment

by:Serega
ID: 2715411
About your second question:
What prevent you from loading images from files or from resources ?
If you declare array of TImages, it wouldn't be huge. Memory will filled when you'll load images sequentially one by one from files (or resources) to each image. Then load them only when you need it. Of course, you'll need after using to free them sequentially too.
0
 
LVL 4

Expert Comment

by:jeurk
ID: 2715584
ressources is not the solution, because I think felonious does not know in advance what he will have to load...
0
 
LVL 2

Expert Comment

by:Serega
ID: 2715715
If felonious want, he can even do not make an array and to load images directly before painting. But that kind of operation make worse his problem :-) and flickering will be more visible.
0
 
LVL 2

Expert Comment

by:mullet_attack
ID: 2715901
Why not create bitmaps dynamically, load your picture into it, and store the references in a TList,

var
 MyList : TList;

somewhere..
MyList := TList.create;


procedure LoadPiccy
var
  MyBitmap : TBitmap;
begin
  MyBitmap := TBitmap.create;
  MyBitmap.loadfromfile...
   or
  MyBitmap.loadfromresource...
  index := MyList.add(Mybitmap);
end;

to retrieve bitmap :

aBitmap := TBitmap(MyList[index]);
0
Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 4

Expert Comment

by:jeurk
ID: 2715947
that's what I say. And for that use the listbox.items list...
0
 

Author Comment

by:felonious
ID: 2717459
ok, I think i'm finally starting to get the picture here.  First off, use a TListView.  I can store image text/descriptions in the TListView.items (TStrings) and then use TListView.items.addobject (TStrings.AddObject) to associate the bitmap with the items entry (By doing what mullet_attack described).  Finally i can draw the TListView manually on the TListView.Canvas (and use the LockWindowUpdate stuff to suppress flicker... maybe :) eh he).

Wow, I dont think I've ever got this kind of response before in a programming forum.  You guys are both very helpful and very quick to respond with useful information.  I think that the points should go to jeurk since the original question was about tlistview.  Any ojections to me leaving this thread open till I get this all to work?  :)  (maybe another day or two)
0
 

Author Comment

by:felonious
ID: 2717583
um, ofcourse i mean listbox not listview :)
0
 

Author Comment

by:felonious
ID: 2717709
ok, i've got some of the code in place and am having *some* success.  only one problem so far.  

First off, I have a thread running that is looking for files and then reading them in.  This thread is what is adding my bitmaps to the listbox.  When it finds what it is looking for:

                convertimage(value);
                bitmap1.loadfromfile(value);
               fskinbrowser.listbox1.items.addObject(value,bitmap1);

it converts the image to a bitmap loads it and then addobjects it.  that all works fine.  I also have in place some of the draw routines for the listbox.

procedure TfSkinBrowser.ListBox1DrawItem(Control: TWinControl;
  Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
      Bitmap: TBitmap;
begin
  with (Control as TListBox) do { draw on control canvas, not on the form }
  begin
    Bitmap := TBitmap(Items.Objects[Index]);
    Canvas.FillRect(Rect); { clear the rectangle }
  //draw you image
      Canvas.Draw(Rect.Left + 2, Rect.Top, Bitmap);
      //Image1.Picture.Graphic);

{    if pos(uppercase(edtLocate.text), uppercase(Items[Index])) <> 0 then
      Canvas.font.Color := $00FF0080;}
//    Canvas.TextOut(Rect.Left + 18 + bitmap.Width, Rect.Top + 2, 'the text of image1'); { display the text }
  end;
end;

which also seems to work on so far (although I dont have the text in place yet).  the problem occures in this procedure:

procedure TfSkinBrowser.ListBox1MeasureItem(Control: TWinControl;
  Index: Integer; var Height: Integer);
var
      Bitmap1: TBitmap;
begin
      with Control as TListBox do
      begin
   Bitmap1 := TBitmap(Items.Objects[Index]);
   if Bitmap1 <> nil then
            if Bitmap1.Height > Height then Height := Bitmap1.Height;
      end;
end;

this seems to generate an exception (EAccessViolation).  Actually as the procedure is it doesn't produce an accessViolation, but it also doesn't set the height correctly.  But if I go and try to do anything with Bitmap1 (such as Height := Bitmap1.Height;) it does.  I also tried to check Bitmap1.Empty and I get the same thing.  Any ideas?
0
 
LVL 2

Expert Comment

by:mullet_attack
ID: 2718006
Very interesting. I have never used variable height list box before, so i tried your sample. I get the same results. TBitmap(Items.Objects[Index]) returns nil. I traced it thru the VCl, and it seems that this function in Stdctrls doesn't get any object.

function TListBoxStrings.GetObject(Index: Integer): TObject;
begin
  Result := TObject(ListBox.GetItemData(Index));
  if Longint(Result) = LB_ERR then Error(SListIndexError, Index);
end;

Because TListbox is a wrapper around a windows class, the VCL uses messages to add the object reference in the AddObject call, and to retrieve it during the GetItemData function. For some reason this fails during MeasureItem, but then works OK during Drawitem.

At this point I tend towards a genuine bug in the VCL (somebody prove me wrong, please !)

I also tried this with Raize 3rd party control, same result.

Also, MeasureItem is only called the first time the item is drawn, not every time as the help 'suggests'


   

0
 

Author Comment

by:felonious
ID: 2718538
i'm thinking that its pretty easy to get around this bug.  maybe i'll just add another item somehow that i will set to the bitmap height when i load in the bitmap.  should solve the problem.
0
 

Author Comment

by:felonious
ID: 2718754
grrr. eh he.  ok, i managed to get around that little problem i was having with the height settings.  instead of being home free i found out something else about TListbox.  The height is limited to 255 pixels.  If you try to set it to 256 it actually sets the height to 1 pixel! (257 = 2, 258 = 3, etc)  interesting.  seems the height is probably a byte instead of a integer as the MeasureItem call seems to imply.

procedure TfSkinBrowser.ListBox1MeasureItem(Control: TWinControl;
  Index: Integer; var Height: Integer);

Also figured out something else about the code i posted earlier.

with Control as TListBox do
begin
   Bitmap := TBitmap(Items.Objects[Index]);
   if Bitmap <> nil then
            if Bitmap.Height > Height then Height := Bitmap.Height;}
      end;

this snipet actually sets the control height (control.hieght - in this case ListBox1) (if the bitmap would actually load) instead of the passed var Height.  This wouldn't be so damn funny if the snippet wasn't actually a direct paste from the Borland Delphi 5 help.
0
 
LVL 2

Expert Comment

by:mullet_attack
ID: 2718764
Given any further thought to using a drawgrid, as Serega suggested?
0
 

Author Comment

by:felonious
ID: 2718785
i'm starting to think moveing on (yeah, maybe to drawgrid) would be easier.  :)  this has been fun though.  learning experience.  Anyone out there with a Listbox that isn't a decendent of Tcustomlistbox?  I'd be interested to see if the same kind of problems occure.
0
 

Author Comment

by:felonious
ID: 2718794
i'm starting to think moveing on (yeah, maybe to drawgrid) would be easier.  :)  this has been fun though.  learning experience.  Anyone out there with a Listbox that isn't a decendent of Tcustomlistbox?  I'd be interested to see if the same kind of problems occure.
0
 

Author Comment

by:felonious
ID: 2719833
even though this turned out not to work it was no failt of jeurks.  since it should have worked (but didn't because of problems in some VCLs) i'm going to give jeurk the points anyway.  Thanks for the effort everyone.
0
 
LVL 4

Expert Comment

by:jeurk
ID: 2722035
Hello,Thanks for the points.
Sorry it did not work...
Did you saw that they is a sample showing a listbox ownerdraw...
in dir demos/ownerlst/
CU
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

Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…
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…

760 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

21 Experts available now in Live!

Get 1:1 Help Now