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

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

LVL 13
rfwoolfAsked:
Who is Participating?

Improve company productivity with a Business Account.Sign Up

x
 
HypoConnect With a Mentor Commented:
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
 
Eddie ShipmanAll-around developerCommented:
Why not exclude the border from your painting area, then you don't have to worry about having to repaint it.
0
 
rfwoolfAuthor Commented:
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
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

 
HypoCommented:
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
 
rfwoolfAuthor Commented:
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
 
Eddie ShipmanAll-around developerCommented:
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
 
rfwoolfAuthor Commented:
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
 
rfwoolfAuthor Commented:
"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
 
rfwoolfAuthor Commented:
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
 
rfwoolfAuthor Commented:
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
 
Eddie ShipmanAll-around developerCommented:
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
 
rfwoolfAuthor Commented:
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
 
Eddie ShipmanAll-around developerCommented:
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
 
rfwoolfAuthor Commented:
"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
 
Eddie ShipmanAll-around developerCommented:
I'm such a visual person, I guess I'd have to see it, sorry I don't really understand, yrt.
0
 
rfwoolfAuthor Commented:
Eddie I hope you're not joking just to embaress me. Attached is an example
ExampleForEddie.jpg
0
 
Eddie ShipmanAll-around developerCommented:
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
 
Eddie ShipmanConnect With a Mentor All-around developerCommented:
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
 
rfwoolfAuthor Commented:
It's looking good Eddie!
I have to rush out but I'll be back shortly to test it further.
0
 
rfwoolfAuthor Commented:
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
 
rfwoolfAuthor Commented:
Thank you for trying anyway.  I do appreciate it.
0
 
Eddie ShipmanAll-around developerCommented:
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
 
rfwoolfAuthor Commented:
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
 
Eddie ShipmanConnect With a Mentor All-around developerCommented:
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
 
Eddie ShipmanAll-around developerCommented:
[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
 
rfwoolfAuthor Commented:
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
 
Eddie ShipmanAll-around developerCommented:
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
 
Eddie ShipmanAll-around developerCommented:
You still there? Can you post your DFM?
0
 
Eddie ShipmanAll-around developerCommented:
so, you gonna post your DFM or what?
0
 
rfwoolfAuthor Commented:
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
 
Eddie ShipmanAll-around developerCommented:
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
 
Eddie ShipmanAll-around developerCommented:
Richard, How's things coming on this? Are you able to post your DFM, yet?
0
 
rfwoolfAuthor Commented:
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
 
Eddie ShipmanAll-around developerCommented:
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
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.