Steven Lloyd wrote:
MouseMove returns the label, and the x, y coordinates of the mouse, right?
Right - but it gives an unsigned value. This is not usually a problem,
as you don't normally seem mouse move locations outside the client area,
which always has 'small' positive values. The code below contains a
work-around for this issue, and I will look at Jeremy's solution from
the other related thread.
Not wanting to steal your thunder on this one, but I had a few hours on
a plane this-morning, and I came up with a way of doing the drawing that
doesn't need the Redraw/InvalidateRect calls. It also deals with not
drawing over windows that overlap the one you hover the mouse over.
I tried to avoid using Win32::API, but in the end needed a couple of
extra calls to get the full functionality that I was looking for.
Regards,
Rob.
#!perl -w
use warnings;
use strict;
use Win32::GUI;
use Win32::GUI::BitmapInline();
use Win32::API;
# Some constants
sub CW_USEDEFAULT() {0x80000000}
sub R2_NOT() {6}
sub PS_INSIDEFRAME() {6}
sub NULL_BRUSH() {5}
sub GWL_STYLE() {-16}
sub CWP_SKIPINVISIBLE() {1}
sub CLR_BLUE() {0xFF0000}
# Using Win32::API, as GetWindowDC not in Win32::GUI
my $GetWindowDC = Win32::API->new("user32", "GetWindowDC", 'N', 'N')
or die "Can't load GetWindowDC: $!";
# Note that the prototype of 'NNNN' is a cheat, and relies on the fact
# that the point structure (2nd argument) that must be passed by value
# can be pushed onto the calling stack as 2 16-bit numbers, rather than
# a single 32-bit struct. Doing it this way avoids having to use
# Win32::API::Struct to define a POINT struct, and using the prototype
# 'NSN'.
my $ChildWindowFromPointEx = Win32::API->new("user32",
"ChildWindowFromPointEx", 'NNNN', 'N')
or die "Can't load ChildWindowFromPointEx: $!";
# Some resources that we'll be using a lot; create them once:
my $cursor = get_cursor();
my $blank_cursor = get_cursor2();
my $pen = Win32::GUI::Pen->new(
-style => PS_INSIDEFRAME,
-width => 3,
-color => 0, # pen color is ignored when foreground mix mode is R2_NOT
) or die "Creating Pen";
my $null_brush = Win32::GUI::GetStockObject(NULL_BRUSH);
# The window handle of the window that we're over, and have
# drawn a rectangle around. Use 0 to indicate no such window.
my $curHwnd = 0;
# Create out main window
my $mw = Win32::GUI::Window->new(
-name => "MainWindow",
-title => "Win32::GUI Spy++",
-left => CW_USEDEFAULT,
-size => [215,140],
-resizable => 0,
) or die "Creating Main Window failed";
# Instruction label
$mw->AddLabel(
-name => "Instructions",
-text => "Drag the target over the window you want information about",
-pos => [5,4],
-size => [160, 32],
) or die "Creating Instruction label failed";
# Target bitmap label
$mw->AddLabel(
-name => "Target",
-pos => [170,4],
-icon => $cursor,
-background => CLR_BLUE,
-notify => 1,
-onMouseDown => \&mouseDown,
-onMouseUp => \&mouseUp,
-onMouseMove => \&mouseMove,
) or die "Creating Target Label failed";
# Label and Textfield to report window handle
$mw->AddTextfield(
-name => 'HWND',
-prompt => [ 'Handle', 40 ],
-pos => [5,40],
-size => [160,20],
-readonly => 1,
) or die "Creating HWND Field failed";
# Label and Textfield to report window title/text
$mw->AddTextfield(
-name => 'TITLE',
-prompt => [ 'Title', 40 ],
-pos => [5,62],
-size => [160,20],
-readonly => 1,
) or die "Creating TITLE field failed";
# Label and Textfield to report window class
$mw->AddTextfield(
-name => 'CLASS',
-prompt => [ 'Class', 40 ],
-pos => [5,84],
-size => [160,20],
-readonly => 1,
) or die "Creating CLASS field failed";
# Display the window and enter the dialog phase
$mw->Show();
Win32::GUI::Dialog();
# We're done
exit(0);
######################################################################
# Event handlers
######################################################################
######################################################################
# Target Label Mouse Down handler:
# - Capture the mouse, set the cursor to the target cursor, and remove
# the icon from the label
sub mouseDown{
my $label = shift;
$label->SetCapture();
Win32::GUI::SetCursor($cursor);
$label->SetIcon($blank_cursor);
return;
}
######################################################################
# Target Label Mouse Up handler:
# - If the label has captured the mouse (should have done in the
# mouse down handler), then release capture, set the label icon
# and set the fact that we're no longer over a window
# - Force a redraw of all windows, in case we got any of our
# drawing wrong.
sub mouseUp{
return unless Win32::GUI::GetCapture();
my $label = shift;
$label->ReleaseCapture();
$label->SetIcon($cursor);
$curHwnd = 0;
# redraw everything to remove any trace that we
# were here (in case my drawing is wrong somewhere).
Win32::GUI::InvalidateRect(0,1);
return;
}
######################################################################
# Target Label Mouse Up handler:
# - If the label has captured the mouse, then find the window the
# mouse is over. If it has changed, then undraw any highlight
# rectangle we have already drawn, draw the highlight rectangle
# around the new window and update the information fields.
sub mouseMove{
return unless Win32::GUI::GetCapture();
my ($label, $cx, $cy) = @_; # x,y in client co-ordinates
Win32::GUI::SetCursor($cursor);
# Take into account that client co-ordinates can be negative,
# convert pos to screen co-ordinates and get handle to window
# at that position:
$cx -= 65536 if $cx > 32767;
$cy -= 65536 if $cy > 32767;
my ($sx, $sy) = $label->ClientToScreen($cx, $cy);
my $hwnd=GetWindowFromPoint($sx, $sy);
# If we moved into a new window, then undraw the rect
# from the previous window, draw the rect around the
# new window, and update the textfields:
if($hwnd != $curHwnd) {
DrawInvertedRect($curHwnd) if $curHwnd; # undraw old highlight
DrawInvertedRect($hwnd); # draw new highlight
$curHwnd = $hwnd; # store the window with the highlight
# Update the information fields
$mw->HWND->Text(sprintf("0x%08X", $hwnd));
$mw->CLASS->Text(Win32::GUI::GetClassName($hwnd));
$mw->TITLE->Text(Win32::GUI::Text($hwnd));
}
return;
}
######################################################################
# Helper Functions
######################################################################
######################################################################
# GetWindowFromPoint
# - returns the handle of the window whose location is at the screen
# co-ordinates x, y
sub GetWindowFromPoint
{
my ($sx, $sy) = @_;
# Firstly get the window handle at the screen
# co-ordinates given. This ignores hidden,
# disabled and static text controls.
my $hwnd=Win32::GUI::WindowFromPoint($sx, $sy);
# Convert $x, $y to client co-ordinates of the hwnd
# that has been found, and use ChildWindowFromPointEx
# to find static text controls and disabled
# windows (but ignoring hidden windows, as we can't
# draw on them). Only use return value if not NULL(0)
# which happens if we're in the non-client region of a window
my ($cx, $cy) = Win32::GUI::ScreenToClient($hwnd, $sx, $sy);
my $chwnd=$ChildWindowFromPointEx->Call($hwnd, $cx, $cy,
CWP_SKIPINVISIBLE);
$hwnd = $chwnd if $chwnd;
# If We've got a child window, then walk it's siblings to see
# if there is a visible window higher in the z-order that we
# should be using. This is needed, for example, when a groupbox
# contains controls that have the same parent as the groupbox
# itself. I.e this copes with multiple controls with the same
# parent occupying the same space - GetChildWindowFromPoint only
# ever gives the lower z-order window. Why?
#
# It would be better to use EnumChildWindows, but we don't have
# that in Win32::GUI, and I don't trust the Win32::API callback
# support.
if((Win32::GUI::GetWindowLong($hwnd, GWL_STYLE) & WS_CHILD) ==
WS_CHILD) {
my $shwnd = $hwnd;
while($shwnd = Win32::GUI::GetWindow($shwnd, GW_HWNDNEXT)) {
my ($l, $t, $r, $b) = Win32::GUI::GetWindowRect($shwnd);
if( Win32::GUI::IsVisible($shwnd) and
$sx >= $l and $sx <= $r and
$sy >= $t and $sy <= $b) {
$hwnd = $shwnd;
}
}
}
return $hwnd;
}
######################################################################
# DrawInvertedRect
# - Draws a rectangle around a window. Uses a NULL brush to avoid
# painting over the window content; use a pen with style
# PS_INSIDEFRAME to adjust the rectangle edge to be drawn entirely
# inside the rectangle boundaries; use a foreground mix mode
# (SetROP2) of R2_NOT, so that drawing the same rectangle a second
# time, undoes the draw.
sub DrawInvertedRect
{
my $hwnd = shift or return 0;
# Get window position in screen co-ordinates
my($l, $t, $r, $b) = Win32::GUI::GetWindowRect($hwnd);
# Get a handle to the WINDOW DC (the whole window, not just
# the client area, so that we can draw in the non-client
# area for window where we need to)
my $hdc = $GetWindowDC->Call($hwnd);
# Set the foreground mix mode
my $oldROP = Win32::GUI::DC::ROP2($hdc, R2_NOT);
# Set the pen and brush
my $oldPen = Win32::GUI::DC::SelectObject($hdc, $pen);
my $oldBrush = Win32::GUI::DC::SelectObject($hdc, $null_brush);
# Draw the rectangle
Win32::GUI::DC::Rectangle($hdc, 0, 0, $r-$l, $b-$t);
# Restore the forgeround mix mode, pen and brush, and relese
# the window DC
Win32::GUI::DC::ROP2($hdc, $oldROP);
Win32::GUI::DC::SelectObject($hdc, $oldPen);
Win32::GUI::DC::SelectObject($hdc, $oldBrush);
Win32::GUI::DC::ReleaseDC($hwnd, $hdc);
return 1;
}
######################################################################
# get_cursor
# - returns a Win32::GUI::Cursor object for the target cursor/icon
sub get_cursor
{
return Win32::GUI::BitmapInline->newCursor( q(
AAACAAEAICAAAA8AEAAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAEAAAAAAAAAAAAAAAAA
AAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAGDAAACbIAABQFAAApsoA
AUAFAAECgQACoAqAAqgqgAIBAIACqCqAAqAKgAECgQABQAUAAKbKAABQFAAAJsgAABgwAAAHwAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////g////g
D///wAf//4gj//8YMf/+ODj//nx8//wMYH/8A4B//AOAf/wDgH/8DGB//nx8//44OP//GDH//4gj
///AB///4A////g///////////////////////////////////////8=
) );
}
######################################################################
# get_cursor2
# - returns a Win32::GUI::Cursor object for a blank (transparent)
# cursor/icon, used to replae the target icon in the TARGET label
# while we're draggin the target around
sub get_cursor2
{
return Win32::GUI::BitmapInline->newCursor( q(
AAABAAEAICAAAAEAAQAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////8=
) );
}