Process Windows Messages in your WPF application
I have some code that attaches and injects code into a target application (like Visual Studio or IE) to examine its memory use. In order to do that, my code freezes the target.
I wanted my application to respond to WM_QUERYENDSESSION so that when the user is shutting down, it unfreezes the target, so that the Force Shutdown dialog doesn’t show.
The #define constants for the WM_xxx messages can be found In the file c:\program files (x86)\microsoft sdks\windows\v7.0a\include\winuser.rh
For example,
#define WM_QUERYENDSESSION 0x0011
#define WM_QUERYOPEN 0x0013
#define WM_ENDSESSION 0x0016
You can multi-edit these 3 lines: Cut and paste those into the Visual Studio Editor.
Put the cursor at the beginning of the first “#”, then hold the Alt-Shift down while you hit the down arrow 3 times. This causes a column selection of zero width. You can also move right to make the column width non-zero. Then you can type text which will replace the current selection, like “Private Const”. You won’t get intellisense, but you will change multiple lines easily. This trick comes in handy when you want to select columns of text for copy/paste too.
The code below adds a Window Procedure to be hooked into the normal processing chain. The code just shows the window messages as they occur in the window, along with a timestamp and parameters.
Start VS 2010
File->New->Project->VB->WPF app
Replace the MainWindow.Xaml.Vb code with the code below.
Try moving the mouse, typing keys, resizing, moving, etc.
Try starting Task Manager (Ctrl-Shift-Esc) and note the windows messages that are logged. What’s happening? See How does Task Manager determine if an Application is Not Responding?
<Code>
Imports System.Windows.Interop
Class MainWindow
'c:\program files (x86)\microsoft sdks\windows\v7.0a\include\winuser.rh
Enum Msgs
WM_MOVE = &H3
WM_SIZE = &H5
WM_ACTIVATE = &H6
WM_SETFOCUS = &H7
WM_KILLFOCUS = &H8
WM_ENABLE = &HA
WM_SETREDRAW = &HB
WM_SETTEXT = &HC
WM_GETTEXT = &HD
WM_GETTEXTLENGTH = &HE
WM_PAINT = &HF
WM_CLOSE = &H10
WM_QUERYENDSESSION = &H11
WM_QUERYOPEN = &H13
WM_ENDSESSION = &H16
WM_QUIT = &H12
WM_ERASEBKGND = &H14
WM_SYSCOLORCHANGE = &H15
WM_SHOWWINDOW = &H18
WM_WININICHANGE = &H1A
WM_ACTIVATEAPP = &H1C
WM_SETCURSOR = &H20
WM_GETMINMAXINFO = &H24
WM_WINDOWPOSCHANGING = &H46
WM_WINDOWPOSCHANGED = &H47
WM_CONTEXTMENU = &H7B
WM_STYLECHANGING = &H7C
WM_STYLECHANGED = &H7D
WM_DISPLAYCHANGE = &H7E
WM_GETICON = &H7F
WM_SETICON = &H80
WM_NCCALCSIZE = &H83
WM_NCHITTEST = &H84
WM_NCPAINT = &H85
WM_NCACTIVATE = &H86
WM_NCMOUSEMOVE = &HA0
WM_NCLBUTTONDOWN = &HA1
WM_NCLBUTTONUP = &HA2
WM_NCLBUTTONDBLCLK = &HA3
WM_NCRBUTTONDOWN = &HA4
WM_NCRBUTTONUP = &HA5
WM_NCRBUTTONDBLCLK = &HA6
WM_NCMBUTTONDOWN = &HA7
WM_NCMBUTTONUP = &HA8
WM_NCMBUTTONDBLCLK = &HA9
WM_KEYDOWN = &H100
WM_KEYUP = &H101
WM_CHAR = &H102
WM_DEADCHAR = &H103
WM_SYSKEYDOWN = &H104
WM_SYSKEYUP = &H105
WM_SYSCHAR = &H106
WM_SYSDEADCHAR = &H107
WM_MOUSEMOVE = &H200
WM_LBUTTONDOWN = &H201
WM_LBUTTONUP = &H202
WM_LBUTTONDBLCLK = &H203
WM_RBUTTONDOWN = &H204
WM_RBUTTONUP = &H205
WM_RBUTTONDBLCLK = &H206
WM_MBUTTONDOWN = &H207
WM_MBUTTONUP = &H208
WM_MBUTTONDBLCLK = &H209
WM_SIZING = &H214
WM_CAPTURECHANGED = &H215
WM_MOVING = &H216
WM_ENTERSIZEMOVE = &H231
WM_EXITSIZEMOVE = &H232
WM_IME_SETCONTEXT = &H281
WM_IME_NOTIFY = &H282
WM_NCMOUSEHOVER = &H2A0
WM_MOUSEHOVER = &H2A1
WM_NCMOUSELEAVE = &H2A2
WM_MOUSELEAVE = &H2A3
End Enum
Private Class txtStatus
Inherits TextBox
Sub New()
' MaxLines = 25
AcceptsReturn = True
AcceptsTab = True
IsReadOnly = True
FontFamily = New FontFamily("Courier New") ' monospace
VerticalScrollBarVisibility = ScrollBarVisibility.Auto
Text = String.Empty
End Sub
Sub AddStat(ByVal str As String)
str = String.Format("{0} {1}", DateTime.Now.ToLongTimeString, str)
AppendText(str + vbCrLf)
CaretIndex = Text.Length
ScrollToEnd()
' force background rendering thread to render even though we might be at full CPU
Me.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, Function() Nothing)
' write to log file, so we can see even in logoff/shutdown case
System.IO.File.AppendAllText("c:\t.log", str + vbCrLf)
End Sub
End Class
Private _txtStatus As New txtStatus
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Try
Dim lamWndProc = Function(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handleed As Boolean) As IntPtr
Dim retVal = IntPtr.Zero
Try
Select Case msg
Case Msgs.WM_ACTIVATEAPP, Msgs.WM_GETICON, Msgs.WM_ENDSESSION, Msgs.WM_QUERYENDSESSION
End Select
Dim strMsg = CType(msg, Msgs).ToString
If Char.IsDigit(strMsg) Then
strMsg = msg.ToString("x8") ' convert to hex
End If
_txtStatus.AddStat(String.Format("{0,-20} {1:x8} {2:x8}", strMsg, wParam.ToInt32, lParam.ToInt32))
Catch ex As Exception
End Try
Return retVal
End Function
Dim hwndSrc = CType(HwndSource.FromVisual(Me), HwndSource)
hwndSrc.AddHook(lamWndProc)
Me.Content = _txtStatus
AddHandler Me.Closing, Sub()
hwndSrc.RemoveHook(lamWndProc)
End Sub
Catch ex As Exception
Me.Content = ex.ToString
End Try
End Sub
End Class
</Code>