Wednesday, November 19, 2008

Using WebBrowser To Edit Web Pages On The Fly

The WebBrowser control is more versatile than you might think it is. It can be used to alter the look and feel of specific pages, click buttons, and add text to input boxes. Here's a good example.

1. Create a new Windows Form and add a WebBrowser control to it.
2. Create and attach an event handler to the DocumentCompleted event of the WebBrowser control. Make the event handler look like this.


bool _finished = false;

 

void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)

{

    if (!_finished)

    {

        HtmlElement qInput = webBrowser1.Document.GetElementsByTagName("INPUT").OfType<HtmlElement>().Where(el => el.Name == "q").FirstOrDefault();

        if (qInput != null)

        {

            qInput.SetAttribute("value", "Hello World!");

        }

 

        HtmlElement qButtonSearch = webBrowser1.Document.GetElementsByTagName("INPUT").OfType<HtmlElement>().Where(el => el.Name == "btnG").FirstOrDefault();

        if (qButtonSearch != null)

        {

            qButtonSearch.InvokeMember("click");

        }

        _finished = true;

    }

}



Now run the application. The result I received was this:



Let's walk through the code briefly:

The key lines in here are within the if statement that wraps the entire method code. First, I fetch all the elements with the tag name of INPUT, and I select only the tag named "q", which is the name of the element on the web page. The SetAttribute method will allow you to set the DOM attribute of any element in the HTML page. Knowing this, I can simply set the "value" attribute of the input to have the effect of filling in the textbox. In my example, I fill the textbox with the text "Hello World!".

The next small block uses the same general concept to find the Search button on the page, and then it calls "InvokeMember" to invoke the event attached to the control. In this situation above, I want to invoke the "click" event, so I pass the "click" string into the InvokeMember method, which kicks off the search, and ultimately redirects me to another page.

A brief warning:

Not all pages load all of their HTML at once. Some pages load in stages. This can have the effect of making some of your elements unavailable at DocumentCompleted. It may be necessary in this situation this to have some kind of Timer whose tick event kicks off the HTML actions you want to perform. Then, you can wrap all the actions in a try/catch, and stop the timer once the actions execute successfully.

This same general concept can also be used to set style on elements (try setting Style to "DISPLAY:none;" on an element, and see what happens.)

Friday, November 7, 2008

C# 4.0 Features Announced!

So, I'm a little behind the eight-ball on this one (this was released about two weeks ago), but the C# 4.0 team has released a whitepaper detailing the planned features for C# 4.0 including the new "dynamic" type, optional parameters, variance for suitable interfaces and types, as well as the new "in" and "out" modifiers for generic type parameters, allowing for covariance and contravariance. They've posted this information here!

Give it a look!

Wednesday, November 5, 2008

An Event Monitor Class



I recently received a question on the forums regarding the creation of an anonymous event handler method. Pretty much every event in the .NET Framework employs the same signature, with slight variations. According to the EventHandler page on MSDN, the standard signature for an event handler is a method that returns void, takes object as the first parameter, and takes a type that has been derived from EventArgs as it's second parameter. Microsoft has adhered to this rule with excellent consistency. Because of this, the ability to attach to every single event within a specific class is not too terribly difficult to do.

Nevertheless, I've found that at times, I've wanted to know which event is actually being raised. Perhaps I want to handle all the events in a single method, but I want to know for certain the name of the event raised on the object to which I'm subscribing. This is what I've created below.

First, I created an AnonymousEventArgs class, which itself derives from EventArgs, and contains two properties: EventName, which carries the name of the event raised, and InnerEventArgs, which contains the original EventArgs value that was created when the event was raised. It looks like this:


public class AnonymousEventArgs : EventArgs
{
public AnonymousEventArgs(string eventName, EventArgs innerEventArgs)
{
EventName = eventName;
InnerEventArgs = innerEventArgs;
}

public EventArgs InnerEventArgs { get; private set; }

public string EventName { get; private set; }
}
Next, I created a single class called "EventMonitor". EventMonitor has, at it's heart, a class called "AnonymousEventHandlerContainer" This class is designed as a generic class, with a type parameter that must inherit from EventArgs. This type parameter is used as the second parameter of the "Handler" method, which is the event handler for the specific event it is subscribed to. Each event on the object passed in as the sender in the EventMonitor will have one AnonymousEventHandlerContainer created for it, and that object will subscribe to only one event per instance. It is a nested class, and it looks like this:


public class AnonymousEventHandlerContainer<eventargs> where eventargs : EventArgs
{
// This is the name of the event subscribed to.
public string EventName { get; private set; }

// This references the parent EventHandler.
public EventMonitor Parent { get; private set; }

public AnonymousEventHandlerContainer(EventMonitor parent, string eventName)
{
EventName = eventName;
Parent = parent;
}

// This the generic handler that is used to subscribe to the object.
public void Handler(object sender, eventargs e)
{
if (Parent != null && Parent.Event != null)
Parent.Event(sender, new AnonymousEventArgs(EventName, e));
}
}
The EventMonitor is constructed by passing in the object whose methods you wish to subscribe to. It contains four methods, two of which subscribe or unsubscribe to specific events and two that subscribe to all the events on the object.

In addition to this, EventMonitor contains one event of type EventHandler<AnonymousEventArgs>, which is used for the creating class to subscribe to all events in the passed in object. The entire class looks like this:

public class EventMonitor
{
public EventMonitor(object sender)
{
Sender = sender;
}

public event EventHandler<AnonymousEventArgs> Event;

public object Sender { get; private set; }

private Dictionary<string, Delegate> eventHandlers = new Dictionary<string, Delegate>();

private int EventCount
{
get
{
return eventHandlers.Count;
}
}

/// <summary>
/// Subscribes to every event on the Sender.
/// </summary>
public void AttachAllEvents()
{
Sender.GetType().GetEvents().ToList().ForEach(e => AttachEvent(e.Name));
}

/// <summary>
/// Unsubscribes from every event on the Sender.
/// </summary>
public void DetachAllEvents()
{
Sender.GetType().GetEvents().ToList().ForEach(e => DetachEvent(e.Name));
}

/// <summary>
/// Stops monitoring a specific event.
/// </summary>
/// <param name="eventName">The name of the event to stop subscribing to. </param>
public void DetachEvent(string eventName)
{
if (eventHandlers.ContainsKey(eventName))
{
// Get the event information for the specified event.
EventInfo eventInfo = Sender.GetType().GetEvent(eventName);

if (eventInfo != null)
{
// Unsubscribe from the event.
eventInfo.RemoveEventHandler(Sender, eventHandlers[eventName]);

// Remove the handler method from the list of handler methods.
eventHandlers.Remove(eventName);

}
}
}

/// <summary>
/// Adds a generic event handler to any event whose type subscribes to the standard
/// event handler signature. (First parameter: object, second parameter inherits
/// from EventArgs).
/// </summary>
/// <param name="eventName">The name of the event to subscribe to.</param>
public void AttachEvent(string eventName)
{
// Don't subscribe twice.
if (!eventHandlers.ContainsKey(eventName))
{

// Get the event information for the specified event.
EventInfo eventInfo = Sender.GetType().GetEvent(eventName);

if (eventInfo != null)
{

// Get the handler type for the specified event.
Type handlerType = eventInfo.EventHandlerType;

// Get the ParameterInfo collection for the event handler's invoke method.
ParameterInfo[] parameterInfos = handlerType.GetMethod("Invoke").GetParameters();

// Get the collection of types corresponding to the event handler's parameters.
Type[] parameterTypes = parameterInfos.Select(p => p.ParameterType).ToArray();

// Only attach a handler to methods that conform to the standard event handler signature
// of two parameters, one being an object sender, and the second being derived from
// eventargs.
if (parameterTypes.Length == 2 && parameterTypes[0] == typeof(object) &&
typeof(EventArgs).IsAssignableFrom(parameterTypes[1]))
{
// Create an array of generic type parameters.
Type[] genericTypeParameters = new Type[] { parameterTypes[1] };

// Get the type of the AnonymousDelegateClass, and fill the generic parameters.
Type type = typeof(AnonymousEventHandlerContainer<>).MakeGenericType(genericTypeParameters);

Type[] constructorTypes = new Type[] { typeof(EventMonitor), typeof(string) };

// Get the constructor for the class that takes the string value.
ConstructorInfo constructor = type.GetConstructor(constructorTypes);

// Create an instance of the class, which will subscribe to the event specified.
object instance = constructor.Invoke(new object[] { this, eventName });

// Get the Handler method, which will be the event handler for the event.
MethodInfo method = type.GetMethod("Handler");

// Create a delegate of the same type of the handler type based on the Handler method.
Delegate eventhandler = Delegate.CreateDelegate(handlerType, instance, method);

// Add the event handler to the object specified.
eventInfo.AddEventHandler(Sender, eventhandler);

// Finally, record the method subscriber in case we need to remove it.
eventHandlers.Add(eventName, eventhandler);
}
}
}
}


public class AnonymousEventHandlerContainer<eventargs> where eventargs : EventArgs
{
// This is the name of the event subscribed to.
public string EventName { get; private set; }

// This references the parent EventHandler.
public EventMonitor Parent { get; private set; }

public AnonymousEventHandlerContainer(EventMonitor parent, string eventName)
{
EventName = eventName;
Parent = parent;
}

// This the generic handler that is used to subscribe to the object.
public void Handler(object sender, eventargs e)
{
if (Parent != null && Parent.Event != null)
Parent.Event(sender, new AnonymousEventArgs(EventName, e));
}
}
}

Now, how to use the class. I tested this class by creating a new Windows Forms application, adding a single RichTextBox called "richTextBox1" and adding a DateTimePicker called "dateTimePicker1" onto the form. I then attached the DateTimePicker to the EventMonitor class. My form looked like this:

public partial class Form1 : Form
{
EventMonitor monitor;

public Form1()
{
InitializeComponent();
monitor = new EventMonitor(dateTimePicker1);
monitor.AttachAllEvents();
monitor.Event +=
new EventHandler<AnonymousEventArgs>(monitor_Event);

}

void monitor_Event(object sender, AnonymousEventArgs e)
{
richTextBox1.AppendText(string.Format("{0}: {1}{2}",
DateTime.Now.ToShortTimeString(),
e.EventName,
Environment.NewLine));
}
}
If you run this application, you'll notice output from every event that is raised on the DateTimePicker. The event name is displayed in the RichTextBox, along with the time it was raised.

I have found this class to be helpful in determining which events are raised, whether events are raised, and in what order they're raised.