Link to home
Start Free TrialLog in
Avatar of felonious
felonious

asked on

TListView and bitmaps

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).
Avatar of Epsylon
Epsylon

Maybe TAdvListView from

http://www.tmssoftware.com

can do it...
Avatar of felonious

ASKER

tried it.  it has flick problems that i couldn't correct
sorry, i mean 'flicker' problems
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;
ASKER CERTIFIED SOLUTION
Avatar of jeurk
jeurk

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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.
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.
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


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.
ressources is not the solution, because I think felonious does not know in advance what he will have to load...
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.
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]);
that's what I say. And for that use the listbox.items list...
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)
um, ofcourse i mean listbox not listview :)
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?
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'


   

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.
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.
Given any further thought to using a drawgrid, as Serega suggested?
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.
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.
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.
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