FredS 138 Posted April 1, 2021 Last time I had a similar issue I simply used a DX Adorner.. perhaps you can use a panel that changes parent, visibility and is re positioned and be done with it.. This works because those would cover a disabled/dimmed tab but be accessible since they aren't parented by the tab.. Share this post Link to post
FredS 138 Posted April 1, 2021 Last time I had a similar issue I simply used a DX Adorner.. perhaps you can use a panel that changes parent, visibility and is re positioned and be done with it.. This works because those would cover a disabled/dimmed tab but be accessible since they aren't parented by the tab.. Share this post Link to post
Remy Lebeau 1395 Posted April 1, 2021 19 hours ago, aehimself said: 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. A LONG time ago (9 years!), I posted a demo on StackOverflow for displaying an alpha-blended TForm as a dimming shadow over top of another TForm, while allowing individual controls to remain uncovered and interactive through the shadow: How do I put a semi transparent layer on my form Basically, the shadow TForm uses its AlphaBlend/Value properties to provide the dimming effect, and its TransparentColor/Value properties to mask out holes in the shadow where individual controls want to peek through. I'm sure a similar technique can be adapted for this situation. 1 Share this post Link to post
Renate Schaaf 64 Posted April 1, 2021 29 minutes ago, Remy Lebeau said: Basically, the shadow TForm uses its AlphaBlend/Value properties to provide the dimming effect, and its TransparentColor/Value properties to mask out holes in the shadow where individual controls want to peek through. Dang! That's like the sample I posted and then deleted. There's nothing one can think of without you guys having thought of it before... Share this post Link to post
aehimself 396 Posted April 1, 2021 (edited) This is the final unit I created and which works as I expected to. Feel free to suggest improvements and use if you see fit! I'm not sure if selecting areas to capture / paint would speed up the process but that is a plausible choke point. Unit uDimPanel; Interface Uses Vcl.ExtCtrls, Vcl.Graphics, Vcl.Controls; Type TDimPanel = Class(TPanel) strict private _bitmap: TBitMap; Procedure UpdateBitmap; protected Procedure Paint; Override; Procedure Resize; Override; Procedure VisibleChanging; Override; public Constructor Create(inOwner: TWinControl); ReIntroduce; Destructor Destroy; Override; End; Implementation Uses System.SysUtils, System.Classes, WinApi.Windows; //Procedure TDimPanel.SetTransparent(Const inWinControl: TWinControl; Const inTransparent: Boolean); //Var // exStyle: DWORD; //Begin // exStyle := GetWindowLongPtr(inWinControl.Handle, GWL_EXSTYLE); // If exStyle = 0 Then RaiseLastOSError; // // If inTransparent Then // Begin // exStyle := exStyle Or WS_EX_LAYERED; // If SetWindowLongPtr(inWinControl.Handle, GWL_EXSTYLE, exStyle) = 0 Then // RaiseLastOSError; // If Not SetLayeredWindowAttributes(inWinControl.Handle, 0, 127, LWA_ALPHA) Then // RaiseLastOSError; // End // Else // Begin // exStyle := exStyle XOr WS_EX_LAYERED; // SetWindowLong(inWinControl.Handle, GWL_EXSTYLE, exStyle); // End; //End; Constructor TDimPanel.Create(inOwner: TWinControl); Begin inherited Create(nil); // Change to inOwner if you don't want to free it up by yourself... _bitmap := Vcl.Graphics.TBitMap.Create; // Drawing with opacity might alphablend seemingly random parts with // pfDevice (default) or pf32bit. Enforce a 24 bit pixel format to ensure // what is on the owner is what gets painted. _bitmap.PixelFormat := pf24bit; Self.Visible := False; Self.DoubleBuffered := True; Self.Parent := inOwner; Self.ParentBackground := False; // Might cause flicker if true, plus we are custom drawing Self.Left := 0; Self.Top := 0; Self.Width := Self.Parent.ClientWidth; Self.Height := Self.Parent.ClientHeight; Self.Anchors := [akLeft, akTop, akRight, akBottom]; Self.BevelOuter := bvNone; End; Destructor TDimPanel.Destroy; Begin FreeAndNil(_bitmap); inherited; End; Procedure TDimPanel.Paint; Begin // Omit the call to inherited in general. We only need a black background // and the opaque bitmap we captured earlier. Self.Canvas.Brush.Color := clBlack; Self.Canvas.FillRect(Rect(0, 0, Self.Width, Self.Height)); Self.Canvas.Draw(0, 0, _bitmap, 125); 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 // If the dimpanel is visible, it will be included in the screenshot. So // let's "hide" it... Self.SendToBack; // ...and kindly ask the parent to repaint so new dimensions can be // captured correctly! Self.Parent.Repaint; End; Try _bitmap.SetSize(Self.Parent.Width, Self.Parent.Height); dc := GetDC(Self.Parent.Handle); Try BitBlt(_bitmap.Canvas.Handle, 0, 0, _bitmap.Width, _bitmap.Height, dc, 0, 0, SRCCOPY); Finally ReleaseDC(Self.Parent.Handle, dc); End; Finally If Self.Visible Then Self.BringToFront; End; End; Procedure TDimPanel.VisibleChanging; Begin inherited; If Not Self.Visible Then Begin Self.Resize; Self.UpdateBitmap; // UpdateBitmap is not called if Self.Visible is false... End Else _bitmap.SetSize(0, 0); // clear bitmap to free up memory End; End. Edit: I might also want to disable controls on the parent in VisibleChanging to ensure nothing can be focused by pressing tab as @Attila Kovacs suggested earlier. Edited April 1, 2021 by aehimself Share this post Link to post
Renate Schaaf 64 Posted April 2, 2021 (edited) 8 hours ago, aehimself said: I might also want to disable controls on the parent in VisibleChanging to ensure nothing can be focused by pressing tab as @Attila Kovacs suggested earlier. Good idea, also it's possible to parent the panel to the pagecontrol (I hadn't thought that it would work, but...). Then you also have the pagecontrol tabs disabled. https://imgur.com/48gzWCX (how do you get images in here?) But for this some small changes must be made: Constructor TDimPanel.Create(inOwner: TWinControl); Begin .......... //******Width/Height instead of ClientWidth/ClientHeight Self.Width := Self.Parent.Width; Self.Height := Self.Parent.Height; Self.Anchors := [akLeft, akTop, akRight, akBottom]; Self.BevelOuter := bvNone; End; Procedure TDimPanel.VisibleChanging; Begin inherited; If Not Self.Visible Then Begin Self.Resize; Self.UpdateBitmap; // UpdateBitmap is not called if Self.Visible is false... //******** Self.BringToFront; //BringToFront isn't called either, necessary for parent is TPageControl End Else _bitmap.SetSize(0, 0); // clear bitmap to free up memory End; Now it would be really nice if one could use it as a component so one can design the controls on it... Edited April 2, 2021 by Renate Schaaf Share this post Link to post
aehimself 396 Posted April 2, 2021 4 hours ago, Renate Schaaf said: Procedure TDimPanel.VisibleChanging; Begin inherited; If Not Self.Visible Then Begin Self.Resize; Self.UpdateBitmap; // UpdateBitmap is not called if Self.Visible is false... //******** Self.BringToFront; //BringToFront isn't called either, necessary for parent is TPageControl End Else _bitmap.SetSize(0, 0); // clear bitmap to free up memory End; True that! In my code I have a child inherited from this and I'm calling BringToFront there... but yes, it makes a lot more sense here! 4 hours ago, Renate Schaaf said: Now it would be really nice if one could use it as a component so one can design the controls on it... Feel free to add the Register procedure 🙂 I like to keep my IDE as clean as possible... too many components might make it sluggist / unstable. Share this post Link to post
aehimself 396 Posted April 4, 2021 Unfortunately I cannot go back to delete the previous unit implementations so they have to stay. Anyway I added enabling/disabling parent controls AND the register procedure. Thank you all who helped to make this happen! 🙂 Unit uDimPanel; Interface Uses Vcl.ExtCtrls, Vcl.Graphics, Vcl.Controls, System.Generics.Collections; Type TDimPanel = Class(TPanel) strict private _bitmap: TBitMap; _enabledcontrols: TList<TControl>; Procedure DisableParentControls; Procedure EnableParentControls; Procedure UpdateBitmap; strict protected Procedure InternalThemeChanged(Const inSystemTheme: Boolean); Virtual; protected Procedure Paint; Override; Procedure Resize; Override; Procedure VisibleChanging; Override; public Constructor Create(inOwner: TWinControl); ReIntroduce; Virtual; Destructor Destroy; Override; Procedure ThemeChanged(Const inSystemTheme: Boolean); End; Procedure Register; Implementation Uses System.SysUtils, System.Classes, WinApi.Windows; Procedure Register; Begin RegisterComponents('Additional', [TDimPanel]); End; //Procedure TDimPanel.SetTransparent(Const inWinControl: TWinControl; Const inTransparent: Boolean); //Var // exStyle: DWORD; //Begin // exStyle := GetWindowLongPtr(inWinControl.Handle, GWL_EXSTYLE); // If exStyle = 0 Then RaiseLastOSError; // // If inTransparent Then // Begin // exStyle := exStyle Or WS_EX_LAYERED; // If SetWindowLongPtr(inWinControl.Handle, GWL_EXSTYLE, exStyle) = 0 Then // RaiseLastOSError; // If Not SetLayeredWindowAttributes(inWinControl.Handle, 0, 127, LWA_ALPHA) Then // RaiseLastOSError; // End // Else // Begin // exStyle := exStyle XOr WS_EX_LAYERED; // SetWindowLong(inWinControl.Handle, GWL_EXSTYLE, exStyle); // End; //End; Constructor TDimPanel.Create(inOwner: TWinControl); Begin inherited Create(nil); // Change to inOwner if you don't want to free it up by yourself... _bitmap := Vcl.Graphics.TBitMap.Create; // Drawing with opacity might alphablend seemingly random parts with // pfDevice (default) or pf32bit. Enforce a 24 bit pixel format to ensure // what is on the owner is what gets painted. _bitmap.PixelFormat := pf24bit; _enabledcontrols := TList<TControl>.Create; Self.Visible := False; Self.DoubleBuffered := True; Self.Parent := inOwner; Self.ParentBackground := False; // Might cause flicker if true, plus we are custom drawing Self.Left := 0; Self.Top := 0; Self.Width := Self.Parent.ClientWidth; Self.Height := Self.Parent.ClientHeight; Self.Anchors := [akLeft, akTop, akRight, akBottom]; Self.BevelOuter := bvNone; End; Destructor TDimPanel.Destroy; Begin FreeAndNil(_bitmap); FreeAndNil(_enabledcontrols); inherited; End; Procedure TDimPanel.DisableParentControls; Var a: Integer; Begin // Should be empty every time, but to be sure... _enabledcontrols.Clear; For a := 0 To Self.Parent.ControlCount - 1 Do If (Self.Parent.Controls[a] <> Self) And Self.Parent.Controls[a].Enabled Then Begin _enabledcontrols.Add(Self.Parent.Controls[a]); Self.Parent.Controls[a].Enabled := False; End; End; Procedure TDimPanel.EnableParentControls; Var control: TControl; Begin Try For control In _enabledcontrols Do control.Enabled := True; Finally _enabledcontrols.Clear; End; End; Procedure TDimPanel.InternalThemeChanged(Const inSystemTheme: Boolean); Begin // Dummy End; Procedure TDimPanel.Paint; Begin // Omit the call to inherited in general. We only need a black background // and the opaque bitmap we captured earlier. Self.Canvas.Brush.Color := clBlack; Self.Canvas.FillRect(Rect(0, 0, Self.Width, Self.Height)); Self.Canvas.Draw(0, 0, _bitmap, 125); End; Procedure TDimPanel.Resize; Begin inherited; If Self.Visible Then Self.UpdateBitmap; End; Procedure TDimPanel.ThemeChanged(Const inSystemTheme: Boolean); Begin Self.InternalThemeChanged(inSystemTheme); End; Procedure TDimPanel.UpdateBitmap; Var dc: HWND; Begin If Self.Visible Then Begin // If the dimpanel is visible, it will be included in the screenshot. So // let's "hide" it... Self.SendToBack; // ...and kindly ask the parent to repaint so new dimensions can be // captured correctly! Self.Parent.Repaint; End; Try _bitmap.SetSize(Self.Parent.Width, Self.Parent.Height); dc := GetDC(Self.Parent.Handle); Try BitBlt(_bitmap.Canvas.Handle, 0, 0, _bitmap.Width, _bitmap.Height, dc, 0, 0, SRCCOPY); Finally ReleaseDC(Self.Parent.Handle, dc); End; Finally If Self.Visible Then Self.BringToFront; End; End; Procedure TDimPanel.VisibleChanging; Begin inherited; If Not Self.Visible Then Begin // Force owned controls to resize before showing the panel Self.Resize; // Make sure nothing can be interacted with while parent is dimmed Self.DisableParentControls; // UpdateBitmap is not called if Self.Visible is false... Self.UpdateBitmap; // Neither does BringToFront Self.BringToFront; End Else Begin // Clear bitmap to free up memory _bitmap.SetSize(0, 0); // Re-enable all controls we disabled earlier Self.EnableParentControls; End; End; End. 1 Share this post Link to post
Renate Schaaf 64 Posted April 5, 2021 I would remove the Register-procedure for the time being, it needs a quite a few changes to make it play nicely in the IDE, one sore point is e.g. the re-introduced constructor. Share this post Link to post
aehimself 396 Posted April 5, 2021 7 hours ago, Renate Schaaf said: I would remove the Register-procedure for the time being, it needs a quite a few changes to make it play nicely in the IDE, one sore point is e.g. the re-introduced constructor. To be completely honest I did not try to install it in the IDE as I prefer to create most of my custom components runtime (in my local unit the Register procedure does not exist at all because of this). The constructor can be the standard, just throw an AV if the specified owner is not a TWinControl. What else is problematic? Share this post Link to post
Renate Schaaf 64 Posted April 5, 2021 31 minutes ago, aehimself said: The constructor can be the standard, just throw an AV if the specified owner is not a TWinControl. If you drop a component, the owner is always the form. That's one of the things. You could of course always drop it on the control you want to dim, then you've got the parent right. But some things need to be disabled at design time, the VisibleChanging for once. I'm working on something that doesn't blow up in your face, lets you design the child controls, and also allows you to specify a dimmed control other than what you can drop it on at design time ... say, a listbox or a pagecontrol. Much of this works really nicely with your runtime-only version. I want to get this done without having to write a designer. Maybe it's not worth the trouble and placing the controls at runtime is easy enough 🙂 1 Share this post Link to post
Renate Schaaf 64 Posted April 6, 2021 (edited) Ok, here is the installable component. The dimming is not tied to visible anymore, but to a property Active, which can only be set at runtime. The control to be dimmed isn't tied to the parent at design time, instead there is a published property DimmedControl, which the parent will be set to at runtime (unless it's nil, then no dimming is possible). At design time the component can be dropped anywhere to design the child controls. DimmedControl can be changed at runtime. The attachment contains packages for runtime and design and a crude demo. I haven't changed aehimself's unit- and class names, which I should have done, but I'm too tired now. I'm using Delphi 10.3.3, it should work for 10.4.*, too. Instructions: Load BuildandDemo.groupproj Build DimPanel.bpl (runtime) for both Win32 and Win64 Build DimPanelD.bpl (design) Install DimPanelD.bpl Open and run DemoDim The component is on the "Additional" tab. If TDimPanel is grayed under Win64, try to close and reopen the IDE. Hope it roughly works 🙂 Edit: Bug in previous attachment The size was not set right initially, has been fixed by an override to Loaded DimPanel.zip Edited April 6, 2021 by Renate Schaaf Bug in previous attachment 2 Share this post Link to post
Renate Schaaf 64 Posted April 8, 2021 Just a note: Flickering when using styles can almost completely be eliminated by removing seBorder from the form's style elements. But if you prefer to see the buggy styled caption bar, you're out of luck:) Share this post Link to post