2010-04-14 38 views
0

我有一个带有主窗口和0个或多个其他窗口的Windows窗体应用程序。其他打开的窗口不属于主窗口,也不是模态对话框或任何东西。但是,默认行为是,如果主窗口关闭,则应用程序关闭,因为方法返回Application.Run。这很好,但因为用户可能在其他打开的窗口中有未保存的工作,所以我实现了一些窗体关闭逻辑。使用任务栏时出现奇数窗体关闭行为Close All Windows

当其他窗口关闭时,它会检查未保存的更改并使用标准的保存/不保存/取消Microsoft Word样式提示提示用户。

当主窗口关闭时,它会尝试先关闭所有其他打开的窗口。如果其中任何一个未能关闭(即用户点击了取消),则停止关闭事件。

这个逻辑发生在FormClosing事件中,除非用户使用任务栏的“关闭所有窗口”命令,否则它将很好地工作。当分组处于活动状态时(虽然它被标记为“关闭组”),它会出现在7的新任务栏以及XP/Vista中。

该命令似乎向所有窗口发送关闭消息。问题是每个其他窗口检查更改和提示,然后主窗口尝试关闭其他窗口。如果我使用标准的MessageBox.Show命令提示用户,则关闭事件在对话框等待用户响应时暂停。点击按钮后,按正常方式处理,但所有其他窗口要么放弃要么忽略窗口关闭命令。无论他们点击了什么都没有关系。显示提示的表单正确反应(如果它们按下Cancel,它仍然保持打开状态,否则它会正常关闭)。但其他所有窗口,包括主要行为都没有发生。他们的FormClosing事件从未被提出。

如果我使用TaskDialog(通过调用非托管TaskDialogIndirect),那么在出现提示时暂停表单关闭事件,而其他表单处理它们的表单关闭事件。这不会在同一个线程上(主UI线程)。当主窗口出现时,它会尝试关闭所有正常的窗体。任何尝试提示的表单仍处于打开状态,其他表单由于“​​关闭所有窗口”命令而自行关闭。主窗口试图关闭那些仍然存在的事件,导致第二个FormClosing事件进行处理,并且第二次尝试提示(毕竟,这些更改仍未保存!),所有主线程都会引起你的注意。

最终结果是,在通过调用堆栈展开后,提示符会连续出现两次。我知道这一切都是通过Visual Studio的调用堆栈在同一个线程上发生的。在任何时候,我可以一直回顾第一次提示尝试,直到它再次调用它。只有第二个调用似乎实际处理它并显示提示。第一次通过它几乎就像在非托管代码中的某处,它正在产生其他消息。我应该提到,我不会在任何地方自己调用Application.DoEvents。

是TaskDialogIndirect某种半异步调用?但我从来没有离开过这个主线。然后,为什么标准的MessageBox会立即提示(因为我认为TaskDialog也应该),但是似乎放弃了所有其他窗口关闭事件?其他窗口关闭消息是否可能超时?他们不应该只是在消息队列中等待,直到模态对话框(消息框)返回?

我有一种感觉,这可能是由于Windows窗体—或leaky abstraction的“Win32 API的管理包装”性质。

回答

0

关闭所有窗口(自XP以来)的实施是一种哈克。在您的FormClosing实施中,检查表单是否被禁用,因为TaskDialog或任何其他提示与窗体是提示的所有者一起显示。

在您检查时,请检查关闭方案如何执行WM_QUERYENDSESSION,即用户使用未决更改进行注销。

+0

我有适当的检查,应该关机或任务管理器强制退出,它会跳过这些检查更改,只是丢弃它们。 Close-all-windows命令显示为标准的UserClosing原因,就像他们点击红色的X.我无法检查打开的TaskDialogs,因为它们还没有出现。我第一次试图展示一个,它似乎在实际出现之前屈服于其他形式的关闭事件。 – 2010-04-14 16:41:32

+0

在显示保存更改提示之前,检查WS_DISABLED的表单。启动记事本并键入任何内容,然后尝试注销并获得确认。 – wqw 2010-04-14 17:39:14

0

Close all windowsWM_CLOSE发送到任务栏组中的所有窗口,通常(总是?)包括主窗口。许多应用程序在主窗口上有确认对话框提示,但不在子窗口上。某些子窗口可能会在主窗口之前收到WM_CLOSE消息,因此即使用户决定取消关闭请求也会关闭此消息。

下面是一些代码,如果它是发送消息的窗口之一,那么会拦截WM_CLOSE消息,然后将postsWM_CLOSE拦截到主窗口。这可以防止子窗口关闭,如果用户决定取消关闭请求,这很好。

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Drawing; 
using System.Linq; 
using System.Runtime.InteropServices; 
using System.Text; 
using System.Threading; 
using System.Windows.Forms; 

namespace WindowsFormsApplication1 { 

public static class CloseAllWindowsHandler { 

    private const int WM_CLOSE = 0x10; 
    private const int WM_DESTROY = 0x2; 

    static List<NW> closing = new List<NW>(); 
    static List<NW> nws = new List<NW>(); 
    static Thread thread = null; 
    static IntPtr hwndMainWindow = IntPtr.Zero; 

    private class NW : NativeWindow { 

     // determine to allow or deny the WM_CLOSE messages 
     bool intercept = true; 

     public NW() {} 

     protected override void WndProc(ref System.Windows.Forms.Message m) { 
      if (m.Msg == WM_CLOSE) { 
       if (!intercept) { 
        intercept = true; 
        base.WndProc(ref m); 
        return; 
       } 

       closing.Add(this); 

       Thread t = null; 
       t = new Thread(() => { 
        try { 
         Thread.Sleep(100); 
        } catch {} 

        if (thread == t) { 
         // no more close requests received in the last 100 ms 
         // if a close request was sent to the main window, then only post a message to it 
         // otherwise send a close request to each root node at the top of the owner chain 
         NW nwMain = null; 
         foreach (NW nw in closing) { 
          if (nw.Handle == hwndMainWindow) { 
           nwMain = nw; 
           break; 
          } 
         } 

         BackgroundWorker bgw = new BackgroundWorker(); 
         var closing2 = closing; 
         closing = new List<NW>(); 
         bgw.RunWorkerCompleted += (o, e) => { 
          try { 
           if (nwMain != null) { 
            // if the 'Close all windows' taskbar menu item is clicked, then closing2.Count 
            // will contain all the window handles 
            nwMain.intercept = false; 
            PostMessage(hwndMainWindow, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); 
           } 
           else { 
            // doesn't seem to ever happen, closing2.Count always equals 1 
            // so nothing really has to be done 
            // if (closing2.Count > 1) 

            foreach (NW nw in closing2) { 
             nw.intercept = false; 
             PostMessage(nw.Handle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); 
            } 
           } 
           bgw.Dispose(); 
          } catch {} 
         }; 
         bgw.RunWorkerAsync(); 
        } 
       }); 
       thread = t; 
       t.IsBackground = true; 
       t.Priority = ThreadPriority.Highest; 
       t.Start(); 
       return; 
      } 
      else if (m.Msg == WM_DESTROY) { 
       ReleaseHandle(); 
       nws.Remove(this); 
      } 

      base.WndProc(ref m); 
     } 
    } 

    [DllImport("user32.dll")] 
    private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 

    [DllImport("user32.dll")] 
    public static extern IntPtr GetParent(IntPtr hWnd); 

    private static void RegisterWindow(IntPtr hwnd) { 
     NW nw = new NW(); 
     nws.Add(nw); // prevent garbage collection 
     nw.AssignHandle(hwnd); 
    } 

    private const int WINEVENT_OUTOFCONTEXT = 0; 
    private const int EVENT_OBJECT_CREATE = 0x8000; 

    public static void AssignHook(IntPtr mainWindowHandle) { 
     hwndMainWindow = mainWindowHandle; 
     uint pid = 0; 
     uint tid = GetWindowThreadProcessId(mainWindowHandle, out pid); 
     CallWinEventProc = new WinEventProc(EventCallback); 
     hHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_CREATE, IntPtr.Zero, CallWinEventProc, pid, tid, WINEVENT_OUTOFCONTEXT);  
    } 

    [DllImport("user32.dll")] 
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 

    [DllImport("user32.dll")] 
    private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); 

    [DllImport("user32.dll")] 
    private static extern int UnhookWinEvent(IntPtr hWinEventHook); 

    private static IntPtr hHook = IntPtr.Zero; 
    private static WinEventProc CallWinEventProc; 
    private delegate void WinEventProc(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime); 
    private static void EventCallback(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime) { 
     if (iEvent == EVENT_OBJECT_CREATE) {  
      IntPtr pWnd = GetParent(hWnd); 
      if (pWnd == IntPtr.Zero) { // top level window 
       RegisterWindow(hWnd); 
      } 
     } 
    } 
} 

public class Form2 : Form { 

    public Button btnOpen = new Button { Text = "Open" }; 
    public CheckBox cbConfirmClose = new CheckBox { Text = "Confirm Close" }; 
    private static int counter = 0; 
    public Form2() { 
     Text = "Form" + counter++; 
     FlowLayoutPanel panel = new FlowLayoutPanel { Dock = DockStyle.Top }; 
     panel.Controls.AddRange(new Control [] { btnOpen, cbConfirmClose }); 
     Controls.Add(panel); 

     btnOpen.Click += btnOpen_Click; 
    } 

    void btnOpen_Click(object sender, EventArgs e) { 
     Form2 f = new Form2(); 
     f.Owner = this; 
     f.Size = new Size(300,300); 
     f.Show(); 
    } 

    protected override void OnFormClosing(FormClosingEventArgs e) { 
     if (cbConfirmClose.Checked) { 
      var dr = MessageBox.Show(this, "Confirm close?", "Close " + Text, MessageBoxButtons.OKCancel); 
      if (dr != System.Windows.Forms.DialogResult.OK) 
       e.Cancel = true; 
     } 

     base.OnFormClosing(e); 
    } 
} 

public class Program2 { 

    [STAThread] 
    static void Main() { 

     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     Form2 f2 = new Form2(); 
     f2.HandleCreated += delegate { 
      CloseAllWindowsHandler.AssignHook(f2.Handle); 
     }; 
     Application.Run(f2); 
    } 
} 

} 
相关问题