Sådan lytter du til Windows-udsendelsesbeskeder i .NET?

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg har en Object (dvs. ikke en formular), der ønsker at lytte til udsendelsesmeddelelser fra Windows, f.eks .:



  • WM\_SETTINGCHANGE

  • WM\_DWMCOLORIZATIONCOLORCHANGED

  • WM\_DWMCOMPOSITIONCHANGED

  • WM\_THEMECHANGED

  • WM\_POWERBROADCAST



Hvad er mekanismen i .NET for at opsætte et vindue uden WinForm, der kan lytte til udsendelsesmeddelelser? [33] [34] [35] [36] [37]


dvs. Er der en WindowsListener klasse?


Bonuschatter



I gamle dage, i andre udviklingsmiljøer, gav rammen en AllocateHwnd funktion: [38]


HWND listener = AllocateHWnd(ListenerWindowProc);


hvor ListenerWindowProc var min vinduet procedure metode:


private void ListenerWindowProc(ref Message msg)
{
    switch (msg.msg)
    {
       case WM\_SETTINGCHANGE: 
       {
          ...
       }
       break;
       case WM\_POWERBROADCAST:
       {
          ...
       }
       break;
       case WM\_THEMECHANGED: 
       {
          ...
       }
       break;
       ...
   }
   DefWindowProc(msg);
}


Den hemmelige sauce var funktionen AllocateHwnd:


Pseudo-kode:


public static HWND AllocateHWnd(WndMethod method)
{
   HWND result;
   WNDCLASS tempClass;
   Boolean classRegistered;

   UtilWindowClass.hInstance := HInstance;
   Boolean classRegistered = GetClassInfo(HInstance, UtilWindowClass.lpszClassName, tempClass);
   if (!classRegistered || (tempClass.lpfnWndProc != @DefWindowProc)
   {
      if classRegistered
      {
         UnregisterClass(utilWindowClass.lpszClassName, HInstance);
         RegisterClass(utilWindowClass);
      }
      result = CreateWindowEx(WS\_EX\_TOOLWINDOW, UtilWindowClass.lpszClassName,
            '', WS\_POPUP, 0, 0, 0, 0, 0, 0, HInstance, null);
      if (!Method != null)
         SetWindowLong(result, GWL\_WNDPROC, UInt32(MakeObjectInstance(Method)));
   }
}


Med en hel del mere hemmelig kode forbundet med UtilWindowClass.


Og da du var færdig, ville du DeallocateHwnd: [39]


DeallocateHwnd(listenerWindow);


Jeg kender et eller andet sted dybt i .NET-rammen, de skal reagere på WM\_SETTINGCHANGE og opdatere interne .NET-datastrukturer. Jeg ville stjæle den kode, men Reflector finder ingen henvisning til WM\_SETTINGCHANGE; formodentlig fordi den dekompilerede kode ikke viser konstant navn , bare konstanten 0x001A .


Opdater :


Bemærk : Dette objekt skal være selvstændigt. Enhver, der bruger klassen, behøver ikke at ændre deres ansøgning, så min klasse kan returnere korrekte data. Det skal lytte til udsendelser fra Windows selv og ikke kræve en udvikler at ændre deres ansøgning for at lytte efter bestemte meddelelser til mig (det vil sige ikke at bryde indkapslingen af ​​den vanskelige eller komplikationsoperation)


For eksempel:


public class FrobbingGrobber: IDisposable
{
    private IntPtr hwnd = IntPtr.Zero;

    public FrobbingGrobber
    {
       \_hwnd = AllocateHwnd(listenerWindowProc);
    }

    protected virtual void listenerWindowProc(ref Message msg)
    {
       switch (msg.msg)
       {
          case WM\_DwmColorizationColorChanged, WM\_DwmCompositionChanged: 
          {
              UpdateColorizationColor();
          }
          break;
       }
       DefWindowProc(msg);
    }

    public void UpdateColorizationColor()
    {
        NativeMethods.DwmGetColorizationColorParameters\_UndocumentedExport137(ref \_color);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected void Dispose(Boolean safeToDisposeManagedObjects)
    {
       if (\_hwnd != 0)
       {
          DeallocateHwnd(\_hwnd);      
          \_hwnd = 0;
       }

       if (safeToDisposeManagedObjects)
          GC.SuppressFinalize(this);
    }

    public ~FrobbingGrobber
    {
       //If the user forgot to call Dispose i could (correctly) leak a handle, 
       //or i could fix their mistake for them
       Dispose(false);
    }

Bedste reference


Jeg antager, at de eksempler du gav, var blot det: Eksempler. Fordi et antal af dem har formået at svare ækvivalenter, der allerede indpakker alt dette til dig. Ligesom WM\_POWERBROADCAST er indpakket af Microsoft.Win32.SystemEvents.PowerModeChanged arrangementet. Og WM\_SETTINGCHANGED svarer til Microsoft.Win32.SystemEvents.UserPreferenceChanged. [40] [41]


Alligevel sendes udsendte meddelelser som dem, du beskriver, til alle vinduer på øverste niveau, så alt du virkelig skal gøre er at oprette et vindue på øverste niveau og tilsidesætte dets WndProc metode til at behandle meddelelsesmeddelelserne, som du er interesseret i i.


Brug klassen NativeWindow for at gøre det nemt for dig selv. I dette tilfælde behøver du ikke alt, hvad der leveres af Form klassen, alt hvad du behøver, er noget, der skal pakkes CreateWindowEx og giver en vindueprocedure. Opret bare vinduet uden WS\_VISIBLE]] flag fordi du ikke vil have det vist på skærmen. [42]


.NET Framework gør dette overalt internt. For eksempel anvendes den interne TimerNativeWindow klasse til System.Windows.Forms.Timer. Hvis du vil tjekke implementeringen for dig selv i Reflector, skal du begynde at se der. Du bør være i stand til at søge efter konstanter, men at borde ned i et hierarki af klasser, som du ved der skal være sådan en meddelelsesmeddelelse, der håndteres internt, er generelt en smartere måde at søge på. Klassen SystemEvents (diskuteret ovenfor) er også et godt sted at begynde at lede efter implementeringsstrategier.


Bemærk, at du ikke kan bruge et vindue med kun besked her (HWND\_MESSAGE) fordi de ikke modtog udsendelsesbegivenheder. Den TimerNativeWindow, som jeg nævnte ovenfor gør , gør det, fordi det ikke er ligeglad med udsendelseshændelser, så skal du bare kopiere og indsætte koden derfra! [43]