views:

752

answers:

5

Hi,

How to dim / fade all other windows of an application in Delphi 2009.

Form has an AlphaBlend property, but it controls only transparency level. But it would be nice if we can have something like this (Concentrated window) . Even stackoverflow.com does that, when we try to insert a link/ image etc in the post.

How can we achieve this in a delphi application?

Thanks & Regards, Pavan

+1  A: 

I'm not sure about the "right" way to do it, but in order to "fade-to-white", what you can do is place your form in another completely white form (white background color, no controls).

So when your form is in 0% transparency, it will show as a regular form, but when it's in 50% transparency it will be faded to white. You can obviously choose other colors as your background.

I'm looking forward to seeing other answers...

EDIT: after seeing your "Jedi Concentrate" link, it seems that a dark-gray background will mimic the Expose effect better.

Roee Adler
+15  A: 

Here is a unit I just knocked together for you.

To use this unit drop a TApplication component on your main form and in the OnModalBegin call _GrayForms and then in the OnModalEnd call the _NormalForms method.

This is a very simple example and could be made to be more complex very easily. Checking for multiple call levels etc....

For things like system (open, save, etc) dialogs you can wrap the dialog execute method in a try...finally block calling the appropriate functions to get a similar reaction.

This unit should work on Win2k, WinXP, Vista and should even work on Win7.

Ryan.

unit GrayOut;

interface

procedure _GrayForms;
procedure _GrayDesktop;
procedure _NormalForms;

implementation

uses windows, classes, forms, Contnrs, Types, Graphics, sysutils;

var
   gGrayForms : TComponentList;

procedure _GrayDesktop;
var
   loop : integer;
   wScrnFrm : TForm;
   wForm : TForm;
   wPoint : TPoint;

begin
   if not assigned(gGrayForms) then
   begin
      gGrayForms := TComponentList.Create;
      gGrayForms.OwnsObjects := true;

      for loop := 0 to Screen.MonitorCount - 1 do
      begin
         wForm := TForm.Create(nil);
         gGrayForms.Add(wForm);

         wForm.Position := poDesigned;
         wForm.AlphaBlend := true;
         wForm.AlphaBlendValue := 64;
         wForm.Color := clBlack;
         wForm.BorderStyle := bsNone;
         wForm.Enabled := false;
         wForm.BoundsRect := Screen.Monitors[loop].BoundsRect;
         SetWindowPos(wForm.handle, HWND_TOP, 0,0,0,0, SWP_NOSIZE or SWP_NOMOVE);
         wForm.Visible := true;
      end;
   end;
end;

procedure _GrayForms;
var
   loop : integer;
   wScrnFrm : TForm;
   wForm : TForm;
   wPoint : TPoint;
   wScreens : TList;

begin
   if not assigned(gGrayForms) then
   begin
      gGrayForms := TComponentList.Create;
      gGrayForms.OwnsObjects := true;

      wScreens := TList.create;
      try
         for loop := 0 to Screen.FormCount - 1 do
            wScreens.Add(Screen.Forms[loop]);

         for loop := 0 to wScreens.Count - 1 do
         begin
            wScrnFrm := wScreens[loop];

            if wScrnFrm.Visible then
            begin
               wForm := TForm.Create(wScrnFrm);
               gGrayForms.Add(wForm);

               wForm.Position := poOwnerFormCenter;
               wForm.AlphaBlend := true;
               wForm.AlphaBlendValue := 64;
               wForm.Color := clBlack;
               wForm.BorderStyle := bsNone;
               wForm.Enabled := false;
               wForm.BoundsRect := wScrnFrm.BoundsRect;
               SetWindowLong(wForm.Handle, GWL_HWNDPARENT, wScrnFrm.Handle);
               SetWindowPos(wForm.handle, wScrnFrm.handle, 0,0,0,0, SWP_NOSIZE or SWP_NOMOVE);
               wForm.Visible := true;
            end;
         end;
      finally
         wScreens.free;
      end;
   end;
end;

procedure _NormalForms;
begin
   FreeAndNil(gGrayForms);
end;

initialization
   gGrayForms := nil;

end.
Ryan J. Mills
Interesting solution, but why not just use ONE form that covers the entire desktop with a larger alphablendvalue? The method above will cause boxes on overlapped forms which will appear darker.
skamradt
If your going to overlap the entire desktop then that would be great, but I figure that's only useful for a system modal dialog rather than a application modal dialog. Like I said this was something I knocked off quickly as an example. I don't currently have a use for something like this and I thought this would be a good mental exersize.
Ryan J. Mills
Besides your question is about all other windows of the application. Not the desktop.
Ryan J. Mills
+1, also for your comments.
mghie
I've updated the unit. It now also does the Desktop using the _GrayDesktop method which should also work with multiple monitors. I haven't tested that one yet. I also fixed the over lapped window issue. Personally I think I may have to use this in one of my applications now.
Ryan J. Mills
As pointed out by mghie in another comment. The _GrayDesktop method would only be really effective during a System Modal dialog. Otherwise the Alt-Tab might have a negative effect.
Ryan J. Mills
_GrayDesktop() does *not* work with multiple monitors, it just blends one half of each monitor on my system.
mghie
Problem is with poDesktopCenter. Using poDesigned works, but then the taskbar is affected as well - the code should calculate the work area for each monitor separately instead of using Screen.Monitors[loop].BoundsRect.
mghie
@mghie, Thanks for the update. I was wondering about that. I don't have multiple monitors available on my laptop, just on my dev machines. I'll have to fix it and resubmit it later.
Ryan J. Mills
I like it. +1 for a simple solution that can be applied without requiring changes to the actual forms.
Cobus Kruger
I've updated the code to fix the _Graydesktop method. It now uses poDesigned instead of poDesktopCenter. As for it graying the taskbar I would expect it to do that for a System Modal event anyways. Personal preference I guess.
Ryan J. Mills
A: 

I created a similar effect to the Jedi Concentrate with a Form sized to the Screen.WorkArea with Color := clBlack and BorderStyle := bsNone

I found setting the AlphaBlendValue was too slow to animate nicely, so I use SetLayeredWindowAttributes()

The unit's code:

unit frmConcentrate;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs;

type
   TFadeThread = class(TThread)
   private
      fForm: TForm;
   public
      constructor Create(frm: TForm);
      procedure Execute; override;
   end;

   TConcentrateFrm = class(TForm)
      procedure FormDestroy(Sender: TObject);
      procedure FormClick(Sender: TObject);
   private
      { Private declarations }
      fThread: TFadeThread;
   public
      { Public declarations }
   end;

procedure StartConcentrate(aForm: TForm = nil);

var
   ConcentrateFrm: TConcentrateFrm;

implementation

{$R *.dfm}

procedure StartConcentrate(aForm: TForm = nil);
var
   Hnd: HWND;
begin
   try
      if not Assigned(ConcentrateFrm) then
         ConcentrateFrm := TConcentrateFrm.Create(nil)
      else
         Exit;

      ConcentrateFrm.Top    := Screen.WorkAreaTop;
      ConcentrateFrm.Left   := Screen.WorkAreaLeft;
      ConcentrateFrm.Width  := Screen.WorkAreaWidth;
      ConcentrateFrm.Height := Screen.WorkAreaHeight;

      Hnd := GetForegroundWindow;

      SetWindowLong(ConcentrateFrm.Handle, GWL_EXSTYLE,
         GetWindowLong(ConcentrateFrm.Handle, GWL_EXSTYLE) or WS_EX_LAYERED
      );
      SetLayeredWindowAttributes(
         ConcentrateFrm.Handle,
         ColorToRGB(clBlack),
         0,
         LWA_ALPHA
      );
      ConcentrateFrm.Show;

      if Assigned(aForm) then
         aForm.BringToFront
      else
         SetForegroundWindow(Hnd);

      ConcentrateFrm.fThread := TFadeThread.Create(ConcentrateFrm);
      Application.ProcessMessages;
      ConcentrateFrm.fThread.Resume;
   except
      FreeAndNil(ConcentrateFrm);
   end;
end;

procedure TConcentrateFrm.FormClick(Sender: TObject);
var
   p: TPoint;
   hnd: HWND;
begin
   GetCursorPos(p);

   ConcentrateFrm.Hide;
   hnd := WindowFromPoint(p);
   while GetParent(hnd)  0 do
      hnd := GetParent(hnd);

   SetForegroundWindow(hnd);

   Release;
end;

procedure TConcentrateFrm.FormDestroy(Sender: TObject);
begin
   ConcentrateFrm := nil;
end;

{ TFadeThread }

constructor TFadeThread.Create(frm: TForm);
begin
   inherited Create(true);
   FreeOnTerminate := true;
   Priority := tpIdle;

   fForm := frm;
end;

procedure TFadeThread.Execute;
var
   i: Integer;
begin
   try
      // let the main form open before doing this intensive process.
      Sleep(300);

      i := 0;
      while i < 180 do
      begin
         if not Win32Check(
            SetLayeredWindowAttributes(
               fForm.Handle,
               ColorToRGB(clBlack),
               i,
               LWA_ALPHA
            )
         ) then
         begin
            RaiseLastOSError;
         end;
         Sleep(10);
         Inc(i, 4);
      end;
   except
   end;
end;

end.
jasonpenny
-1 for calling VCL methods from a worker thread, and for calling Windows API functions on a HWND created by a different thread.
mghie
I have been using this for a couple of years and I haven't noticed any problems. Are there problems that I just haven't seen?and which are VCL methods in the worker thread?
jasonpenny
Using "fForm.Handle" in the worker thread may result in any number of VCL methods being called, if the handle has not been allocated yet. Your code may work, but calling VCL methods from bg threads should best be avoided in general. And while accessing Windows objects from different threads may work it is very difficult to get right. If possible, avoid it as well. Good resource: http://blogs.msdn.com/oldnewthing/archive/2005/10/10/479124.aspx and the four following parts. Choice quote: "generally speaking, modifications to a window should be made only from the thread that owns it".
mghie
+1  A: 

One way to do this is to place another form behind your dialog, this form would have no borders, and would contain a single image. This image would be a capture of the entire desktop from just before the dialog popped up, then run through a transform to lower the luminosity of each pixel by 50%. One trick that works quite well here is to use a black form, and to only include ever other pixel. If you know for certain that you will have theme support, you can optionally use a completely black form and use the alphablend and alphablendvalue properties..this will allow the OS to perform the luminosity transformation for you. An alphablendvalue of 128 is = 50%.

EDIT

As mghie pointed out, there is the possibility of a user pressing alt-tab to switch to another application. One way to handle this scenario would be to hide the "overlay" window in the application.OnDeactivate event, and to show it on the application.OnActivate event. Just remember to set the zorder of the overlay window lower than your modal dialog.

skamradt
What happens when user alt-tabs to another application while the modal dialog is still open?
mghie
+1  A: 

I have done something similar for showing a modal form trying to keep the implementation as simple as possible. I don't know if this will fit your needs, but here it is:

function ShowModalDimmed(Form: TForm; Centered: Boolean = true): TModalResult;
var
  Back: TForm;
begin
  Back := TForm.Create(nil);
  try
    Back.Position := poDesigned;
    Back.BorderStyle := bsNone;
    Back.AlphaBlend := true;
    Back.AlphaBlendValue := 192;
    Back.Color := clBlack;
    Back.SetBounds(0, 0, Screen.Width, Screen.Height);
    Back.Show;
    if Centered then begin
      Form.Left := (Back.ClientWidth - Form.Width) div 2;
      Form.Top := (Back.ClientHeight - Form.Height) div 2;
    end;
    result := Form.ShowModal;
  finally
    Back.Free;
  end;
end;
Uwe Raabe