How to eliminate screen flickering when receiving a handle to a DrawFocusRect display object

I am trying to draw a focused rectangle around a selected screen object in the Apprehend screen capture component that was developed many years ago. I can DrawFocusRect by getting the handle of the object under the cursor using Handles: = WindowFromPoint (P); but it makes me hide and then show myself so that it works differently, returning the handle to itself.

Unfortunately, when I hide and show the form, it causes flickering when the form is hidden and shown.

I can get a bitmap image of the selected object without any problems, just drawing the selected object infuriates me.

Does anyone have any suggestions to draw a FocusedRect around a selected object so as not to flicker? Is there any API to get a screen handle if the form is on top of it?

I tried using Handles: = WindowFromDC (ScreenDC), so I don't need to hide and show the form, but WindowFromDC still returns the form, not the screen.

TCaptureObjectForm is transparent and lies above the screen. I need TCaptureObjectForm in the component.

// Event FormMouseMove - added 08/2/2011

procedure TCaptureObjectForm.FormMouseMove( Sender: TObject; Shift: TShiftState; X, Y: Integer ); const crHand = -18; var P: TPoint; Handles: HWND; Rect: TRect; ScreenDC: HDC; begin // hide the TCaptureObjectForm form so the screen is found by WindowFromPoint Self.Hide; // get the object on the screen GetCursorPos( P ); Handles := WindowFromPoint( P ); // tried this but it returns self.handle rather than the screen handle //ScreenDC := GetDC( 0 ); //Handles := WindowFromDC(ScreenDC); //ReleaseDC( 0, ScreenDC ); // restore the TCaptureObjectForm Self.Show; // get object rect GetWindowRect( Handles, Rect ); // draw a rect to show it is focused Self.Canvas.DrawFocusRect( Rect ); end; 
+4
source share
2 answers

This article is an example in Microsoft's Visual Basic that does something very similar to what you need.

They take the following approach:

  • Mouse capture in Form_MouseDown .
  • When the mouse moves, draw a rectangle around the window indicated by the mouse: Form_MouseMove .
  • Release the mouse in Form_MouseUp , and also cancel the entire screen to remove the last drawn rectangle.

They are drawn directly in the window that they select. I do not think that all flicker can be avoided using the transparent window approach.

This sample code seems incomplete and does not work, so I modified it (and translated it to Delphi):

 // Not global variables, but private form ones var HwndLastTracked: HWND; CapturedMouse: boolean; procedure InvertTracker(hwndWindow: HWND); var rc: TRect; dc: HDC; pen, oldPen: HPEN; oldBrush: HBRUSH; style, exStyle: longint; cx, cy: integer; begin GetWindowRect(hwndWindow, rc); // Window coordinates of the origin (top-left corner) of a window is (0, 0) OffsetRect(rc, -rc.Left, -rc.Top); // DC returned by GetWindowDC covers the full window area, but in Windows // Vista/7 it seems to be clipped excluding the nonclient region, due to // DWM handling nonclient drawing, so it doesn't allow painting over it. // Thus we need to skip this nonclient area and that is why I adjust the // window rect to match the client area. Using GetClientRect instead of // GetWindowRect is not suitable as excludes scroll bars and child // parts drawed in WM_NCPAINT, such as Windows' WS_EXEDGEs and Delphi's // bevels. style := GetWindowLong(hwndWindow, GWL_STYLE); exStyle := GetWindowLong(hwndWindow, GWL_EXSTYLE); if style and WS_CAPTION <> 0 then begin if exStyle and WS_EX_TOOLWINDOW <> 0 then cy := GetSystemMetrics(SM_CYSMCAPTION) else cy := GetSystemMetrics(SM_CYCAPTION); // discard area covered by caption Inc(rc.Top, cy); end; if style and WS_THICKFRAME <> 0 then begin cx := GetSystemMetrics(SM_CXFRAME); cy := GetSystemMetrics(SM_CYFRAME); end else if style and WS_DLGFRAME <> 0 then begin cx := GetSystemMetrics(SM_CXDLGFRAME); cy := GetSystemMetrics(SM_CYDLGFRAME); end else if style and WS_BORDER <> 0 then begin cx := GetSystemMetrics(SM_CXBORDER); cy := GetSystemMetrics(SM_CYBORDER); end else begin cx := 0; cy := 0; end; if (cx <> 0) or (cy <> 0) then begin // discard area covered by borders OffsetRect(rc, cx, cy); Dec(rc.Right, cx*2); Dec(rc.Bottom, cy*2); end; // Windows API functions don't raise exceptions, so I don't use try-finally dc := GetWindowDC(hwndWindow); // Option 1: focused rect //DrawFocusRect(dc, rc); // Option 2: inverted thick border SetROP2(dc, R2_NOT); pen := CreatePen(PS_INSIDEFRAME, 3 * GetSystemMetrics(SM_CXBORDER), 0); oldPen := SelectObject(dc, pen); oldBrush := SelectObject(dc, GetStockObject(NULL_BRUSH)); Rectangle(dc, rc.Left, rc.Top, rc.Right, rc.Bottom); SelectObject(dc, oldBrush); SelectObject(dc, oldPen); DeleteObject(pen); // End option 2 ReleaseDC(hwndWindow, dc); end; procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if SetCapture(Handle) <> 0 then begin CapturedMouse := true; HwndLastTracked := 0; Screen.Cursor := crCross; end; end; procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var hwndCaptured: HWND; begin if CapturedMouse then begin hwndCaptured := WindowFromPoint(ClientToScreen(Point(X, Y))); // Uncomment this for track root windows instead of childs //hwndCaptured := GetAncestor(hwndCaptured, GA_ROOT); if hwndCaptured <> HwndLastTracked then begin if HwndLastTracked <> 0 then InvertTracker(HwndLastTracked); InvertTracker(hwndCaptured); HwndLastTracked := hwndCaptured; end; end; end; procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if CapturedMouse then begin ReleaseCapture; CapturedMouse := false; if HwndLastTracked <> 0 then begin InvertTracker(HwndLastTracked); HwndLastTracked := 0; end; Screen.Cursor := crDefault; end; end; 

Here's a screenshot of how Microsoft uses this technique in Visual Studio Spy ++. The red ball and the text are mine!

Spy ++

+2
source

I use this

 procedure TForm1.FormCreate(Sender: TObject); begin Form1.DoubleBuffered:=True; end; 
0
source

All Articles