Jump to content
aehimself

How to "dim" a TabSheet?

Recommended Posts

Hello,

 

I am trying to "dim" a whole tabsheet so a notification can be clearly shown - blocking the sheet itself, but not blocking the main form. I guess I'd need an alpha blended panel or a semi-transparent bitmap drawn on a TImage... never really worked with imaging until now so I'm not sure about the terms. Other than the standard components I have Graphics32 installed; can this be achieved with a custom TPanel descendant or with Graphics32 somehow? I would like not to install any 3rd party component just for this if not needed.

 

Worst case scenario I'll capture the tabsheet as a bitmap and draw it on a alClient panel with Canvas.Draw - which supports opacity.

 

Using Delphi 10.4.2 if it matters.

Share this post


Link to post

...one small question, though. Is it possible to make the panel transparent, but not the components on it?

Share this post


Link to post
Guest
Posted (edited)

you can use "TransparentZZZZZ properties in your form" in an easy way. for another component, you can try to use the same color in the Form or in any other hack!

  • no need any code at all...

 

image.thumb.png.9b3383c3f792bbbee712d819e7739f93.png

 

hug

Edited by Guest

Share this post


Link to post
Posted (edited)
2 hours ago, aehimself said:

...one small question, though. Is it possible to make the panel transparent, but not the components on it?

As you see, the code I referred to isn't mine, I just found it useful. But let me try 🙂

 

Edit:

The short answer seems to be that it isn't supported. When one tries to remove the layered style from the child controls of the panel they either get invisible or one gets a Win-Error. For the long answer I guess you have to go back to your bitmap-idea.

This: https://www.codeguru.com/csharp/csharp/cs_controls/tutorials/article.php/c12323/Creating-NonTransparent-Controls-on-a-SemiTransparent-Window.htm is old, but looks good.

Edited by Renate Schaaf

Share this post


Link to post
7 hours ago, Renate Schaaf said:

You can use the code in https://www.delphipraxis.net/1486219-post6.html  to make a TWincontrol (semi)transparent (not just a stringgrid).

Do note that solution is applying the WS_EX_LAYERED window style to a child window, which is supported only in Windows 8 onwards, and only if the app manifest specifies Windows 8 as a <supportedOS>.

  • Thanks 1

Share this post


Link to post
9 hours ago, Renate Schaaf said:

When one tries to remove the layered style from the child controls of the panel they either get invisible or one gets a Win-Error.

I was afraid so. I got WinErrors when I tried to do so.

9 hours ago, Renate Schaaf said:

For the long answer I guess you have to go back to your bitmap-idea.

This: https://www.codeguru.com/csharp/csharp/cs_controls/tutorials/article.php/c12323/Creating-NonTransparent-Controls-on-a-SemiTransparent-Window.htm is old, but looks good.

While the bitmap screenshot idea will work, it feels really hacky. Makes me a bit more comfortable that others got to the same idea to this problem, though.

It's just a little bit strange that "dimming" is this complicated to achieve.

 

Thanks anyway, I'll start with the bitmap idea 🙂

Share this post


Link to post
Posted (edited)
3 hours ago, aehimself said:

While the bitmap screenshot idea will work, it feels really hacky.

 

Here's a different kind of hack:

Design another form with a completely transparent background (transparentColor = true) and borderless.

Place the controls you would have on the semitransparent panel on this form instead.

When you want to dim your tabsheet, place a blank semitransparent panel as before.

Then place the second form on top of the tabsheet and show it.

If so desired, you can have the transparent form size and move (WM_Move) with the main form.

   (In the handler for WM_Move check that the transparent form is assigned, as it is called very early for the main form)

Event handlers for the controls on the 2nd form can be assigned in the code of the main form to avoid circular reference.

It works so far for me, you can certainly figure out how to deal with the rest of the nooks and crannies.

 

This is fun! :classic_biggrin:

 

Edit: I forgot the styles: you need to remove seClient and seBorder from the style elements of the transparent form

 

Edited by Renate Schaaf
forgot the styles

Share this post


Link to post

So, the "screenshot" and repaint dimmed works... almost.

The bitmap is captured correctly (saved to a file, shows perfectly) but drawing it back causes some issues...

 

I have a TPanel descendant, like...

 TDimPanel= Class(TPanel)
 protected
  Procedure Paint; Override;
  Procedure Resize; Override;
 End;
 
Procedure TDimPanel.Resize;
Var
 dc: HWND;
Begin
 inherited;

 _bitmap.SetSize(0, 0); // Clear the bitmap
 _bitmap.SetSize(Self.Parent.Width, Self.Parent.Height);
// Self.Parent.PaintTo(_bitmap.Canvas.Handle, 0, 0); // Does not capture everything, leaves some components out...
 dc := GetDC(Self.Parent.Handle);
 BitBlt(_bitmap.Canvas.Handle, 0, 0, _bitmap.Width, _bitmap.Height, dc, 0, 0, SRCCOPY);
 _bitmap.SaveToFile('C:\shot.bmp');
End;

Procedure TDimPanel.Paint;
Begin
 inherited;

 Self.Canvas.Draw(0, 0, _bitmap, 128);
// Self.Canvas.Ellipse(0, Self.Height - 20, 20, Self.Height);
End;

But only the upper half of the bitmap is drawn on the panel, bottom half is empty. If ellipse drawing is uncommented, it shows up properly. The funny thing is that if I use

 Self.Canvas.Draw(0, 0, _bitmap);

all is drawn perfectly, but I loose opacity... I guess it will have something to do in how the bitmap is set up...? At the moment I have the following in the constructor:

 _bitmap := Vcl.Graphics.TBitMap.Create;
 _bitmap.Transparent := False;
 _bitmap.PixelFormat := pf32bit;

Moving the code out of my project to a TForm and using it's canvas to paint the bitmap to has the same behaviour.

 

Any help is greatly appreciated, these imaging-things are way out of my league.

Share this post


Link to post
7 minutes ago, aehimself said:

Any help is greatly appreciated

I'm confused, what's the reason for wanting your controls to look Un-Dimmed?

 

..and if that is the case why not custom draw just your Tabsheet/Panel background dimmed?

  • Like 1

Share this post


Link to post
3 minutes ago, FredS said:

I'm confused, what's the reason for wanting your controls to look Un-Dimmed?

 

..and if that is the case why not custom draw just your Tabsheet/Panel background dimmed?

I want to display a progress / warning indicator on a tabsheet which will block all actions to the components on the tabsheet... imagine like the UAC "popup".

Everything in the background is dimmed as they are unavailable at the moment, only the important stuff (buttons, labels, etc) are shown with full visibility.

Share this post


Link to post

One more thing, this is not really going to work when resizing, as the parent of my dimmed control is the tabsheet (which I need the image of). When I take an image of the tabsheet while the dimmed panel is visible, it's image will be seen on the picture, slowly fading out everything in the process.

 

I need to think think through.

Share this post


Link to post
2 hours ago, aehimself said:

Everything in the background is dimmed as they are unavailable at the moment, only the important stuff (buttons, labels, etc) are shown with full visibility.

Could you define the symmetric difference of the sets "Everything" and "important stuff" ?

We have no idea what else could be on the page which would make sense to dim but not the "important stuff".

Are you putting unimportant stuff onto the forms? 🙂 Enemy deception? 🙂 

Share this post


Link to post
7 hours ago, Attila Kovacs said:

Could you define the symmetric difference of the sets "Everything" and "important stuff" ?

We have no idea what else could be on the page which would make sense to dim but not the "important stuff".

Are you putting unimportant stuff onto the forms? 🙂 Enemy deception? 🙂 

I don't see why it is that important, but let's say you have a connection to a server on each tab. It can be a web page, RDP, or simply some data aware controls. Once the connection is broken, the tabsheed would go dimmed, with a clear message "Reconnecting" (and an abort button) would be shown. This is just one example.

Or let's say each tab allows you to manipulate data like... financial records. One tab = one person. There is a button which calculates some averages but since it takes so long, it is being done in a background thread. Until the thread is running no data must be modified as it can cause incorrect results. So, the tab sheet goes dimmed, with a marquee progress bar and a "Please wait" text.

Or, you have a chat application, like IRC. One tab = one channel. If you are kicked from a channel, the tab goes dimmed, saying "You have been kicked from the channel" and a button to close the tabsheet.

 

The things dimmed are not important at that stage because they can not be used; let them be a MsTSCAX control, TWebBrowser, TDBEdit or just a TButton - this is "everything". Important stuff means everything that the tab wants you to know at this point, a message maybe with some controls to interact with.

 

Hope this helps to clear the desired outcome 🙂

Share this post


Link to post

@aehimself I see. I'd add a client aligned panel on the top of it, drawing a dimmed/blurred image from the things below and adding some controls to this panel to interact with. You just have to make sure that the underlying controls can't be triggered/selected with pressing tab. Perhaps you need an intermediate container tab on each sheet to be able to disable the whole sheet but not the dimmed/blurred one.

  • Thanks 1

Share this post


Link to post
44 minutes ago, Attila Kovacs said:

@aehimself I see. I'd add a client aligned panel on the top of it, drawing a dimmed/blurred image from the things below and adding some controls to this panel to interact with. You just have to make sure that the underlying controls can't be triggered/selected with pressing tab. Perhaps you need an intermediate container tab on each sheet to be able to disable the whole sheet but not the dimmed/blurred one.

+1

Semi-transparent topmost Panel-dimmer with progress/label/cancel button on it, that's all. Usually it's invisible and appears only when needed.

Share this post


Link to post
47 minutes ago, Attila Kovacs said:

@aehimself I see. I'd add a client aligned panel on the top of it, drawing a dimmed/blurred image from the things below and adding some controls to this panel to interact with.

That is exactly what I am trying to achieve now. The problem I'm getting is why the TPanel's Self.Canvas.Draw(0, 0, _bitmap); works while Self.Canvas.Draw(0, 0, _bitmap, 128); draws only the top half of the panel... see 

50 minutes ago, Attila Kovacs said:

You just have to make sure that the underlying controls can't be triggered/selected with pressing tab. Perhaps you need an intermediate container tab on each sheet to be able to disable the whole sheet but not the dimmed/blurred one.

Good point, thank you! Did not think about this!

 

Share this post


Link to post
Posted (edited)
4 hours ago, aehimself said:

Self.Canvas.Draw(0, 0, _bitmap, 128); draws only the top half of the panel.

Maybe the bottom half gets alphablended away?  I noticed that black vanishes, which I don't understand. Everything is fine, when drawing a colored background first.

 

Quote

One more thing, this is not really going to work when resizing, as the parent of my dimmed control is the tabsheet (which I need the image of). When I take an image of the tabsheet while the dimmed panel is visible, it's image will be seen on the picture, slowly fading out everything in the process.

On resize:

You can send the panel to back, redraw its parent, capture the parent's bitmap, send the panel back to front. Surprisingly, there's hardly any flicker when doing this.

------------------------------- unless you use styles :classic_wacko:

Edited by Renate Schaaf

Share this post


Link to post
Posted (edited)
29 minutes ago, Renate Schaaf said:

Maybe the bottom half gets alphablended away?  I noticed that black vanishes, which I don't understand. Everything is fine, when drawing a colored background first.

Progress. If I open the saved bitmap with Irfanview and re-save it, drawing it with opacity works. Size drops from 1,9 to 1,4 MB, bit depth drops from 32 to 24 bit. So yes, something is wrong with the bitmap format 🙂

 

Edit: damn it. Creating the bitmap like

 _bitmap := Vcl.Graphics.TBitMap.Create;
// _bitmap.Transparent := False;
 _bitmap.PixelFormat := pf24bit;
// _bitmap.AlphaFormat := afDefined;

repainting with opacity works like a charm. It does not work with pfDevice or pf32bit.

 

Time to put my components back on it 🙂

Edited by aehimself

Share this post


Link to post
55 minutes ago, Renate Schaaf said:

You can send the panel to back, redraw its parent, capture the parent's bitmap, send the panel back to front. Surprisingly, there's hardly any flicker when doing this.

It works and surely the flicker is neglectable...unless you have VCL Styles active. Then it flickers like madness 🙂 Guess I'll just have to live with it 😄

 

For anyone interested, this is how the relevant code looks like:

 TDimPanel = Class(TPanel)
 strict private
  _bitmap: TBitMap;
 protected
  Procedure Paint; Override;
  Procedure Resize; Override;
  Procedure VisibleChanging; Override;
 End;

Procedure TDimPanel.Paint;
Begin
 Self.Canvas.Draw(0, 0, _bitmap, 80);
End;

Procedure TDimPanel.Resize;
Begin
 inherited;

 If Self.Visible Then
   Self.UpdateBitmap;
End;

Procedure TDimPanel.UpdateBitmap;
Var
 dc: HWND;
Begin
 If Self.Visible Then
 Begin
   Self.SendToBack;
   Self.Parent.Repaint;
 End;
 Try
   _bitmap.SetSize(Self.Parent.Width, Self.Parent.Height);
   dc := GetDC(Self.Parent.Handle);
   BitBlt(_bitmap.Canvas.Handle, 0, 0, _bitmap.Width, _bitmap.Height, dc, 0, 0, SRCCOPY);
 Finally
   If Self.Visible Then
     Self.BringToFront;
 End;
End;

Procedure TDimPanel.VisibleChanging;
Begin
 inherited;

 If Not Self.Visible Then
 Begin
   Self.UpdateBitmap;
   Self.Repaint;
 End
 Else
   _bitmap.SetSize(0, 0); // clear bitmap to free up memory
End;

...just make sure you change _bitmap.PixelFormat to pf24bit in the constructor 🙂

 

  • Like 1

Share this post


Link to post
Posted (edited)

I can't see ReleaseDC.

Also, instead of DC you could pass Canvas.Handle.

Also, you could tune it with only repainting clipping area's.

Btw, for me it works with pf32bit.

Edited by Attila Kovacs

Share this post


Link to post
1 minute ago, Attila Kovacs said:

I can't see ReleaseDC.

Also, instead of DC you could pass Canvas.Handle.

Also, you could tune if with only repainting clipping area's.

Btw, for me it works with pf32bit.

I never really worked with imaging, I had no idea that there is such a thing as ReleaseDC 🙂 I am relying on errors by ReportMemoryLeaksOnShutdown which missed this.

 

btw, if you meant to change it like this:

  BitBlt(_bitmap.Canvas.Handle, 0, 0, _bitmap.Width, _bitmap.Height, THackWinControl(Self.Parent).Canvas.Handle, 0, 0, SRCCOPY);

It does not work, throws an AV. Canvas seems to be nil?
 

Share this post


Link to post
8 minutes ago, aehimself said:

I am relying on errors by ReportMemoryLeaksOnShutdown which missed this.

Ok, it's not a memory leak. It's GDI leak 😉

 

9 minutes ago, aehimself said:

It does not work, throws an AV. Canvas seems to be nil?

You are right, there is not necessarily an initialized canvas. I assumed it's a TPanel already.

 

By the way, for some reason which I can't recall, I'm using a TImage and its TBitmap, not only TBitmap.

In order: Form -> TPanel client aligned -> TImage client aligned and blurred. (To "lock" an application)

Maybe to give the painting stuff away....

 

Share this post


Link to post
1 hour ago, aehimself said:

..unless you have VCL Styles active. Then it flickers like madness 🙂

For very simple forms I use a Fake-Style, which improves drawing immensely.

One could call RemoveStyle(self) in OnCreate and FakeStyle(self) in OnCreate or whenever the style changes:

 

uses ... VCL.Controls, VCL.ComCtrls, VCL.Forms, VCL.Themes;
//and what I forgot

type

  TCrackControl = class(Vcl.Controls.TControl);

procedure RemoveStyle(aControl: TControl);
var
  i: integer;
begin
  if (csDesigning in aControl.ComponentState) then
    exit;
  // I like to keep the style of the buttons and TabControls
  if not((aControl is TButton) or (aControl is TCustomTabControl)) then
    aControl.StyleElements := [];
  //keep the scrollbars of scrollboxes but get rid of the form's styled caption bar
  if (aControl is TScrollingWinControl) and (not(aControl is TForm)) then
  aControl.StyleElements:=[seBorder];

  if aControl is TWinControl then
    with TWinControl(aControl) do
      for i := 0 to ControlCount - 1 do
        RemoveStyle(Controls[i]);
end;

procedure FakeStyle(aControl: TControl);
var
  BGColor, FontColor {
    , SelectedColor,
    SelectedFontColor
  } : TColor;
  i: integer;
begin
  if (csDesigning in aControl.ComponentState) then
    exit;
  BGColor := StyleServices.GetStyleColor(scWindow);
  FontColor := StyleServices.GetStyleFontColor(sfWindowTextNormal);
  TCrackControl(aControl).Font.Color := FontColor;
  TCrackControl(aControl).Color := BGColor;
  if aControl is TWinControl then
    with TWinControl(aControl) do
      for i := 0 to ControlCount - 1 do
        FakeStyle(Controls[i]);
end;

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×