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=
) );
}


Reply via email to