I'm interested in the architecture of the Winforms. One of the things that I'm trying to understand is the event handling. On the one side we have a bunch of protected On-methods (OnPaint, OnMouseDown, etc) in the Control class. On the other side we have a WndProc function. But what is in the middle? Who actually calls these protected methods?
The majority start out as a Windows message, either posted to the message queue with PostMessage (like WM_KEYDOWN => OnKeyDown, WM_LBUTTONDOWN => OnMouseDown) or sent directly to the window procedure with SendMessage (like WM_ACTIVATE => OnActivate, WM_SHOWWINDOW => OnLoad).
These messages are dispatched when the Application.Run() message loop calls GetMessage() and processed by Control.WndProc(). Which basically contains a large switch-statement on Message.Msg. A class derived from Control has its own WndProc() override to handle messages that are specific to the native Windows control they wrap. Like TreeView, TVN_SELCHANGING => OnBeforeSelect.
Then there are some that are synthesized by Winforms itself. From your code assigning a property like AutoSize => OnAutoSizeChanged, BackColor => OnBackColorChanged, Parent => OnParentChanged. The kind of properties that can have side effects on other controls.
And a few that are synthetic from state that Winforms builds on top of normal Windows messages, OnEnter, OnLeave, OnValidating, OnLayout fit that category.
There's a great deal of code involved with this, but this is the rough outline of it.
Related
I am trying to create a subclass of VScroll control and intercepting the WM_VSCROLL message to do some stuff. On msdn, and countless articles/forums, WM_VSCROLL is supposed to be 0x115, even in the windows header file that's what it is. However waiting on 0x115 doesn't bring anything. I realized it's coming through as 0x2115 instead, even this article is using it as 0x2115. Any reason/explanation as to why it's coming as 0x2115 instead? Is it because it's a WinForms control?
Thanks
Yes, this is pretty standard in Windows GUI class libraries, Winforms is no exception. The native Windows control, ScrollBar in your case, send notifications to their parent. After all, they were designed to make their parent act on the notification, not themselves. Or in other words, you expect the parent window to scroll.
That however is not very compatible with the notion of a control class whose behavior you can modify by overriding its message handling and generation. Or for that matter the idea of events in .NET, anybody can subscribe to the Scroll event, not just the parent.
That buys a lot of goodness, but something must be done to get the WM_VSCROLL message from the parent back to the control. Which is what the Winforms plumbing takes care of. It sends the message back but alters the message number to indicate that it was a reflected message, not the original. It adds 0x2000. The value of the (fake) WM_REFLECT message in the Winforms source code.
i recently acquired some source code for a console wrapper for a server. The program was originaly in WPF and part of the code was:
private void ServerProc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Dispatcher.Invoke(new Action(() =>
{
ConsoleTextBlock.Text += e.Data + "\r\n";
ConsoleScroll.ScrollToEnd();
}));
}
private void ServerProc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Dispatcher.Invoke(new Action(() =>
{
ConsoleTextBlock.Text += e.Data + "\r\n";
ConsoleScroll.ScrollToEnd();
ParseServerInput(e.Data);
}));
}
Its also had this annotation in both voids:
// You have to do this through the Dispatcher because this method is
called by a different Thread
However in WinForms there is no such thing - is there a way to change this to a Background worker or something (Ive barely done any multi-threading)?
Both methods are event handlers. Chances are they are from some kind of listening code and I would expect that they are called from a non UI thread (eg normally a threadpool thread that is doing the listening). You can check that by putting a break point and looking at the threads window in the debugger.
So you will need to apply the winforms way of updating the UI from a non UI thread.
If you search SO you should find quite a lot on how to do that. E.g
Updating UI from a different thread
How to update GUI from another thread in C#?
Some background: A process that is running in a thread other than the UI thread is not allowed to access any UI controls directly. Your WPF ServerProc is running in a different thread than your UI which requires the Dispatcher to help you communicate from the ServerProc thread back to the UI controls in your UI thread.
If your ServerProc -- in WPF or WinForms -- were running in the UI thread, you would not need to surround it with the Dispatcher.Invoke call.
For you, you can put your ServerProc in a BackgroundWorker (MSDN example). Your DoWork method would contain the meat of the code doing the work and then depending on how the ServerProc does its work, you might be able to use ProgressChanged to do what both your sample WPF methods are doing. ProgressChanged has a parameter passed in that you would indicate if it were an error or data has been received and inside the function you could display the appropriate info. Take a look at the MSDN docs because they have a good example.
What's important to point out is that ProgressChanged happens on the UI thread so you do NOT need to surround your calls to your UI controls with Invoke; just call them normally. Same goes for RunWorkerCompleted which may be the other option for displaying data when your ServerProc has finished its job.
Finally, if you actually had to access a UI control from within your thread, you do something very similar looking as your WPF code sample. Look at MethodInvoker. Instead of Dispatcher, you're really just calling it from your main Form.
Is there documentation on the paint cycle in WinForms?
When i am programming in Windows the paint cycle is usually of the form:
sent a WM_PAINT message
{
call BeginPaint(&paintStruct)
//BeginPaint sends WM_NCPAINT and WM_ERASEBKGND
sent a WM_ERASEBKGND message
{
i can:
- allow default processing (Windows will fill the area with the default background color (e.g. white)
- erase and background myself (e.g. a gradient) and prevent default processing
- do nothing (letting whatever was there before me stay there) and prevent default processing
}
perform whatever painting i desire on
paintStruct.hDC (Device Context)
paintStruct.rcPaint (Invalid Rectangle)
that was populated into paintStruct during BeginPaint
call EndPaint()
}
This is all documented on MSDN: Windows Development\Graphics and Multimedia\Windows GDI\Painting and Drawing\About Painting and Drawing
i cannot find any such documentation about WinForms and its paint cycle. i can randomly find methods and events that have the name paint in them:
OnPaint (protected method "Raises the Paint event.")
OnPrint (protected method "Raises the Paint event.")
InvokePaint (protected method "Raises the Paint event for the specified control.")
Paint (public event)
InvokePaintBackground (protected method "Raises the PaintBackground event for the specified control.")
OnPaintBackground (protected method "Paints the background of the control.")
Note: Ignoring the fact that there is no PaintBackground event
Is there documentation describing the design relationship between these entities? Is there documentation on the paint cycle in WinForms?
It isn't substantially different from the native Windows paint cycle, the .NET events are raised by the corresponding Windows messages. Starting from the bottom, the messages are generated by a call to InvalidateRect(), either by the window manager or by the app itself. The .NET version is Control.Invalidate(). Windows keeps track of the update region for the window, deciding whether to deliver a WM_PAINT, WM_NCPAINT and WM_ERASEBKGND message.
The WM_PAINT and WM_ERASEBKGND messages are recognized by Control.WndProc() when the ControlStyles.UserPaint style is turned on. It calls the virtual OnPaint() and OnPaintBackground() methods. A derived control can override these methods to customize the painting as necessary. And must call the base method. Eventually that reaches the Control.OnPaint/Background method, that fires the Paint and PaintBackground events to allow other code to customize the painting.
The only other wrinkle is double-buffering, enabled by the DoubleBuffered property. Winforms creates a bitmap buffer for the control and runs OnPaintBackground() and OnPaint(), passing a Graphics object created from that bitmap. Then blits the bitmap to the screen.
Is this what you are looking for?
MSDN: Custom Control Painting and Rendering
OP Edit: For when Microsoft implements their next round of link breaking, the documentation's location is:
MSDN Library
Development Tools and Languages
Visual Studio 2010
Visual Studio
Creating Windows-Based Applications
Windows Forms
Getting Started with Windows Forms
Windows Forms Controls
Development Custom Windows Forms Controls with the .NET Framework
Custom Control Painting and Rendering
Scenario:
The VB 6 form has a InteropControl (WinForms).
The InteropControl has a ElementHost
The ElementHost has my WPF control
Everything seems to be working except that Application.Current seems to be null when I need it. All I really want to do is hook into the unhandled exception event before the first form is fully displayed.
In this scenario is a WPF Application object ever created?
If so, when it is created?
If not, what causes messages to be pumped?
What would happen if I started the Application object on a background thread?
First I will explain how message loops work in interop scenarios, then I will answer your questions and give a few recommendations.
Message loop implementations in your scenario
Your scenario involves three separate technologies: VB 6, WinForms, and WPF. Each of these technologies is implemented on top of Win32. Each has its own GetMessage()/DispatchMessage() loop to pump Win32 window messages.
Here is where each GetMessage()/DispatchMessage() loop is implemented:
VB 6 implements it internally
WinForms implements it in System.Windows.Forms.Application
WPF implements it in System.Windows.Threading.Dispatcher
WPF Application object is optional
Your question assumes that WPF implements the message loop in the Application object. This is not the case. In WPF, all essential functions that WinForms handled in the Application object have been moved to other objects such as Dispatcher, HwndSource, InputManager, KeyboardDevice, MouseDevice, etc.
In WPF the Application object is completely optional. You can construct a complete WPF application with a complex UI without ever creating an Application object. An application object is only useful if you need one of the services it provides, for example:
A common ResourceDictionary
Mapping WM_APPACTIVATE message into Activated and Deactivated events
Mapping WM_QUERYENDSESSION message into OnSessionEnding event
Lifecycle management (Startup/Run/Shutdown/Exit)
Automatic shutdown when the last window or main window closes
Default icon for WPF windows
Remembering the first window opened (MainWindow)
Common registration for NavigationService events (Navigated, etc)
StartupUri
The Application class also provides several useful static members such as FindResource, GetResourceStream and LoadComponent that don't require an Application object to exist.
When you call Application.Run(), all it does is:
Install the mechanism to handle WM_APPACTIVATE and WM_QUERYENDSESSION, and
Execute Dispatcher.Run()
All of the actual message loop functionality is in Dispatcher.Run().
Registering for unhandled exceptions in WPF message loop
The Application.DispatcherUnhandledException event you were trying to use is a simple wrapper around the Dispatcher.UnhandledException event. I think they included it in the Application object because WinForms programmers expected it to be there, but your question shows that this may have backfired.
To register for unhandled exceptions from WPF's Dispatcher, all you have to do is:
Dispatcher.Current.UnhandledException += ...;
Unlike Application.Current, Dispatcher.Current cannot be null: If you access Dispatcher.Current from a thread that doesn't yet have a Dispatcher, one will be created automatically.
Once you have subscribed to Dispatcher.UnhandledException, any unhandled exception from a Dispatcher message loop on the current thread will cause your event handler to be called. Note that this only applies to unhandled exceptions when Dispatcher.Run() is pumping messages: When another technology such as VB 6 or WinForms is pumping messages, that technology's exception handling mechanism will be used instead.
WPF message loop also optional
Not only can WPF run without creating an Application object, it can also function without Dispatcher.Run(), as long as another technology is pumping Win32 window messages. This is done by creating a dummy window and/or subclassing a WPF window to install a message hook. Thus no matter what message loop is pumping messages, WPF will work as expected.
In fact, when you use ElementHost, the WPF Dispatcher is not used for message pumping unless you use one of the following methods:
Window.ShowDialog
Dispatcher.Invoke
Dispatcher.Run (or equivalently, Application.Run)
DispatcherOperation.Wait
Because of this, your WPF exception handler will probably not be called. Instead you will need to install your exception handler at the VB 6 or WinForms level.
Answers to your questions
In this scenario is a WPF Application object ever created?
No.
If not, what causes messages to be pumped?
VB 6 is pumping the messages.
What would happen if I started the Application object on a background thread?
Very little:
If you have application resources these would be created on the background thread, possibly leading to exceptions when they are used on the main thread.
If you add a handler to Application.Current.DispatcherUnhandledException it would only apply to the background thread. In other words, the handler will never be called unless you create windows on the background thread.
Your Application.Startup will be called from the background thread, which is probably a bad thing. Ditto for StartupUri.
Recommendation
From what you are asking it sounds like you are getting an unhandled exception during the loading of your WPF control and you want to catch that exception. In this case, the best plan is probably to wrap your WPF control inside a simple ContentControl whose constructor uses code like this to construct the child:
Dispatcher.Current.UnhandledException += handler;
Disptacher.Current.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
{
Content = CreateChildControl();
Dispatcher.Current.Invoke(DispatcherPriority.ApplicationIdle, new Action(() => {});
});
How it works: The BeginInvoke delays construction of the child until VB 6 and/or InteropControl have completed all processing. The Invoke call after the child control is created invokes an empty action at low priority, causing all pending DispatcherOperations to complete.
The net result is that any exceptions that were thrown within or just after the constructor are now passed to your exception handler.
In WPF, the Application object is not directly responsible for the message pump, it is the Dispatcher. When you start a WPF app, Application.Run() is called at startup, which calls Dispatcher.Run().
In your interop scenario, Application.Current will return null since it is never created. Message pumping is handled by VB, since it creates the main window. If you rely on it in your code you can either:
Create a new Application object:
if (Application.Current != null)
{
new Application();
}
Application is a singleton, so it will be automatically stored in Application.Current.
Avoid relying on it whenever possible (which I think is the recommended way). You should note that many of the services this class provides (e.g. the Exit event) will not be available in your scenario anyhow. If all you need is the unhandled exception event, you can use Dispatcher.CurrentDispatcher.UnhandledException.
I found some of my winform application controls, such as DataGridView and ToolStrips, are referred to by UserPreferenceChangedEventHandlers. I have no idea what setting of the controls will generate such references and why such references keep my control alive in memory. How can I remove such references from that event? Thanks.
It is the delegate type for the SystemEvents.UserPreferenceChanged event. This event fires when Windows broadcasts the WM_SETTINGCHANGE message. Which typically happens when the user uses a control panel applet and changes a system setting.
Several controls register an event handler for this event, DataGridView, DateTimePicker, MonthCalendar, ProgressBar, PropertyGrid, RichTextBox, ToolStrip, NumericUpDown. They typically are interested in font or cue changes and anything that would affect the layout.
SystemEvents.UserPreferenceChanged is a static event. Registering a handler and forgetting to unregister it causes a memory leak, it prevents the control from being garbage collected. The listed controls ensure this doesn't happen, they unregister the event handler in either the OnHandleDestroyed() or the Dispose() method.
You'll get in trouble when neither of those two methods run. That will happen when you remove the control from the container's Controls collection and forget to Dispose() it. While forgetting to call Dispose() is not normally a problem, it is a hard requirement for controls. It is easy to forget too, controls are normally automatically disposed by the Form. But that only happens for controls in the Controls collection.
Also be sure to call Dispose() on forms that you display with the ShowDialog() method, after you obtained the dialog results. The using statement is the best way to handle that.
One more excruciating detail is important about the UserPreferenceChanged event, it is often the one that deadlocks your app when you create controls on a worker thread. Typically when the workstation is locked (press Win+L). Which cannot come to a good end when you use the controls I listed, the SystemEvents class tries to raise the event on the UI thread but of course cannot do this correctly when more than one thread has created them.
Also the kind of bug that can have a lasting effect, a splash screen for example can get the SystemEvents class to guess wrong about which thread is your UI thread. After which it then permanently raises the event on the wrong thread. Very ugly to diagnose, the deadlock is well hidden.