Solved

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

Posted on 2009-05-07
34
1,473 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
Comment Utility
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
Comment Utility
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
Comment Utility
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
 
LVL 12

Accepted Solution

by:
Hypo earned 325 total points
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
"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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
"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
Comment Utility
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
Comment Utility
Eddie I hope you're not joking just to embaress me. Attached is an example
ExampleForEddie.jpg
0
 
LVL 26

Expert Comment

by:EddieShipman
Comment Utility
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
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 26

Assisted Solution

by:EddieShipman
EddieShipman earned 175 total points
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
Thank you for trying anyway.  I do appreciate it.
0
 
LVL 26

Expert Comment

by:EddieShipman
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
[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
Comment Utility
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
Comment Utility
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
Comment Utility
You still there? Can you post your DFM?
0
 
LVL 26

Expert Comment

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

Author Comment

by:rfwoolf
Comment Utility
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
Comment Utility
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
Comment Utility
Richard, How's things coming on this? Are you able to post your DFM, yet?
0
 
LVL 13

Author Comment

by:rfwoolf
Comment Utility
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
Comment Utility
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

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…

743 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

14 Experts available now in Live!

Get 1:1 Help Now