Introduction
Debugging a managed Windows application is, most of the time, not an easy task, thus any tool that can help will make your life easier. Here, I'm trying to present a small tool that can help you in developing Windows applications, especially working with forms and their controls.
The main idea behind this tool is to provide an easy way to modify properties of objects at runtime without having to restart the application. The Runtime Object Editor tool provides you this and a few more:
- A properties editor like the VS editor that can be used to change the properties of any object or control at runtime.
- Shows you all the properties that are defined on an object (even if they are not normally visible in the designer) e.g.: "
Controls
".
- Shows you all the fields of an object organized by the class in the hierarchy that owns that property.
- Shows all the methods of an object organized by the class and visibility of the method.
- Provides a simple way to invoke methods on objects and pass arguments on any method (
public
, private
...).
- Shows you all the events defined on an object and all the event listeners registered to listen to a specific event (e.g.:
Form_Load
).
- Shows process information including static information about
Application
, CurrentContext
, CurrentThread
, CurrentPrincipal
, CurrentProcess
, and garbage collection.
- A simple way to find a window/control in your application.
- And an (almost) non intrusive way to integrate it into your application.
- Can be injected in any .NET process allowing you to easily hook and modify other processes. You can even hook into Visual Studio and modify some of its (.NET) properties (E.g.: the Properties Editor from VS).
- Supports back/forward navigation between the last edited objects, and supports navigation to child items in collections, enumerations or arrays (E.g.: the
Controls
collection of a Control
).
Background
Reflection
Reflection is the main way to interact with objects that you know nothing about, thus this tool makes use of reflection for all the information that it can display on its tabs. For a short description of how Reflection can be used, see [1].
PInvoke
PInvoke calls are used to detect the window from the point where the mouse is located. A good article about how to detect a window from a point (mouse location) can be found here: [2].
Modules
The Runtime Object Editor has three major components:
- WindowFinder - Provides a drag and drop pointer that you can use to select your window. The implementation is quite similar with [2], with the major difference that it's a C# implementation and that I can also correctly detect the hidden/disabled windows when trying to select a control.
- XPropertyGrid and its tabs - A
PropertyGrid
derived class where you can see and edit the properties of your object.
- Descriptors/Designers and TypeConverters - These classes will describe the "properties" of a method like: parameters, type converters, return type, and will do the real invoke of the method.
- The assembly injector helper DLL and classes that allow the editor to attach to any running .NET process and modify its properties.
WindowFinder
A drag and drop component is provided for selecting the target window that you want to see in the object editor.
Its behavior is quite simple: click the finder icon, capture the mouse, whenever the mouse is moved, detect the window under the mouse pointer, and fire an event notifying that a new window was found. The only noticeable code in the window finder is the WindowFinder_MouseMove
method that detects the window from the mouse location.
private void WindowFinder_MouseMove(object sender, MouseEventArgs e)
{
if ( !searching )
EndSearch();
POINT windowPoint = POINT.FromPoint( this.PointToScreen(new Point(e.X,e.Y) ));
IntPtr found = WindowFromPoint ( windowPoint );
if ( found != IntPtr.Zero )
{
if( ScreenToClient ( found, ref windowPoint ) )
{
IntPtr childWindow = ChildWindowFromPoint(found, windowPoint);
if ( childWindow != IntPtr.Zero )
{
found = childWindow;
}
}
}
if( found != window.DetectedWindow )
{
window.SetWindowHandle ( found );
Trace.WriteLine ( "FoundWindow:" + window.Name + ":" +
window.Text + " Managed:" + window.IsManaged );
InvokeActiveWindowChanged ();
}
}
XPropertyGrid and the tabs
XPropertyGrid
inherits from the PropertyGrid
in order to be able to add our own tabs to the PropertyGrid
.
All tabs inherit from System.Windows.Forms.Design.PropertyTab
and provide a list of properties in public override PropertyDescriptorCollection GetProperties(object component, Attribute[] attributes)
. The list of methods are obtained as PropertyDescriptor[]
objects. From this point on, you have no more direct control over what we display, and we have to rely completely on overriding the methods from the objects used for describing other objects (PropertyDescriptor
, TypeConverter
, ComponentDesigner
, UITypeEditor
).
The custom tabs are:
- Properties
- AllFields
- AllProperties
- Events
- Methods
- Process Information
Properties tab
The Properties tab simply emulates the Properties tab we normally see in Visual Studio. This Properties tab will show the properties of the object based on the attributes they have set. Some properties that are normally visible at design time only, might not be visible here. Also, properties that are marked with DesignerSerializationVisibily(Hidden)
or Browsable(false)
will not be visible in this tab.
AllFields tab
The AllFields tab will show you all the fields defined in each class from the selected object's hierarchy. The fields can be modified (unless marked as read-only).
AllProperties tab
Contrary to the Properties tab that filters properties based on some of the attributes applied and does not show non-public properties, the AllProperties tab will display all the properties defined on the objects, disregarding the attributes or visibility of the properties. The AllProperites tab also allows hierarchical navigation inside the collections by allowing you to expand them and select items you want to modify.
AllEvents tab
The Events tab will display all the events defined on an object and give you the option to view all the event listeners attached to each event, including the objects that own the event handler and the function name that will handle the event. Because events can be implemented using the public event { add; remove; } pattern, and there are no specific guidelines about how this pattern is to be implemented, it is possible that the Events tab will not be able to correctly read the listeners registered for some of the events.
Methods tab
Describes how a method is "to be seen" in the grid.
A list of such objects is generated by the MethodsTab.GetProperties
and passed on to the grid. The grid will interrogate the descriptor for its value, the editor, and the child objects. We will return as the value of a method, the result from the last invocation. As child objects, we publish the list of parameters the method has, thus we can open the method declaration and fill in the typed parameters.
Whenever we change the value of a parameter, the MethodPropertyDescriptor
will invoke the method for us and will give us the return value.
ParameterPropertyDescriptor
When the grid editor requests the list of child properties of the MethodPropertyDescriptor
, we return an array containing a ParameterPropertyDescriptor
to describe each parameter of the method, and a ReturnParamterDescriptor
to describe and show the returned value of the last invoke of the method.
Usage
The RuntimeObjectEditor
can be integrated in your application to be used for debugging purposes or can be injected into any running .NET process (including Visual Studio if you want to be convinced that Visual Studio contains .NET code).
Injecting RuntimeObjectEditor into other processes:
- Start the RuntimeObjectEditorLoader.exe (the loader that can inject the editor into other processes).
- Start any .NET application (.NET 1.1 or .NET 2.0).
- Drag and drop the WindowFinder on any window from the newly started .NET process.
- The title of the editor will change to "target in different process. release selection to hook".
- Once you release the mouse after the drag and drop operation, the RuntimeObjectEditor will hook into the selected process and restart inside the new process.
- Start playing with the objects from the new process.
Note:
- Some .NET processes use unmanaged windows that can not be hooked by the RuntimeObjectEditor (e.g.: you can not select a button from a
MessageBox
or a web browser window hosted in another process).
- If you open a modal dialog and you can not get access to the RuntimeObjectEditor anymore, just press Control+Shift+R to restart the RuntimeObjectEditor on top of the modal window you have active.
- Once loaded in a process, the RuntimeObjectEditor can "jump" into another process if you drag the window finder to a window in another process.
- If you want to hook into Visual Studio, you can start by selecting the Properties window of Visual Studio or any form open in Design mode.
- Changes done to forms that are visible in Design mode inside Visual Studio do get applied if you switch to a code window or save the form.
Integrating RuntimeObjectEditor into your application
- Add a reference to RuntimeObjectEditor.dll.
- Write
RuntimeObjectEditor.ObjectEditor.Instance.Enable();
in your static void Main
just before your Application.Run
code. This will enable the ObjectEditor
and will register the hotkey. The default hotkey used is "Control+Shift+R". If you want to change the default hotkey, just set RuntimeObjectEditor.ObjectEditor.Instance.HotKey
to whatever key combination you want. The property supports combinations like: Control|Alt|Shift|+key (e.g.: Control+Shift+Alt+F1).
- When running your application, press the hot key combinations you registered and start playing with the editor.
Future improvements
- Provide better invoke options (invoke on request).
- Be able to hook/log any event, and maybe break into debugger.
- Make a log with all the changes you apply to a window so you can then copy/paste them into code.
- Integrate with Lutz Roeder's .NET Reflector.
References
History
- You can always find the latest version at: Runtime Object Editor's Blog.
- 29 May 2006 v1.1.4 - Second major release:
- Show/Edit fields.
- Show/Edit all properties.
- Navigate collections.
- Show events and registered event listeners.
- Show process information.
- Navigate back/forward between objects.
- Inject into running .NET processes.
- 27 Feb 2005 v1.0.1 - First version:
- Show/Edit public properties normally visible in the Designer.
- Invoke methods on objects.
- Code integration in other applications.
Acknowledgements
I have to thank Rama Krishna Vavilala for his article A simple Windows Forms properties spy, from where I got the process injection code and integrated it into the RuntimeObjectEditor.