Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Transparent panel


This question is answered. Helpful answers available: 2. Correct answers available: 1.


Permlink Replies: 11 - Last Post: Feb 9, 2015 7:09 AM Last Post By: Martin Nijhoff
Martin Nijhoff

Posts: 75
Registered: 8/26/10
Transparent panel  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 4, 2015 1:41 AM
Hi,

I'm writing a transparent panel control, derived from TCustomPanel, in C++Builder XE6.

Here's my code:

class PACKAGE TXPanel : public TCustomPanel
{
    typedef TCustomPanel inherited;
 
    private:
        bool FTransparent;
 
        void __fastcall SetTransparent (bool Value);
 
        HIDESBASE MESSAGE void __fastcall WMEraseBkgnd (TWMEraseBkgnd &Message);
 
    protected:
        virtual void __fastcall CreateParams (TCreateParams &Params);
        virtual void __fastcall Paint ();
 
    public:
        __fastcall virtual TXPanel (TComponent* Owner);
 
    __published:
        __property bool Transparent = {read=FTransparent, write=SetTransparent, default=false};
 
        __property Align;
        __property Alignment;
        ...
        __property OnStartDrag;
        __property OnUnDock;
 
#pragma warn -iip
BEGIN_MESSAGE_MAP
    VCL_MESSAGE_HANDLER(WM_ERASEBKGND, TWMEraseBkgnd, WMEraseBkgnd)
END_MESSAGE_MAP(inherited)
#pragma warn .iip
};
 
 
__fastcall TXPanel::TXPanel (TComponent* Owner)
    : inherited(Owner)
{
    FTransparent = false;
}
 
 
void __fastcall TXPanel::SetTransparent (bool Value)
{
    if (FTransparent != Value)
    {
        FTransparent = Value;
        RecreateWnd();
    }
}
 
 
void __fastcall TXPanel::CreateParams (TCreateParams &Params)
{
    inherited::CreateParams(Params);
 
    if (FTransparent)
    {
        Params.ExStyle |= WS_EX_TRANSPARENT;
        ControlStyle >> csOpaque;
    }
    else
        ControlStyle << csOpaque;
}
 
 
void __fastcall TXPanel::Paint ()
{
    TRect R = ClientRect;
 
    if (!FTransparent)
    {
        Canvas->Brush->Color = Color;
        Canvas->FillRect(R);
    }
 
    // For now, paint a simple black border.
    Canvas->Brush->Style = bsClear;
    Canvas->Pen->Color = clBlack;
    Canvas->Rectangle(R);
 
    Canvas->Font = Font;
    DrawTextW(Canvas->Handle, Caption.c_str(), -1, LPRECT(&R), DT_SINGLELINE | DT_CENTER | DT_VCENTER);
}
 
 
void __fastcall TXPanel::WMEraseBkgnd (TWMEraseBkgnd &Message)
{
    if (FTransparent)
        Message.Result = 1;
    else
        inherited::Dispatch(&Message);
}


To test the transparency, I aligned a TImage with a bitmap to the client area of a TForm and placed a TXPanel on top of it.

The TXPanel is painted correctly when Transparent = false, even when the form is resized.
The TXPanel is also painted correctly when Transparent = true, as long as the form is not resized.
However, when Transparent = true and the form is resized, the TXPanel 'disappears' until I force a repaint (by moving the form off-screen or explicitly calling Invalidate() on the TXPanel).
Apparently, the TXPanel is painted over by the TImage, even though the TXPanel should receive a WM_PAINT message after the TImage (because of the WS_EX_TRANSPARENT window style).

Of course, I could call Invalidate() on the TXPanel in the form's OnResize event handler, but I would prefer the TXPanel to always paint itself correctly.

Any ideas what I'm doing wrong?

--
Martin
Jean-Milost Rey...

Posts: 23
Registered: 11/8/11
Re: Transparent panel  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 4, 2015 7:06 PM   in response to: Martin Nijhoff in response to: Martin Nijhoff
Hello,

Martin Nijhoff wrote:
I'm writing a transparent panel control, derived from TCustomPanel
Unfortunately, writing a transparent panel is a little more complex than simply ignore background painting. If you do that, this can sometimes works, if you're lucky, but generally some strange artifacts will quickly appear on your control background, because it will be undefined. Make VCL components to paint transparency is a hard and painful way. The better and quickest solution I know is to use FireMonkey if possible, otherwise try to inherit from TGraphicControl, that at least offers the possibility to paint on the parent canvas directly (graphic controls don't inherits from TWinControl, for this reason they don't have canvas. To workaround that, the VCL uses the parent canvas instead)

I know you defined the WS_EX_TRANSPARENT style, hoping that will turn your control background transparent. But it's a lie. See e.g. http://blogs.msdn.com/b/oldnewthing/archive/2012/12/17/10378525.aspx

Martin Nijhoff wrote:
However, when Transparent = true and the form is resized, the TXPanel 'disappears' until I force a repaint (by moving the form off-screen or explicitly calling Invalidate() on the TXPanel).
Apparently, the TXPanel is painted over by the TImage, even though the TXPanel should receive a WM_PAINT message after the TImage (because of the WS_EX_TRANSPARENT window style).
Your TXPanel is painted over by the TImage simply because it receive no messages at all while resize is executed, nor after. It seems that something breaks your message loop at this moment, but I don't know what exactly. I noticed that you update your style while control parameters are applied, perhaps WS_EX_TRANSPARENT stops this process, or something like that. You should debug your control and find what exactly breaks your paint process.

Martin Nijhoff wrote:
Canvas->Font = Font;
You should never do that. This can cause strange access violations later. It's better to assign font as follow:
Canvas->Font->Assign(Font);


Regards

Edited by: Jean-Milost Reymond on Feb 4, 2015 7:07 PM
Martin Nijhoff

Posts: 75
Registered: 8/26/10
Re: Transparent panel  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 5, 2015 1:39 AM   in response to: Jean-Milost Rey... in response to: Jean-Milost Rey...
Thanks for your reply, Jean-Milost.

Jean-Milost Reymond wrote:
Unfortunately, writing a transparent panel is a little more complex than simply ignore background painting.

That is exactly what 'transparent' means. The controls underneath paint itself first and the transparent control only paints the non-transparent parts (i.e. not paint its background).

Jean-Milost Reymond wrote:
The better and quickest solution I know is to use FireMonkey if possible, otherwise try to inherit from TGraphicControl, that at least offers the possibility to paint on the parent canvas directly (graphic controls don't inherits from TWinControl, for this reason they don't have canvas.

A panel can contain other controls and hence must be a windowed control, derived from TWinControl. Otherwise, I would simply use a TBevel, which is a graphic control, but can't contain other controls.

BTW, a graphic control, derived from TGraphicControl, actually does have a canvas (see the Canvas property).

Jean-Milost Reymond wrote:
To workaround that, the VCL uses the parent canvas instead)

The transparent panel does not need to paint itself in its parent's canvas. As long as the parent paints first, the panel's own canvas will do just fine.

Jean-Milost Reymond wrote:
I know you defined the WS_EX_TRANSPARENT style, hoping that will turn your control background transparent. But it's a lie. See e.g. http://blogs.msdn.com/b/oldnewthing/archive/2012/12/17/10378525.aspx

If you read that blog post carefully, the author states that it requires more than just setting the WS_EX_TRANSPARENT window style. The control needs to know not to draw itself opaquely, which is exactly what my control does.

The problem is not painting transparently. My control already has that part covered.

Jean-Milost Reymond wrote:
Your TXPanel is painted over by the TImage simply because it receive no messages at all while resize is executed, nor after.

That is exactly the problem. My control's canvas does not get invalidated when the form resizes, because the control doesn't move or resize itself.

Jean-Milost Reymond wrote:
It seems that something breaks your message loop at this moment, but I don't know what exactly. I noticed that you update your style while control parameters are applied, perhaps WS_EX_TRANSPARENT stops this process, or something like that. You should debug your control and find what exactly breaks your paint process.

Any idea how I could debug the paint process?

Jean-Milost Reymond wrote:
You should never do that. This can cause strange access violations later. It's better to assign font as follow:
Canvas->Font->Assign(Font);


FYI, this is exactly what the setter for the Font property does:

procedure TCanvas.SetFont(Value: TFont);
begin
  FFont.Assign(Value);
end;


--
Martin
Jean-Milost Rey...

Posts: 23
Registered: 11/8/11
Re: Transparent panel  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 5, 2015 6:17 AM   in response to: Martin Nijhoff in response to: Martin Nijhoff
Hi Martin,

Martin Nijhoff wrote:
Unfortunately, writing a transparent panel is a little more complex than simply ignore background painting. ... That is exactly what 'transparent' means.
I think you're optimist :-) That's exactly that the documentation says that WS_EX_TRANSPARENT means, but in practice, things are not so simple. As I mentioned, this can works well if you're lucky, and as you can see in the above mentioned link, they say that this can works sometimes, but there are many limitations. I tried to compile your code, and I saw the above mentioned artifacts, for example when the DoubleBuffered = true on the parent form. Yes, I know that the painting pipeline used internally in the VCL is different in this case, but as "user" of your controls, if I may so speak, this kind of issues should never appear. This is a board, too, to not create a control that works only in a particular configuration, and not in others, because usage of your controls can quickly become a hell.

But all this is just my point of view.

Martin Nijhoff wrote:
BTW, a graphic control, derived from TGraphicControl, actually does have a canvas (see the Canvas property).
Yes, they create a canvas object, but the device context used in this canvas is the device context of the parent control, as TGraphicControl inherits from TControl, not from TWinControl, and thus has no native handle to create an independent context (at least as I know, if I make a mistake, thanks to demonstrate that for me). For this reason, the canvas represents a portion of the parent control canvas, and not an independent canvas. And in this condition, I maintain my idea that's is a quick way to create a transparent control, as the background is painted first by the parent itself.

Martin Nijhoff wrote:
A panel can contain other controls and hence must be a windowed control, derived from TWinControl
More exactly, a control must be configured with some styles during construction to be able to accept children controls. This is done by the VCL itself, internally, e.g. when you create a TPanel. But it's true, I don't know if it's possible to configure a TGraphicControl the same way. Perhaps simply adding the csAcceptsControls flag inside control styles during construction can resolve that?

Martin Nijhoff wrote:
The transparent panel does not need to paint itself in its parent's canvas.
In the case of a TGraphicControl descendant, yes, because as I mentioned above, they haven't an own device context. But it's true, it's inexact to say that they paint on the parent canvas. Their simply share the same device context. More one time, as I know, and until proven otherwise.

Martin Nijhoff wrote:
As long as the parent paints first, the panel's own canvas will do just fine.
Yes, under certain conditions. This can change, e.g. depending of the specified flags passed during class construction. But yes, there are configurations in which things can happen this way. However, I suggest you try to change some properties in other controls, as e.g. DoubleBuffered on your main form. I doubt that all of these changes don't impact your control painting in a manner or other.

Martin Nijhoff wrote:
Canvas->Font->Assign(Font);
FYI, this is exactly what the setter for the Font property does:
This can be a mistake from my part, but I already experimented some issues using Canvas->Font = Font. But perhaps it was inside a previous RAD studio version, or caused by another programming error that I made. However, I have become accustomed to assign properties that contains pointers this way. It was the motivation for this advice. But more one time, it's only my point of view.

Regards

Edited by: Jean-Milost Reymond on Feb 5, 2015 6:49 AM
Martin Nijhoff

Posts: 75
Registered: 8/26/10
Re: Transparent panel  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 6, 2015 3:13 AM   in response to: Jean-Milost Rey... in response to: Jean-Milost Rey...
Hi Jean-Milost,

That's exactly that the documentation says that WS_EX_TRANSPARENT means, but in practice, things are not so simple. As I mentioned, this can works well if you're lucky, and as you can see in the above mentioned link, they say that this can works sometimes, but there are many limitations.

The blog post states "it's not a just turn this on and you get transparent rendering feature". I know, let's move on.

I tried to compile your code, and I saw the above mentioned artifacts, for example when the DoubleBuffered = true on the parent form. Yes, I know that the painting pipeline used internally in the VCL is different in this case, but as "user" of your controls, if I may so speak, this kind of issues should never appear. This is a board, too, to not create a control that works only in a particular configuration, and not in others, because usage of your controls can quickly become a hell.

I don't recall mentioning anywhere that this was a finished product. I wouldn't have posted my question here if it was.

More exactly, a control must be configured with some styles during construction to be able to accept children controls. This is done by the VCL itself, internally, e.g. when you create a TPanel. But it's true, I don't know if it's possible to configure a TGraphicControl the same way. Perhaps simply adding the csAcceptsControls flag inside control styles during construction can resolve that?

No, it does not. Only windowed controls can be the parent of other (windowed or graphic) controls.

The csAcceptsControls flag in the ControlStyle property tells the form designer that the windowed control is allowed to become the parent of another control. Without this flag, you are not able to drop another control into the windowed control in the designer.

Yes, under certain conditions. This can change, e.g. depending of the specified flags passed during class construction. But yes, there are configurations in which things can happen this way.

This is what the WS_EX_TRANSPARENT window style is for, and only that. It lets the controls 'underneath' paint first. Nothing more, nothing less.

However, I suggest you try to change some properties in other controls, as e.g. DoubleBuffered on your main form. I doubt that all of these changes don't impact your control painting in a manner or other.

Of course double buffering affects the painting process. I didn't expect it not to.
Again, the WS_EX_TRANSPARENT window style is not some magic solution for transparency.

This can be a mistake from my part, but I already experimented some issues using Canvas->Font = Font. But perhaps it was inside a previous RAD studio version, or caused by another programming error that I made.

This is not a pointer assignment. It merely calls the setter method of TCanvas' Font property, which in turn calls Assign() to copy the TFont's properties.

--
Martin
Jean-Milost Rey...

Posts: 23
Registered: 11/8/11
Re: Transparent panel  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 5, 2015 6:34 AM   in response to: Martin Nijhoff in response to: Martin Nijhoff
Martin Nijhoff wrote:
Any idea how I could debug the paint process?
Yes. I suggest this way. First adding the following code:

in the protected section of your .h
virtual void __fastcall WndProc(TMessage& message);


in the .cpp
void __fastcall TXPanel::WndProc(TMessage& message)
{
    // dispatch message
    switch (message.Msg)
    {
        ...
    }
 
    inherited::WndProc(message);
}


Then you can listen all messages sent inside your control main loop, especially the WM_PAINT / WM_NCPAINT /WM_PRINT and WM_PRINTCLIENT messages. If you have the source code of the VCL, you can trace into it too, starting from your Paint() function or from the message loop (even if in this case this is often fruitless), to see what happen inside the parent.

But because no message are sent to your control message loop during the resize, another idea to explore is to listen the application main loop directly. For example, using an internal TApplicationEvents object inside your panel, you can listen all messages received by the application itself, and react when something interesting happens.

Regards

Edited by: Jean-Milost Reymond on Feb 5, 2015 6:35 AM
Sune Andersen

Posts: 13
Registered: 4/2/03
Re: Transparent panel  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 6, 2015 5:03 AM   in response to: Martin Nijhoff in response to: Martin Nijhoff
Jean-Milost Reymond wrote:
I know you defined the WS_EX_TRANSPARENT style, hoping that will turn your control background transparent. But it's a lie. See e.g. http://blogs.msdn.com/b/oldnewthing/archive/2012/12/17/10378525.aspx

If you read that blog post carefully, the author states that it requires more than just setting the WS_EX_TRANSPARENT window style. The control needs to know not to draw itself opaquely, which is exactly what my control does.

The problem is not painting transparently. My control already has that part covered.
Actually if you read on it also states very clearly that it only works for sibling windows, and you said:
To test the transparency, I aligned a TImage with a bitmap to the client area of a TForm and placed a TXPanel on top of it.
If you look at the help files TImage inherits from TControl, not TWinControl, so it can hardly be a sibling of anything in the windows sense of the word! But you could put the TImage into a TWinControl descended control that IS a sibling of your TXPanel.
Martin Nijhoff

Posts: 75
Registered: 8/26/10
Re: Transparent panel  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 9, 2015 7:09 AM   in response to: Sune Andersen in response to: Sune Andersen
Hi Sune,

Actually if you read on it also states very clearly that it only works for sibling windows, and you said:
To test the transparency, I aligned a TImage with a bitmap to the client area of a TForm and placed a TXPanel on top of it.
If you look at the help files TImage inherits from TControl, not TWinControl, so it can hardly be a sibling of anything in the windows sense of the word!

You are correct that the WS_EX_TRANSPARENT style only applies to the painting order of sibling windows.

However, a parent window always paints before its child windows, so the TImage is already painted when the TXPanel paints.

The problem is that the TXPanel doesn't get invalidated, unless the WS_CLIPCHILDREN style is removed from the parent.

--
Martin
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Transparent panel [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 5, 2015 10:52 AM   in response to: Jean-Milost Rey... in response to: Jean-Milost Rey...
Jean-Milost wrote:

Unfortunately, writing a transparent panel is a little more complex
than simply ignore background painting.

The VCL has a TCustomTransparentControl class, have a look at its source
to see the extra work it has to perform.

Canvas->Font = Font;

You should never do that. This can cause strange access violations
later.

No, it does not.

It's better to assign font as follow:

Canvas->Font->Assign(Font);

The TCanvas::Font property has a setter method that calls the Assign() method:

__property TFont* Font = {read=FFont, write=SetFont};


procedure TCanvas.SetFont(Value: TFont);
begin
  FFont.Assign(Value);
end;


--
Remy Lebeau (TeamB)
Jean-Milost Rey...

Posts: 23
Registered: 11/8/11
Re: Transparent panel [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 5, 2015 5:09 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Hi Remy,

Remy Lebeau (TeamB) wrote:
The VCL has a TCustomTransparentControl class, have a look at its source to see the extra work it has to perform.
I didn't know. Excellent info, thank you very much.

Regards
Martin Nijhoff

Posts: 75
Registered: 8/26/10
Re: Transparent panel [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 6, 2015 3:50 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Hi Remy,

The VCL has a TCustomTransparentControl class, have a look at its source
to see the extra work it has to perform.

The only extra work TCustomTransparentControl does (regarding the painting process), is invalidate any controls that may be located underneath the control. It will also not get invalidated unless it is moved, resized or explicitly invalidated.

The WS_EX_TRANSPARENT window style causes the control to be painted after any siblings without this style, but does not trigger a repaint when a sibling underneath repaints.

Do you know a way to fix this, without removing the parent's WS_CLIPCHILDREN window style?

Please note that TCustomTransparentControl doesn't check if controls are actually underneath (by checking if they intersect with the control). It also invalidates controls that are 'below' in Z-order but outside the control's bounding rectangle. That is, in XE6. I don't know if this changed in XE7.

--
Martin
Martin Nijhoff

Posts: 75
Registered: 8/26/10
Re: Transparent panel  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 5, 2015 3:40 AM   in response to: Martin Nijhoff in response to: Martin Nijhoff
As it turns out, I can fix the problem (i.e. my control not being invalidated) by removing the WS_CLIPCHILDREN window style of the parent control.

In my test app, I removed WS_CLIPCHILDREN of the form:

void __fastcall TForm1::CreateParams (TCreateParams &Params)
{
    inherited::CreateParams(Params);
 
    Params.Style &= ~WS_CLIPCHILDREN;
}


As it also turns out, removing WS_CLIPCHILDREN of the parent also voids the need for a Transparent property (and in my case a custom panel control).
I can simply set a standard TPanel's ParentBackground = true, to end up with a truely transparent panel.
Please note that a TPanel is not truely transparent without removing WS_CLIPCHILDREN of its parent.

However, removing the WS_CLIPCHILDREN window style may brake the painting of other controls.
One of these controls is TPageControl, which ends up with a completely transparent TTabSheet when the form is resized.

Another drawback that I found, is that it causes severe screen flickering when visual styles are applied to the form.
BTW, screen flickering does not occur when the default Windows theme is used.

Are there any other drawbacks to removing the WS_CLIPCHILDREN of the parent control?

--
Martin
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02