Solved

Bug in TPanel - border doesn't get repainted when TPanel repaints

Posted on 2009-05-07
34
1,484 Views
Last Modified: 2013-11-22
Using Delphi BDS 2006, if I have a TPanel on a form, with BorderStyle =  BsSingle, and BorderWidth = 1,
if you paint on the form's canvas and paint something over this border,
and then I call Panel1.repaint and Form1.repaint;
everything is redrawn except for the border.

To redraw the border, I have to say:
  Panel1.BorderStyle := bsNone;
  Panel1.BorderStyle := bsSingle;
(the reason this works is because this calls panel1.RecreateWnd which "destroys the underlying windows object" and recreates it. You cannot however call panel1.RecreateWnd directly because it is private);

Another solution seems to be:
How can I repaint this border properly?

Attached are more detailed instructions on how to recreate the problem...
On a form, place a TPanel.
Set its Left to 50;
Set its Top to 50;
Set its BorderStyle = bsSingle
Set BorderWidth to 1 // (this doesn't actually matter, it can also be 0).
 
//Paint onto the forms canvas by putting a TButton with this OnClick event:
 
procedure TForm1.Button1Click(Sender: TObject);
var
  C : TCanvas;
  MyHDC : HDC;
begin
  C := TCanvas.Create;
  with C do
  try
    MyHDC := GetWindowDC(self.handle);
      try
        Handle := MyHDC;
        Brush.Style := bsSolid;
        Pen.Width := 8;
        Rectangle(Rect(10,10,100,100));
      finally
        ReleaseDC(self.handle, MyHDC);
    end;
  finally
    Free;
  end;
//NOW REPAINT THE PANEL
  Panel1.Repaint;
//NOW REPAINT THE FORM
  self.repaint;
end;
 
 
//THE SOLUTION:
  panel1.BorderStyle := bsNone;
  panel1.BorderStyle := bsSingle;

Open in new window

0
Comment
Question by:rfwoolf
  • 16
  • 16
  • 2
34 Comments
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24331378
Why not exclude the border from your painting area, then you don't have to worry about having to repaint it.
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24331453
Thanks for the suggestion Eddie, however, 1) aesthetically I do want to paint over the border, and 2) I'm not really looking for that kind of workaround here.

Yaknow, what should have been a simple procedure of painting a rectangle around a button, has turned into a mammoth scope problem: compensating for the form's titlebar size and borders, then not being able to repaint the panel borders. This has cost me now hours and hours of time.

Now I'm moving on to using a transparent panel with a TShape on it as the rectangle but so far can't get it to work.

If this is a bug in TPanel, it should be reported, and if it already has, there should be a proper workaround / fix.

I'm also thinking about simply saving the canvas of the form, then drawing the rectangle and then (later) restoring the canvas of the form instead of repainting it. Do you know how to do this? If so I will open a new question for you :)

Cheers
0
 
LVL 12

Expert Comment

by:Hypo
ID: 24331686
Hi, You can acheive the same thing as RecreateWnd does with the following line:
  Panel1.Perform(CM_RECREATEWND, 0, 0);

try adding that line after your Panel1.Repaint.

regards
Hypo
0
Announcing the Most Valuable Experts of 2016

MVEs are more concerned with the satisfaction of those they help than with the considerable points they can earn. They are the types of people you feel privileged to call colleagues. Join us in honoring this amazing group of Experts.

 
LVL 12

Accepted Solution

by:
Hypo earned 325 total points
ID: 24331708
However, it seems more pretty to execute CM_BORDERCHANGE instead... then you don't have to worry about all the things that RECREATEWND might do... :D

So, try adding this line after your Panel1.Repaint instead. And the border will be repainted.
  Panel1.Perform(CM_BORDERCHANGED, 0, 0);

regards
Hypo
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24331733
Hi Hypo.

I also saw got to CM_RECREATEWND by digging deeper in the vcl units.
While it did solve the problem, it was very clunky - there's a major flicker for me because all my controls have to be redrawn.
However I did not get to CM_BORDERCHANGED - your apparent knowledge of the Windows API is useful here.

Thank you very much, I'm happy to accept CM_BORDERCHANGED as the solution, but will keep the question open for a while longer for other comments.
Thanks again!
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24332622
rfwoolf,
I don't understand why you are going to such trouble to paint controls with a border when
they are active.

That has been done tons of times and I even have some code done by a friend that does
it. There are several free w/source controls on Torry's that do it, so why don't you download one of those and learn how they do it instead of breaking your head on this problem?
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24332687
Eddie:
I am not trying to border active controls.
I am doing something different.
On the form there are groups of buttons (let's call it Group A) and somewhere else a button (let's call it Button A). If the user hovers over Button A, I want a border to be drawn around all of Group A. And visa verse.
Notice that I said AROUND. This means the border is drawn outside of the control.
To make matters worse, most of these controls are tightly packed on panels within panels - so for example I could have drawn on the canvas of a panel - now I have to draw on the canvas of a button's parents' parent's parent - or to make it simple - I want to draw on the form itself.

I have finally succeeded, but to now 'clear' the borders I have to repaint the entire form and all its controls which causes a big flicker.
So I changed direction again and am now trying a transparent panel - which worked great in tests but now that I'm implementing it, it's not working :(
So now I'm going to change directions AGAIN and do the painting thing.
I like the painting thing but the 'undoing' (repainting) is causing problems :(
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24332692
"I want to draw on the form itself." - I meant to say "I want to draw on the form's canvas"  (which would draw over everything else including buttons, panels and all its children)
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24332899
LOL...
In the beginning my problem was that TShape is not a TWinControl, and according to the help files even if you say BringToFront it will not stay on top of other TWinControl's such as TButton's or TEdit's.
So my solution was to put TShape on a transparent TPanel, and have TPanel brought to front.
Well this worked as expected, with anything I putting on the panel working correctly, except for TShape, again, doesn't show up in front of TButton's etc.
But what IS working no my transparent TPanel is ....
other TPanels.

So the solution, I think, is to use 4 x TPanels, black, thin, to make boxes around my components, and bring them all to the front.
-No transparent Tpanels
-No TShapes
-No drawing on the form's canvas.

Let's hope it works!!!
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24332971
Yes!!! It seems to be working, thank heavens.
I can finally put this chapter to bed I think.
It is still a pitty that I wasn't able to do it the other ways - it's nice to be able to do things in more than one way. At least I learnt one or two things in the process and I'm sure I will be using this information in the near future when I write components.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24334967
Did yuo want colored borders? It seems that you are doing things the hard way as it is and now that you have made it a little clearer what you want, we can help better.
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24335424
Um, just black border is fine - but thick, about 3 pixels in diameter. Solid line. The only problem with using TPanels is you can't make rectangles with rounded corners or circles - but that's fine - I never really intended to do so.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24335641
Why couldn't you draw a rectangle on the form's canvas with a transparent center that allowed everything else to show through? Can you post a screenshot of what you want?
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24335673
"Why couldn't you draw a rectangle on the form's canvas with a transparent center that allowed everything else to show through?"
I did. Then the problem became removing the rectangle - the border of a TPanel would not redraw - hence I posted this question. Hypo posted a solution that passes a message to redraw the border.
Now the problem is the redrawing of everything on the form (to remove the rectangle I drew) is causing a nasty flicker, so I have now opened another question that asks if maybe we can cache the form's canvas, draw the rectangle, and then restore the cache? That way there probably won't be any flicker.
Do you understand?

As a complete alternative, if I use TPanels to construct a rectangle, and simply hide them, then there won't be any flicker :D
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24335713
I'm such a visual person, I guess I'd have to see it, sorry I don't really understand, yrt.
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24335910
Eddie I hope you're not joking just to embaress me. Attached is an example
ExampleForEddie.jpg
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24337370
Try something like this to avoid all the repaints problems:

procedure DrawBorder(ATag:Integer; ADraw: Boolean);
var
  Rect: TRect;
  fcolor: TColor;
begin
  case ATag of
  0:
    begin
      // draw border around big button
      Rect := Form1.BitBtn3.BoundsRect;
      InflateRect(rect, 2, 2);
      if ADraw then
        fColor := clBlack
      else
        fColor := Form1.Color;
      Frame3d(Form1.Canvas, Rect, fColor, fColor, 2);
    end;
  1:
    begin
      Rect.TopLeft := Form1.BitBtn1.BoundsRect.TopLeft;
      Rect.BottomRight := Form1.BitBtn2.BoundsRect.BottomRight;
      InflateRect(rect, 2, 2);
      if ADraw then
        fColor := clBlack
      else
        fColor := Form1.Color;
      Frame3d(Form1.Canvas, Rect, fColor, fColor, 2);
    end;
  end; // case
end;
 
procedure TBitBtn.CMMouseEnter(var Message: TMessage);
begin
  inherited;
  if not FMouseInControl and Enabled and (GetCapture = 0) then
  begin
    FMouseInControl := True;
    DrawBorder(TControl(Self).Tag, FMouseInControl);
    Repaint;
  end;
end;
 
procedure TBitBtn.CMMouseLeave(var Message: TMessage);
begin
  inherited;
  if FMouseInControl and Enabled then
  begin
    FMouseInControl := False;
    DrawBorder(TControl(Self).Tag, FMouseInControl);
    Invalidate;
  end;
end;

Open in new window

0
 
LVL 26

Assisted Solution

by:EddieShipman
EddieShipman earned 175 total points
ID: 24337379
Try this post instead. I forgot the rest of the code:
unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, Buttons;
 
type
  TBitBtn = class(Buttons.TBitBtn)
  private
    FMouseInControl: Boolean;
    procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
  end;
 
  TForm1 = class(TForm)
    BitBtn3: TBitBtn;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    Bevel1: TBevel;
  private
    { Private declarations }
    FborderColor: TColor;
  public
    { Public declarations }
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
{ TBitBtn }
 
procedure DrawBorder(ATag:Integer; ADraw: Boolean);
var
  Rect: TRect;
  fcolor: TColor;
begin
  case ATag of
  0:
    begin
      // draw border around big button
      Rect := Form1.BitBtn3.BoundsRect;
      InflateRect(rect, 2, 2);
      if ADraw then
        fColor := clBlack
      else
        fColor := Form1.Color;
      Frame3d(Form1.Canvas, Rect, fColor, fColor, 2);
    end;
  1:
    begin
      Rect.TopLeft := Form1.BitBtn1.BoundsRect.TopLeft;
      Rect.BottomRight := Form1.BitBtn2.BoundsRect.BottomRight;
      InflateRect(rect, 2, 2);
      if ADraw then
        fColor := clBlack
      else
        fColor := Form1.Color;
      Frame3d(Form1.Canvas, Rect, fColor, fColor, 2);
    end;
  end; // case
end;
 
procedure TBitBtn.CMMouseEnter(var Message: TMessage);
begin
  inherited;
  if not FMouseInControl and Enabled and (GetCapture = 0) then
  begin
    FMouseInControl := True;
    DrawBorder(TControl(Self).Tag, FMouseInControl);
    Repaint;
  end;
end;
 
procedure TBitBtn.CMMouseLeave(var Message: TMessage);
begin
  inherited;
  if FMouseInControl and Enabled then
  begin
    FMouseInControl := False;
    DrawBorder(TControl(Self).Tag, FMouseInControl);
    Invalidate;
  end;
end;
 
end.

Open in new window

0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24337508
It's looking good Eddie!
I have to rush out but I'll be back shortly to test it further.
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24340021
Hi Eddie

It doesn't work if the BitBtn  is on a panel because when you are using Frame3d to draw the rect, you use the form's canvas and it will draw on the form only, and not on its children :(
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24340028
Thank you for trying anyway.  I do appreciate it.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24344019
Then you don't need the panels, correct? otherwise, call it with the panel's canvas. It is protected, so you do know how to access it, correct?
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24344042
Eddie: If the TBitBin is on the EDGE of a panel, then if you use the form's canvas you will get the border on the form but not on the panel, and if you use the panel's canvas it won't appear on the form.
Try it yourself if you don't understand:
Put a TPanel on your form, set Left to 100, Top to 100, Width to 200, Height to 200.
Please a TBitBin on your TPanel. Set it's Left to 0, Top to 0, Width to 100, Height to 100.

Now if you use the Form's canvas you can see it won't appear on the TPanel.
If you use the TPanel's canvas, some of the frame will get cut off because it's not being drawn on the form's canvas. :p
0
 
LVL 26

Assisted Solution

by:EddieShipman
EddieShipman earned 175 total points
ID: 24344057
Make this change and all will be well.
type TMyPanel = class(TPanel);
 
procedure DrawBorder(ATag:Integer; ADraw: Boolean);
var
  Rect: TRect;
  fColor: TColor;
begin
  case ATag of
  0:
    begin
      // draw border around big button
      Rect := Form1.BitBtn3.BoundsRect;
      InflateRect(rect, 2, 2);
      if ADraw then
        fColor := clBlack
      else
        fColor := Form1.Color;
      Frame3d(TMyPanel(Form1.BitBtn3.Parent).Canvas, Rect, fColor, fColor, 2);
    end;
  1:
    begin
      Rect.TopLeft := Form1.BitBtn1.BoundsRect.TopLeft;
      Rect.BottomRight := Form1.BitBtn2.BoundsRect.BottomRight;
      InflateRect(rect, 2, 2);
      if ADraw then
        fColor := clBlack
      else
        fColor := Form1.Color;
      Frame3d(TMyPanel(Form1.BitBtn2.Parent).Canvas, Rect, fColor, fColor, 2);
    end;
  end; // case
end;

Open in new window

0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24344382
[quote]If the TBitBin is on the EDGE of a panel...[/quote]
Then I guess you are going to have to make some adjustments ;-)

I would suggest making the panel that contains the buttons be 4 pixels bigger than the buttons in both height and width. If you set both bevel's to bvNone, it will still do what you want.
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24345403
Hi Eddie. I was just trying out your solution now, and yes it still won't work because a) the TBitBtn is on the edge of a panel, b) there are other objects next to the TBitBtn such as other TBItBtns and some images
I hope at least you now understand the problem. I do appreciate your sagely attention though, but I don't expect you to solve it. I am going with using TPanel's to make a rectangle border and saying TPanel.BringToFront and TPanel.hide / show.
I have also found some transparent panels, but could not get them to work for this, but, I was able to using transparent panels to 'dim' (darken) regions of my form and menus (I am using a combonation of rectangles and dimmed regions to highlight to the user the section of the form he is interacting with) :)
Have a good weekend. P
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24346611
Is there any reason you can't post your DFM so I can actually see what the form looks like exactly? I can reconstruct it and work with it.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24365730
You still there? Can you post your DFM?
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24435163
so, you gonna post your DFM or what?
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24444592
Hey Eddie
I've just started a full time job, so I'm very tired right now and pressed for time, so won't be able to show you the DFM - I did find a solution with TPanel's though. Also you should be able to find this problem yourself if you play around enough.
Basically I have a form, centered on the form is a panel1 let's say 600 x 400px. At the top of this panel is  a panel2 let's say 600 x 100px and aligned to top. On Panel2 are 6 buttons, each 100 x 100px - I think they are TBitBtns actually.

Now try to draw a black border around one of the TBitBtns. Which canvas shall you use?
You cannot use the TBItBtn canvas because the black border is on the OUTSIDE of the buttons.
You cannot use the TPanel2's canvas because in this case the black border is also OUTSIDE of the TPanel2.
You cannot use the canvas of TPanel1 because also some of the border is OUTSIDE of this TPanel1.
So you use the canvas of the window

Problems:
1) The canvas of the window starts from the title bar. So just for example if my button was 100 left, 100 top, and I paint a border there on the window canvas, it will not be accurate because you have to add the height of the title bar, and also you have to add the width of the window's border. This problem was solved with the help of Geert Gruwez and myself.
2) So let's say you have successfully draw on the window's canvas in the right place. Now you need to remove it! The only way I was able to do this was to redraw everything on the form. But TPanel will not repaint its border. That is why I posted this question. So if you painted your rectangle border over the TPanel, and you redraw the TPanel, it will not remove the rectangle on its border.
3) Also redrawing everything on the form causes a flicker which is very ugly.

Cheers
-Richard
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24455723
Well, I'm kind of a visual person and need to see it myself but if you have a solution working for you, great.
I do think I said that you would need to move the buttons away from the panel's edges to get the effect you are looking for with the code I posted. That shouldn't be a problem, now, should it?
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 24814238
Richard, How's things coming on this? Are you able to post your DFM, yet?
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 25086520
Thanks for trying Eddie - your solution didn't work in the end but I gave you points because it still might help other people and I appreciate the effort.

In the end Hypo's solution I think did repaint the panel's borders, but the final solution was to make borders out of black TPanels. This was much easy and 'clean' than drawing on canvasas, iterating through all the controls and repainting them and doing all kinds of crap.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 25087429
I still think that if you'd posted a DFM, I would have been able to come up with a solution for you but, whatever...
0

Featured Post

Gigs: Get Your Project Delivered by an Expert

Select from freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely and get projects done right.

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…
This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
This Micro Tutorial will give you a basic overview how to record your screen with Microsoft Expression Encoder. This program is still free and open for the public to download. This will be demonstrated using Microsoft Expression Encoder 4.
Microsoft Active Directory, the widely used IT infrastructure, is known for its high risk of credential theft. The best way to test your Active Directory’s vulnerabilities to pass-the-ticket, pass-the-hash, privilege escalation, and malware attacks …

815 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

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now