Navigation
Home
Espresso
NetUpdate
Junkanoo
Columbus
Delphi Components
Technical Support
Contact Us
About Us
(c) 2004-2007 Kidmoses.com
All rights reserved.
KidMoses
       home     support     contact us     about us    

Add Watermark / Background Image to Listview

After using the listview component for awhile, undoubtedly you will want to add a background image or a watermark to the background just like Windows Explorer does for the My Music or My Videos folders. A watermark is simply a background image that stays in the bottom right-hand cornor of the listview. Seems simple enough. A quick Goggle search for "listview background image" turns up countless results which range from using a custom owner-drawn technique to using built-in properties for some of the newer versions of Visual Basic or Visual C++.

Background Image
It quickly becomes obvious that adding a background image can be accomplished in any number of ways. And, that no one really seems to know how to add a watermark to the listview the same way that Windows Explorer does. Searching through the Mircosoft documentation for the listview control uncovers the Windows message LVM_SETBKIMAGE and the LVBKIMAGE record or structure. It would seem that this is a good place to start.

There are three key members of the LVBKIMAGE structure which determine how (and if) a bitmap image is displayed in the listview. The three members are ulFlags, hbm, and pszImage. There are three flags to indicate the source of the image, and two flags to indicate the style. The source flags are LVBKIF_SOURCE_NONE, LVBKIF_SOURCE_HBITMAP, and LVBKIF_SOURCE_URL. These three source flags are defined as follows:

LVBKIF_SOURCE_NONE
      The list-view control has no background image.
LVBKIF_SOURCE_HBITMAP
      A background bitmap is supplied via the hbm member of LVBKIMAGE.
LVBKIF_SOURCE_URL
      The pszImage member contains the URL of the background image.

The LVBKIF_SOURCE_NONE flag when set produces obvious results. No background image is displayed.

The LVBKIF_SOURCE_HBITMAP flag is interesting in that the Microsoft documentation indicates that the hbm member is "not currently used". Interesting that there is a flag for a bitmap handle that is not used. As we'll see later, it is more that using the hbm member is undocumented, rather than unused.

This leaves the LVBKIF_SOURCE_URL flag using the pszImage member to set the background image. Most examples found on the Internet use this flag with either the LVBKIF_STYLE_TILE or LVBKIF_STYLE_NORMAL flags. This simple code will tile a bitmap on the listview background:


procedure TForm1.DrawWallpaper;
var
   lv : TLVBKIMAGE;
begin
   FillChar(lv, SizeOf(lv), 0);
   lv.ulFlags := LVBKIF_SOURCE_URL or LVBKIF_STYLE_TILE;
   lv.pszImage := 'c:\sample.bmp';
   SendMessage(ListView1.Handle, LVM_SETBKIMAGE, 0, integer(@lv));
end


You may have to override the listview Windows message WM_ERASEBKGND for the image to actually display without erasing first. Refer to the demo program to see how to do this.

Watermark
Okay, now for the really fun stuff... adding a watermark to the listview. This is where most people seem to have quite a bit of trouble getting it to work. Part of the problem seems to lie in the fact that using LVBKIF_TYPE_WATERMARK and hbm is undocumented.

The only flag that needs to be set is LVBKIF_TYPE_WATERMARK. There are two other flags available: LVBKIF_SOURCE_HBITMAP and LVBKIF_FLAG_ALPHABLEND. The Microsoft documentation indicates that these flags can be used with LVBKIF_TYPE_WATERMARK, but I found they do not work. Setting LVBKIF_SOURCE_HBITMAP is not really necessary and I've found that setting the LVBKIF_FLAG_ALPHABLEND flag causes the watermark to disappear. I'm sure there is a purpose to this flag, but I haven't been able to find it or make it work, even when the bitmap is created with an Alpha Channel. Never mind, you only need to set the LVBKIF_TYPE_WATERMARK flag.

You cannot load the watermark by setting the pszImage member. I have only managed to get the watermark to appear by setting the hbm member using a bitmap stored in a resource. There may be another way, but I haven't found it. Given you have a bitmap resource called 'LVWALLPAPER', you can load the image as a watermark using the following code:


procedure TForm1.DrawWallpaper;
var
   lv : TLVBKIMAGE;
   hinst: Thandle;

const
   LVBKIF_TYPE_WATERMARK = $10000000;
begin
   hinst := GetModuleHandle(nil);
   if (hinst = 0) then exit;

   FillChar(lv, SizeOf(lv), 0);
   lv.ulFlags := LVBKIF_TYPE_WATERMARK;
   lv.hbm := LoadImage(hinst,PChar('LVWALLPAPER'),IMAGE_BITMAP,0,0,
LR_CREATEDIBSECTION or
LR_LOADTRANSPARENT);
   SendMessage(ListView1.Handle, LVM_SETBKIMAGE, 0, integer(@lv));
end;


That's it! That's all you need.

Special Consideration
Though it seems simple, there are a few things that you should know. As I've said, setting any other flag on its own or in combination with LVBKIF_TYPE_WATERMARK will not work. Setting the pszImage member will not work. You cannot switch between a background image and a watermark without first removing the existing background / watermark. To get around this, I've found that simply erasing both, one at a time, before setting the one I want, works well.

The xOffsetPercent and yOffsetPercent members have no effect on the watermark, but do affect the way a tiled background is displayed. For a tiled background, setting these two members specify how many pixels to crop into the image for the first tile. This is a little confusing in the Microsoft documentation.

I've found setting the cchImageMax member is unnecessary in my implementation.

You will find that resizing any of the columns when a background image or watermark is set will cause the listview to flicker really bad. So bad that you wouldn't want to use it. This is because the background is erased and redrawn before the item details are displayed every time the column is moved. Setting Listview1.Doublebuffered does not seem to improve the situation much. You have to set listview's REAL doublebuffer with the following call to the extended styles:


SendMessage(ListView1.Handle,LVM_SETEXTENDEDLISTVIEWSTYLE,
LVS_EX_DOUBLEBUFFER, LVS_EX_DOUBLEBUFFER);


You will be amazed at how well this works!

In Windows XP, the watermark will not show up in folders like My Music if the background is anything other than white. Give it a try. Set Windows background to another color and see if the watermarks show up in My Music or My Pictures. They won't. However, a listview watermark set through the method shown here will show up even if the background is not white. This could look unusual if the bitmap background is white. Setting the LR_LOADTRANSPARENT flag when loading the bitmap into the hbm member with LoadImage() will cause the background to be transparent only if the bitmap is created with 8 bits of color (which is perfect for a greyscale logo for instance). You should be conscieous of this when designing the background, or disable the watermark, as Windows XP does, if the background is any other color.

One last thing. If you have to set the hbm member using a resouce, it would be nice to set the pszImage member from a resouce as well, instead of using a path to the file. Well, you can use a resource to set this member. The format to use would be :


lv.pszImage := 'res://c:\\thisfile.exe/#2/#101';


Here, the #2 identifies the resource as a bitmap, and the #101 is the resource ID (this could be whatever the ID actually is, ie: LVWALLPAPER in the example above). See the complete documentation to see how it is implemented.

Depending on the version of Delphi you are using, you may have to insert ComObj in the uses clause, or call CoInitialize() when the form is created, and CoUninitialize() when the form is destroyed. I've found that using ComObj alone works.

Hope you found this article useful. You can download the entire project here. The kmListView component will implement these concepts in the next version.


procedure TForm1.DrawWallpaper;
var
   lv : TLVBKIMAGE;
   hinst: Thandle;
   szBuffer: array [0..MAX_PATH] of char;
   sPath: string;

const
   LVBKIF_TYPE_WATERMARK = $10000000;
   LVBKIF_FLAG_TILEOFFSET = $00000100;

begin
   if wallpaper = '' then exit;

   hinst := GetModuleHandle(nil);
   if (hinst = 0) then exit;

   // first clear the wallpaper otherwise you will not //
   // be able to switch between tile and watermark if //
   // one or the other is already set //
   // need to clear one at a time to work properly //
   // ------------------------------------------------ //
   FillChar(lv, SizeOf(lv), 0);
   lv.pszImage := '';
   lv.hbm := 0;
   lv.ulFlags := LVBKIF_TYPE_WATERMARK;
   SendMessage(ListView1.Handle, LVM_SETBKIMAGE, 0, integer(@lv));
   lv.ulFlags := LVBKIF_SOURCE_NONE;
   SendMessage(ListView1.Handle, LVM_SETBKIMAGE, 0, integer(@lv));

   if not showWP then exit;

   if tileWP then begin
      lv.ulFlags := LVBKIF_SOURCE_URL or LVBKIF_STYLE_TILE or LVBKIF_FLAG_TILEOFFSET;
      GetModuleFileName(hinst, szBuffer, MAX_PATH);
      sPath := Format('res://%s/#2/%s',[szBuffer,wallpaper]);
      sPath := sPath;
      lv.pszImage := PChar(sPath);
      end
   else begin
      lv.ulFlags := LVBKIF_TYPE_WATERMARK;
      lv.hbm := LoadImage(hinst,PChar(wallpaper),
IMAGE_BITMAP,0,0,
LR_CREATEDIBSECTION or
LR_LOADTRANSPARENT);
      end;

   lv.xOffsetPercent := xOffset;
   lv.yOffsetPercent := yOffset;

   SendMessage(ListView1.Handle, LVM_SETTEXTBKCOLOR, 0, integer(CLR_NONE));
   SendMessage(ListView1.Handle, LVM_SETBKIMAGE, 0, integer(@lv));
end; { DrawWallpaper }


[ COMMENTS ]


Back to main page