Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Runtime Object Editor

0.00/5 (No votes)
30 May 2006 4  
A powerful window/object editor to be used at runtime that allows viewing/changing of properties and fields, method invocations, and object hierarchy navigation.

RuntimeObjectEditor.jpg

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:

  1. A properties editor like the VS editor that can be used to change the properties of any object or control at runtime.
  2. Shows you all the properties that are defined on an object (even if they are not normally visible in the designer) e.g.: "Controls".
  3. Shows you all the fields of an object organized by the class in the hierarchy that owns that property.
  4. Shows all the methods of an object organized by the class and visibility of the method.
  5. Provides a simple way to invoke methods on objects and pass arguments on any method (public, private ...).
  6. 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).
  7. Shows process information including static information about Application, CurrentContext, CurrentThread, CurrentPrincipal, CurrentProcess, and garbage collection.
  8. A simple way to find a window/control in your application.
  9. And an (almost) non intrusive way to integrate it into your application.
  10. 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).
  11. 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:

  1. 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.
  2. XPropertyGrid and its tabs - A PropertyGrid derived class where you can see and edit the properties of your object.
  3. 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.
  4. 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();

    // Grab the window from the screen location of the mouse.

    POINT windowPoint = POINT.FromPoint( this.PointToScreen(new Point(e.X,e.Y) ));
    IntPtr found = WindowFromPoint ( windowPoint );
    
    // we have a valid window handle

    if ( found != IntPtr.Zero )
    {    
        // give it another try, it might be a child

        // window (disabled, hidden .. something else)

        // offset the point to be a client point of the active window

        if( ScreenToClient ( found, ref windowPoint ) )
        {
            // check if there is some hidden/disabled

            // child window at this point

            IntPtr childWindow = ChildWindowFromPoint(found, windowPoint);
            if ( childWindow != IntPtr.Zero )
            {    // great, we have the inner child

                found = childWindow;
            }
        }
    }

    // Is this the same window as the last detected one?

    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.

The Tabs of the Runtime Object Editor

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:

  1. Properties
  2. AllFields
  3. AllProperties
  4. Events
  5. Methods
  6. 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

MethodPropertyDescriptor

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:

  1. Start the RuntimeObjectEditorLoader.exe (the loader that can inject the editor into other processes).
  2. Start any .NET application (.NET 1.1 or .NET 2.0).
  3. 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".
  4. 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.
  5. 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

  1. Add a reference to RuntimeObjectEditor.dll.
  2. 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).
  3. 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here