[2 days left] What’s wrong with your cloud strategy? Learn why multicloud solutions matter with Nimble Storage.Register Now

x
?
Solved

When painting on a form's canvas: it paints on the title bar not the client area

Posted on 2009-05-05
9
Medium Priority
?
662 Views
Last Modified: 2012-05-06
When painting to a TForm's canvas, the point 0,0 is not the top left of the client area, instead it is the top left of the title bar. Also the form's border is a problem, because the client area only starts 4 pixels in on my PC.
So what should be 0,0 is actually 4, 31 on my PC.
Is there a way to work with the canvas and to only refer to the client area?
I guess you could do a Windows API call to get the title bar's height but this doesn't seem normal to me.


0
Comment
Question by:rfwoolf
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 6
  • 2
9 Comments
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24309206
The ClientRect would dictate the area inside your form. If you are using standard GDI on a form's canvas, you may have to convert to client coordinates from screen coordinates, too, using ScreenToClient.
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24309311
Hi Eddie

Sounds promising. I've tried extensively and could use some working code. All I need to do is draw a rectangle around some buttons on a form.
Here's my code below (I've only put the relevant parts) and can't get it to see 0,0 as the top-left of the client area, instead 0,0 is the top-left of the titlebar.

Cheers
procedure TForm2.BorderMyControl(DaControl : TControl);
var
  C : TCanvas;
  MyHDC : HDC;
  mypoint1, mypoint2, mypoint3 : tpoint;
begin
  C := TCanvas.Create;
  with C do
  try
      MyHDC := GetWindowDC(DaControl.Parent.Handle);
      try
        Handle := MyHDC;
        Brush.Style := bsClear;
        Pen.Width := 3;
        mypoint1 := DaControl.clienttoscreen(point(DaControl.left, DaControl.top));
        mypoint2 := form2.ClientToScreen(Point(0, 0));
        mypoint3 := point(mypoint1.X - mypoint2.x, mypoint1.Y - mypoint2.Y);
        Rectangle(Rect(mypoint3.x, mypoint3.y ,mypoint3.x + dacontrol.width + 2,mypoint3.Y + dacontrol.height + 2));
      finally
        ReleaseDC(DaControl.Parent.Handle, MyHDC);
    end;
  finally
    Free;
  end;
end;

Open in new window

0
 
LVL 12

Expert Comment

by:Hypo
ID: 24309542
sometimes, I just cant let go of a problem until it's solved... :D
The problem with your code is that you allocated your own Device Context for the Window, which then had coordinates that also included the Frame of that window. However, If you had used the TForm.Canvas property, then the painting would have been correct since the TForm adjusts the coordinates of the Canvas with respect of the Frame of the window.

However, since you don't actually know what type your parent Control is, you can't really assume that it is the main form of the application; so you also have to adjust the coordinates of the Canvas to fit the frame. And as I recalled, there were Api functions to do this. :D

The function AdjustWindowRectEx takes a requested ClientRect area, and then resizes it to include the frames of that WindowControl it belongs to. So if we use our Parent ClientRect as input, and get the window styles (which include frame data) using GetWindowLong, we can then get the new fram calculated for us. Since the resulting rect is expanded in every direction, and TopLeft coordinate of the ClientRect is (0,0), the resulting rectangle's TopLeft will be the delta between the Window's TopLeft and the ClientAreas top left. :D

Then by using MoveWindowOrg, we can translate the coordinates of the Canvas so that it compensates for this distance; and then we just Paint... :D

I have adjusted your code acordingly...

regards
Hypo
procedure BorderMyControl(DaControl : TControl);
var
  C : TControlCanvas;
  aDC : HDC;
  aRect : TRect;
  aHandle : HWND;
  bMenu : LongBool;
begin
  C := TControlCanvas.Create;
  C.Control := DaControl.Parent;
  try
    aHandle := DaControl.Parent.Handle;
    aDC := GetWindowDC(aHandle);
    aRect := DaControl.ClientRect;
    bMenu := (DaControl.Parent is TForm) and Assigned(TForm(DaControl.Parent).Menu);
    AdjustWindowRectEx(aRect,
                       GetWindowLong(aHandle, GWL_STYLE),
                       bMenu,
                       GetWindowLong(aHandle, GWL_EXSTYLE));
    MoveWindowOrg(aDC, -aRect.Left, -aRect.Top);
    try
      C.Handle := aDC;
      C.Brush.Style := bsClear;
      C.Pen.Width := 3;
      aRect := DaControl.BoundsRect;
      InflateRect(aRect, 2, 2);
      C.Rectangle(aRect);
    finally
      ReleaseDC(DaControl.Parent.Handle, aDC);
    end;
  finally
    C.Free;
  end;
end;

Open in new window

0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
LVL 13

Author Comment

by:rfwoolf
ID: 24309813
Hi Hypo

That's some nice fancy footwork there using the Windows API. I have tested it and it works! (hellelujah!)
A moment or two later I managed to come up with a solution myself which I'll paste below for other people that might have this problem, here's the crux of the solution:
When painting on the form's canvas, 0,0 is the top-left of the window where the titlebar sits.
So if your Tbutton is sitting at 0,0 on the form and you try paint, it will actually be just above the button.
So how do you calculate the 'offset'?
1) You can get the screen co-ordinates of the topleft of the client area using ClientToScreen(0,0) <--- this is the top-left of the form UNDERNEATH the title bar
2) You can then also get the screen co-ordinates of the topleft of the form using Form.Left, and Form.Top.
You can then take 1 and 2 and subtract to get the offset.

Here's the code:
procedure TForm2.BorderMyControl(DaControl : TControl);
var
  C : TCanvas;
  MyHDC : HDC;
  mypoint1, mypoint2 : tpoint;
  myclientrect : trect;
begin
  C := TCanvas.Create;
  with C do
  try
    MyHDC := GetWindowDC(self.handle);
      try
        Handle := MyHDC;
        Brush.Style := bsClear;
        Pen.Width := 3;
        //From the screen's perspective, gives the co-ords of just underneath the titlebar
        mypoint1 := self.ClientToScreen(Point(0, 0));
        //From the form's perspective, gives the co-ords of the control within the form, where 0,0 would be just underneath the title bar
        mypoint2 := point(DaControl.Left, DaControl.Top);
        Rectangle(Rect(mypoint2.x + (mypoint1.x - self.left) - 2,
          mypoint2.y + (mypoint1.y - self.top) - 2,
          mypoint2.x + (mypoint1.x - self.left) + DaControl.width + 2,
          mypoint2.y + (mypoint1.y - self.top) + DaControl.height + 2));
      finally
        ReleaseDC(self.handle, MyHDC);
    end;
  finally
    Free;
  end;
end;

Open in new window

0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24309826
Hypo I plan to award the points to you. Let's keep the question open about another day or so to see if there's anything else we might be missing.
0
 
LVL 12

Accepted Solution

by:
Hypo earned 2000 total points
ID: 24310146
Hi,
I didn't actually test it if the button could be placed at 0,0, but when I did now, I saw that it painted over the frame... which is not nice. However, that problem is fixed by setting a cliprect or a clipregion. I have altered the example to include that, and I used a region to do this, but it could also be done using the ExcludeClipRect and IntersectClipRect funcitons.

regards
Hypo

procedure BorderMyControl(DaControl : TControl);
var
  C : TControlCanvas;
  aDC : HDC;
  aRect : TRect;
  aHandle : HWND;
  bMenu : LongBool;
  aRgn : HRGN;
begin
  C := TControlCanvas.Create;
  C.Control := DaControl.Parent;
  try
    aHandle := DaControl.Parent.Handle;
    aDC := GetWindowDC(aHandle);
    aRect := DaControl.ClientRect;
    bMenu := (DaControl.Parent is TForm) and Assigned(TForm(DaControl.Parent).Menu);
    AdjustWindowRectEx(aRect,
                       GetWindowLong(aHandle, GWL_STYLE),
                       bMenu,
                       GetWindowLong(aHandle, GWL_EXSTYLE));
    MoveWindowOrg(aDC, -aRect.Left, -aRect.Top);
    aRgn := CreateRectRgnIndirect(DaControl.Parent.ClientRect);
    try
      if aRgn <> 0 then begin
        OffsetRgn(aRgn, -aRect.Left, -aRect.Top);
        SelectClipRgn(aDC, aRgn);
      end;
      C.Handle := aDC;
      C.Brush.Style := bsClear;
      C.Pen.Width := 3;
      aRect := DaControl.BoundsRect;
      InflateRect(aRect, 2, 2);
      C.Rectangle(aRect);
    finally
      if aRgn <> 0 then DeleteObject(aRgn);
      ReleaseDC(DaControl.Parent.Handle, aDC);
    end;
  finally
    C.Free;
  end;
end;

Open in new window

0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24310190
Fortunately I wasn't concerned about buttons being on the frame, but it is ultimately an important point.
Thanks Hypo
0
 
LVL 13

Author Closing Comment

by:rfwoolf
ID: 31578212
Great work. Thank you!
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24327913
Hypo is the man. Good work and thanks so much.
0

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
This course is ideal for IT System Administrators working with VMware vSphere and its associated products in their company infrastructure. This course teaches you how to install and maintain this virtualization technology to store data, prevent vuln…
Sometimes it takes a new vantage point, apart from our everyday security practices, to truly see our Active Directory (AD) vulnerabilities. We get used to implementing the same techniques and checking the same areas for a breach. This pattern can re…
Suggested Courses

649 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question