当显示对话框时,是否可以使应用程序的所有其他窗口变暗?

13
如何在Delphi 2009中使应用程序的所有其他窗口变暗/淡化。
Form具有AlphaBlend属性,但它仅控制透明度级别。但如果我们可以像这样(集中的窗口)就太好了。甚至stackoverflow.com也是这样做的,当我们尝试在帖子中插入链接/图像等时。
我们如何在Delphi应用程序中实现这一点?

你应该澄清你的问题,将所有其他应用程序窗口调暗/淡出与Exposé效果不同(在Windows上没有任何位置,那只是没有Exposé功能的盲目模仿编程)。 - mghie
1
请注意,这种行为与Windows平台上所有其他应用程序的行为不一致。尽管这是一个漂亮的效果,但您很可能只会增加Windows平台已经存在的界面混乱。 - onnodb
2009-2022年:现在Windows 11中的分层和高程是标准UID的一部分,但似乎当前的Delphi版本尚未支持这些功能。 - Giorgio Calzolato
5个回答

24

我为您准备了一个组件单元。

要使用此组件单元,请在主窗体上放置TApplication组件,并在OnModalBegin中调用_GrayForms,然后在OnModalEnd中调用_NormalForms方法。

这是一个非常简单的示例,可以很容易地使其更加复杂。检查多个调用级别等等......

对于像系统(打开,保存等)对话框之类的东西,您可以将对话框执行方法包装在try...finally块中,调用适当的函数以获得类似的反应。

这个组件单元应该能够在Win2k、WinXP、Vista上运行,甚至在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.

4
有趣的解决方案,但为什么不只使用一个表单覆盖整个桌面并具有更大的AlphaBlend值?上述方法会导致重叠表格上出现较暗的框。 - skamradt
3
如果你打算重叠整个桌面,那就太好了,但我觉得这只对系统模态对话框有用,而不是应用程序模态对话框。就像我说的,这只是我快速制作的一个示例。我目前没有使用类似这样的东西,我认为这是一个很好的思维锻炼。 - Vivian Mills
2
除此之外,您的问题涉及应用程序中的所有其他窗口,而不是桌面。 - Vivian Mills
1
问题出在poDesktopCenter上。使用poDesigned可以解决,但是任务栏也会受到影响 - 代码应该分别计算每个监视器的工作区域,而不是使用Screen.Monitors[loop].BoundsRect。 - mghie
我对此的问题是它运行非常缓慢:在一个有6个打开表单的应用程序中,所有表单一个接一个地变灰需要大约1秒钟。这是在Windows 7上的情况。有什么优化的想法吗?我已经尝试将Visible := True调用放在一个单独的循环中,在所有灰色表单创建后执行,但这没有带来明显的改善... - Oliver Giesen
显示剩余8条评论

8

我曾经为显示模态表单做过类似的事情,尽可能保持实现简单。我不知道这是否符合您的需求,但是这里是代码:

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;

不错。为当前的Delphi主题应用程序添加“Back.StyleElements:= [seFont,seBorder];”。无论如何,这并不是一种可行的方式,因为它会剥夺用户与桌面上其他窗口进行鼠标交互的可能性。 - Giorgio Calzolato
它确实是有意的。它甚至在为其创建的程序的要求中明确指定了。 - Uwe Raabe

1

我不确定做法是否“正确”,但为了实现“渐变至白”的效果,您可以将表单放置在另一个完全白色的表单中(白色背景色,没有控件)。

因此,当您的表单透明度为0%时,它将显示为常规表单,但当透明度为50%时,它将渐变至白色。您显然可以选择其他颜色作为背景。

我期待看到其他答案......

编辑:在看到您的“Jedi Concentrate”链接后,似乎深灰色背景会更好地模仿Expose效果。


1
一种方法是在对话框后面放置另一个窗体,该窗体没有边框,并且包含单个图像。该图像将是从对话框弹出之前的整个桌面的截图,然后通过变换将每个像素的亮度降低50%。这里有一个很好的技巧,就是使用黑色窗体,并仅包括每隔一个像素。如果您确定会有主题支持,可以选择使用完全黑色的窗体并使用alphablend和alphablendvalue属性..这将允许操作系统为您执行亮度转换。 alphablendvalue为128等于50%。
编辑
正如mghie指出的那样,用户可能会按alt-tab切换到另一个应用程序。处理此场景的一种方法是在application.OnDeactivate事件中隐藏“覆盖”窗口,并在application.OnActivate事件中显示它。只需记住将覆盖窗口的zorder设置为模态对话框以下即可。

当用户在模态对话框仍然打开时切换到另一个应用程序时会发生什么? - mghie

0

我使用一个大小与屏幕工作区相同的窗体,颜色为clBlack,BorderStyle为bsNone,创建了类似Jedi Concentrate的效果。

我发现设置AlphaBlendValue速度太慢,无法流畅地进行动画,因此我使用了SetLayeredWindowAttributes()。

该单元的代码:

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.

1
不应该从工作线程中调用VCL方法,也不应该在由另一个线程创建的HWND上调用Windows API函数。 - mghie
我已经使用这个东西几年了,没有注意到任何问题。难道还有我没发现的问题吗?在工作线程中哪些是VCL方法? - jasonpenny
在工作线程中使用"fForm.Handle"可能会导致调用任意数量的VCL方法,如果句柄尚未分配。您的代码可能有效,但通常最好避免从bg线程调用VCL方法。虽然从不同线程访问Windows对象可能有效,但很难正确处理。如果可能的话,也应该避免它。良好的资源:http://blogs.msdn.com/oldnewthing/archive/2005/10/10/479124.aspx和其后四个部分。选择引用:“一般来说,只有拥有窗口的线程才应该对窗口进行修改”。 - mghie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接