Hi everyone,

After much experimentation, I have finally succeeded in creating an 
owner-drawn control in Win32::GUI. I decided to create this post 
detailing how to create an owner-drawn control in case someone else has 
the need to use one.

For those of you who don't know, an owner-drawn control allows the user 
more control over the appearance of the control. This usually involves 
responding to messages sent whenever the control needs to be drawn and 
drawing the control in anyway that you wish.

In order to get my sample to work you will need to install the PeekPoke 
module from CPAN. This module allows reading and writing of data to and 
from arbitrary memory locations. This is needed to set the height of the 
items of the listbox. More on this below.

This example was created using ActiveState Perl v5.12.0 running on 
Windows XP.

For this example, I will demonstrate how to create an owner-drawn 
listbox. The listbox will have items with a larger height, will display 
two lines of text with different formats, and an image.

All of the files related to this example at the bottom of this post.

Anyway, on with the example.

I decided to store the information about each listbox item in an 
external XML file and use XML::Simple to parse it. This makes it rather 
simple to change the information for the listbox items.

I also created 8 simple 40x40 bitmaps that will be displayed in each 
listbox item.

Now for a description of the code.

The first step is to create a window for our listbox and load our XML 
data from the file using XML::Simple::XMLin(). This is all fairly 
simple, so I won't bother explaining.

Next step is to create a hook for the WM_MEASUREITEM message. This 
message is sent when the listbox is created so the user can specify the 
width and height of the listbox items. The $lParam variable contains the 
address of the structure that is passed to the message, which needs to 
be filled out. Here we use the poke() function to write the desired 
height into the structure, which in our case is 50. 16 is the offset of 
the itemHeight member of the structure which needs to contain the height 
that we want when the message returns.

Next a hook is created for the WM_DRAWITEM message. This message is sent 
whenever an item in the listbox requires drawing. First step is to 
unpack the structure that is passed to the message. If the itemID 
contains -1, then the listbox is empty, so we simply return from the sub 
if this occurs. Otherwise, it contains the zero-based index of the item 
being drawn. The itemAction member contains the action required for the 
drawing. Here we respond if the entire item needs drawing. To begin with 
we draw the bitmap for the item. First we create a compatible DC from 
the DC that is passed to the message, then select the bitmap for the 
current item into it. Then we BitBlt() the contents of the compatible DC 
into the item DC. Next we need to draw the text that will be displayed 
in the item. We create a large font that will be used for the item's 
heading, and select the font into the item DC, remembering the old font. 
Then we draw the text into the item DC using DrawText(). Next we select 
the old font, and draw the other text that will be displayed in the 
item. That completes the drawing for the item, so we return from our sub.

Next step is to create our listbox. The only difference here from 
creating an ordinary listbox is to specify the LBS_OWNERDRAWFIXED style. 
This specifies that the listbox will be owner-drawn, and all the items 
have the same height. An alternative would be to use the 
LBS_OWNERDRAWVARIABLE style instead, which specifies that each item will 
have a different height. In this case, the WM_MEASUREITEM would be sent 
for each item when the control is created, not just once like our case.

Next we loop through each item returned from the XML file, create a 
Win32::GUI::Bitmap from the file name specified in the file, and add the 
relevant data to an array which will be used when drawing the listbox 
items. We also add an item to the listbox using the text as a place 
holder, although it won't get drawn, so it doesn't matter what is 
inserted here. Then we simply show the window and enter the dialog phase.

The listbox acts like any other listbox, it just has larger items and 
different content. This is demonstrated here when an item is selected: 
the heading and text of the selected item are printed.

That's it for creating an owner-drawn listbox.

Various other controls can also be owner-draw, such as buttons, labels, 
and combo boxes. I have yet to try it with other controls, but it 
shouldn't be much different from a listbox.

More information about owner-draw controls can be found in the Windows 
SDK Documentation.

I hope that someone finds this example useful. If you come up with 
something interesting, I wouldn't mind a reply post detailing what you 
have done.

Kevin.

Here are the files:

This is the main code:

#!perl
################################################################################
#
#   customlistbox.pl
#
#   Win32::GUI Owner-drawn Controls
#
#   This script demonstrates the creation and use of an owner-drawn listbox.
#
#   Requirements:
#   Win32::GUI
#   PeekPoke
#   XML::Simple
#
#   This program was written using ActiveState Perl 5.12.0 Build 1200 
running on
#   Windows XP and using Win32::GUI v1.06, PeekPoke v0.01, and 
XML::Simple v2.18
#
################################################################################
use strict;
use warnings;

use PeekPoke qw(poke);
use Win32::GUI qw();
use Win32::GUI::Constants qw(CW_USEDEFAULT WM_MEASUREITEM WM_DRAWITEM
     ODA_DRAWENTIRE SRCCOPY DT_LEFT DT_TOP DT_WORDBREAK LBS_OWNERDRAWFIXED);
use XML::Simple;

# Create our main window
my $winMain = Win32::GUI::Window->new(
     -name      => 'winMain',
     -text      => 'Owner-Drawn Listbox',
     -size      => [ 320, 240 ],
     -minwidth  => 320,
     -minheight => 240,
);

# Load XML data
my $ListBoxItems = XMLin('customlistbox.xml');
my @Items;

# Create a hook to handle WM_MEASUREITEM message. This message is used 
to set the
# height of the listbox items.
$winMain->Hook(
     WM_MEASUREITEM,
     sub {
         my( $self, $wParam, $lParam, $type, $msgcode ) = @_;
         return 1 unless $type == 0;
         return 1 unless $msgcode == WM_MEASUREITEM;
         # Write desired height of items to structure. 16 is the offset 
of the
         # itemHeight member of the MEASUREITEMSTRUCT structure
         poke( $lParam + 16, 50 );
         return 1;
     },
);

# Create a hook to handle the WM_DRAWITEM message. This message is sent 
whenever
# a listbox item needs drawing
$winMain->Hook(
     WM_DRAWITEM,
     sub {
         my( $self, $wParam, $lParam, $type, $msgcode ) = @_;
         my %drawitem;
         # Unpack data from the structure
         @drawitem{qw(CtlType CtlID itemID itemAction itemState hwndItem 
hDC left
             top right bottom itemData)} = unpack 'IIIIILLllllL', unpack 
'P48',
             pack 'L', $lParam;
         # itemID will contain -1 if there are no items, so we just return
         return 1 if $drawitem{'itemID'} == -1;

         # Draw the bitmap and text for the list box item.
         if( $drawitem{'itemAction'} == ODA_DRAWENTIRE ){
             my $hDC = $drawitem{'hDC'};

             # Display the bitmap associated with the item.
             my $image    = $Items[ $drawitem{'itemID'} ]{'image'};
             my $memdc    = Win32::GUI::DC::CreateCompatibleDC($hDC);
             my $oldimage = $memdc->SelectObject($image);
             Win32::GUI::DC::BitBlt(
                 $hDC, $drawitem{'right'} - 45, $drawitem{'top'} + 5,
                 40, 40, $memdc, 0, 0, SRCCOPY
             );

             # Display the text associated with the item.
             my $titlefont = Win32::GUI::Font->new(
                 -height => 12,
                 -weight => 700,
             );
             my $oldfont = Win32::GUI::DC::SelectObject( $hDC, $titlefont );
             Win32::GUI::DC::DrawText(
                 $hDC,
                 $Items[ $drawitem{'itemID'} ]{'heading'},
                 $drawitem{'left'} + 5, $drawitem{'top'} + 5,
                 $drawitem{'right'} - 50, $drawitem{'bottom'} - 5,
             );
             Win32::GUI::DC::SelectObject($drawitem{'hDC'}, $oldfont);
             Win32::GUI::DC::DrawText(
                 $hDC,
                 $Items[ $drawitem{'itemID'} ]{'text'},
                 $drawitem{'left'} + 5, $drawitem{'top'} + 30,
                 $drawitem{'right'} - 50, $drawitem{'bottom'} - 5,
                 DT_LEFT | DT_TOP | DT_WORDBREAK
             );
         }
         return 1;
     }
);

# Create our listbox control
my $lsbCustom = $winMain->AddListbox(
     -name             => 'lsbCustom',
     -pos              => [ 10, 10 ],
     -size             => [ $winMain->ScaleWidth() - 20, 
$winMain->ScaleHeight() - 20 ],
     -nointegralheight => 1,
     -vscroll          => 1,
     -pushstyle        => LBS_OWNERDRAWFIXED,
);
# Add items to listbox
foreach my $item ( @{ $ListBoxItems->{'item'} } ){
     my $bmp = Win32::GUI::Bitmap->new( $item->{'image'} );
     push @Items, {
         heading => $item->{'heading'},
         text    => $item->{'text'},
         image   => $bmp,
     };
     $lsbCustom->InsertString( $item->{text} );
}

$winMain->Show();

Win32::GUI::Dialog();

sub winMain_Terminate {
     return -1;
}

sub winMain_Resize {
     my $width  = $winMain->ScaleWidth();
     my $height = $winMain->ScaleHeight();
     $lsbCustom->Resize( $width - 20, $height - 20 );
     return 1;
}

sub lsbCustom_SelChange {
     my $index = $lsbCustom->GetCurSel();
     print <<EOT;
$Items[$index]{heading}
$Items[$index]{text}
EOT
     return 1;
}

__END__ # of customlistbox.pl



This is the XML file that stores the data for each item in the listbox:

<!-- customlistbox.xml -->
<listboxitems>
<item>
<heading>Item 1</heading>
<image>item1.bmp</image>
<text>This is some text for item 1</text>
</item>
<item>
<heading>Item 2</heading>
<image>item2.bmp</image>
<text>This is some text for item 2</text>
</item>
<item>
<heading>Item 3</heading>
<image>item3.bmp</image>
<text>This is some text for item 3</text>
</item>
<item>
<heading>Item 4</heading>
<image>item4.bmp</image>
<text>This is some text for item 4</text>
</item>
<item>
<heading>Item 5</heading>
<image>item5.bmp</image>
<text>This is some text for item 5</text>
</item>
<item>
<heading>Item 6</heading>
<image>item6.bmp</image>
<text>This is some text for item 6</text>
</item>
<item>
<heading>Item 7</heading>
<image>item7.bmp</image>
<text>This is some text for item 7</text>
</item>
<item>
<heading>Item 8</heading>
<image>item8.bmp</image>
<text>This is some text for item 8</text>
</item>
</listboxitems>
<!-- end of customlistbox.xml -->

If you execute this script, it will create a file called pics.7z file, 
which will contain the 8 bitmaps needed for this sample:

#!perl
use strict;
use warnings;

use MIME::Base64;

open my $fh, '>', 'pics.7z' or die $!;
binmode $fh;
print {$fh} MIME::Base64::decode(
'N3q8ryccAANPwVVtOwEAAAAAAAAjAAAAAAAAAJASfkEAIRNayxcGoME2nyL7I4JzfZi4oHYg66A8
nm6WsRvMHTne+oX2PHIJM7ayDfdnbZ0DmCN8Mf70re7XhMyBeX4+OafcrXhvLiG669M+EMuzgnG7
JvuHqsUDJQokFWg0SzmcesrNrAHXMApzksKeghHSU1HMZ64/6cXUSTzQaCJdREH7ieEAAACBMweu
D9Uvw85WbCkfSCtBMmjGwE0B4XqeDwoyHBt1/T8r3bH8o1BWWPseZbEvATR9EeL4s4UpAsX59y9L
RF7bndv+H7Dz0pCHk43K2555nX5iAiwmibuV8uDOx83QgHHTqy9AORcPkqPfO6duMlkZ+UYo1t0/
TapX+1Jl1LSaAcpSost05OeRFdSSTWGt3tvzEPzEG8sIrZ+vTlWBzDSQrvvsJkdLC0r63jRJhP2+
sK6GAAAXBoCFAQmAtgAHCwEAASMDAQEFXQAQAAAMgWMKAVQcA6kAAA==');
close $fh;

__END__




------------------------------------------------------------------------------
Sell apps to millions through the Intel(R) Atom(Tm) Developer Program
Be part of this innovative community and reach millions of netbook users 
worldwide. Take advantage of special opportunities to increase revenue and 
speed time-to-market. Join now, and jumpstart your future.
http://p.sf.net/sfu/intel-atom-d2d
_______________________________________________
Perl-Win32-GUI-Users mailing list
Perl-Win32-GUI-Users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/perl-win32-gui-users
http://perl-win32-gui.sourceforge.net/

Reply via email to