1

外部アプリケーションが閉じられたときにアラートを受け取ろうとしています。私のテストでは、メモ帳のhwndを取得して、モニターに設定しました。不思議なことに、メモ帳が閉じていなくても、メモ帳から他のアプリケーションをクリックするたびにシステムイベントが発生します。誰かがその理由を知っていますか?

コードを呼び出す簡単な例:

uint EVENT_OBJECT_DESTROY = 0x8001;
IntPtr notepadHwnd = <your pointer>
var mon = new WindowMonitor(notepadHwnd, EVENT_OBJECT_DESTROY);
mon.EventOccurred += (sender, args) => Console.WriteLine("closed");

モニター:

  public class WindowMonitor : IDisposable
  {
    //store delegate to prevent GC
    private User32.WinEventDelegate dEvent;

    private IntPtr _hook;
    private readonly IntPtr _window;
    private readonly List<uint> _watchedEvents = new List<uint>();

    public event EventHandler<AccessibleEventTypeEventArgs> EventOccurred;

    public WindowMonitor(IntPtr windowToMonitor, params User32.AccessibleEventType[] eventsToMonitor)
    {
      //prevent junk
      if (eventsToMonitor == null || eventsToMonitor.Length == 0)
        throw new ArgumentNullException("eventsToMonitor", "Must specify events to monitor");
      if (windowToMonitor == IntPtr.Zero)
        throw new ArgumentNullException("windowToMonitor", "Must specify a valid window handle to monitor");

      _window = windowToMonitor;

      //cast them now so we dont have to cast each one when evaluting events
      uint lowest = (uint) User32.AccessibleEventType.EVENT_MAX;
      uint highest = (uint) User32.AccessibleEventType.EVENT_MIN;
      foreach (User32.AccessibleEventType eventType in eventsToMonitor)
      {
        var castType = (uint) eventType;
        _watchedEvents.Add(castType);

        //need the range of events to subscribe to
        if (castType > highest)
          highest = castType;
        if (castType < lowest)
          lowest = castType;
      }

      //sign up for the event
      dEvent = this.winEvent;
      _hook = User32.SetWinEventHook(lowest, highest, IntPtr.Zero, dEvent, 0, 0, User32.WINEVENT_OUTOFCONTEXT);

      //ensure it worked
      if (IntPtr.Zero.Equals(_hook)) throw new Win32Exception();

      //no kill callback
      GC.KeepAlive(dEvent);
    }

    private void winEvent(IntPtr hWinEventHook, uint eventType, IntPtr hWnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
      //only care about the events for the specified window
      if (hWnd != _window)
        return;

      if (_watchedEvents.Contains(eventType))
      {
        if (EventOccurred != null)
          EventOccurred(this, new AccessibleEventTypeEventArgs((User32.AccessibleEventType) eventType));
      }
    }

    public void Dispose()
    {
      //unhook the listener
      if (!IntPtr.Zero.Equals(_hook))
        User32.UnhookWinEvent(_hook);

      //clear variables
      _hook = IntPtr.Zero;
      dEvent = null;

      //kill any event listeners
      EventOccurred = null;

      GC.SuppressFinalize(this);
    }
  }

およびいくつかのwinapi(イベント定数のリストを省略しました):

public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd,
  int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

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

[DllImport("user32.dll")]
public static extern bool UnhookWinEvent(IntPtr hWinEventHook);
4

1 に答える 1

2

おそらく、キャレットのイベントが表示されます。winevent を取得したら、通常、HWNDidObject をチェックして、それが実際に関心のあるウィンドウの部分に関するイベントであることを確認する必要があります。ウィンドウ自体 (OBJID_WINDOW) か、スクロールバー、キャレット、またはその他のものなど、ウィンドウのその他の側面かどうか。

winevents をデバッグするには、Accessible Event Watcher ツールを確認してください。[モード] メニューから WinEvents をリッスンするように設定し、[モード/設定...] で必要なイベントを選択します。EVENT_OBJECT_DESTROY、および表示するイベント情報 (通常は hwnd、idObject、および idChild)。

--

ところで、コードに関する 1 つのコメント: 特定のウィンドウからのイベントのみを探している場合は、そのウィンドウのスレッド ( GetWindowThreadProcessId ) を取得し、その idThread で SetWinEventHook を使用して、代わりにその特定のスレッドからのみイベントを取得する方が効率的です。すべてのスレッドをリッスンするには 0 を渡します。これにより、Win32 がイベント ソースでフィルタリングを行うことができます。これは、デスクトップ全体のすべてのスレッドからそのタイプのイベントを取得し、必要のないイベントを無視するよりも効率的です。

于 2012-10-28T22:39:45.260 に答える