|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
ContentsIntroductionRecently, I was involved in writing a utility program for interpreting GPS data from a variety of sources, including a Serial Port. The main structure of the library/program involved three layers:
Data providersThe data providers were in charge of reading in data from different sources. For example, one read in data from a file while another one was supposed to read in data from a USB GPS receiver. Upon receiving some data, the providers would fire an event signaling that there was data ready to be parsed. ParsersThese classes parsed the data from the providers and fired off some events notifying the "parent" classes that data was ready. An example of this was parsing the NMEA data into some structures; various events would be fired when location, bearing, etc. changed. PresentationThe final layer listened to the events fired from the parsers and displayed the data in various forms, such as TextBoxes, Graphs, Plots and so on. The problemThings were progressing nicely until the presentation layer. That's when the debugger told me that I was trying to update the UI from another thread. Slightly confused, Google and MSDN came to my rescue. It seems that the So, off to Google for a solution, or not. The only examples I found to begin with said that you should use Eventually, after a bit more digging -- even looking through framework code in Reflector to see how things were accomplished in GenericsInitially, the first version used generics until some kind person on Code Project pointed out that they weren't actually needed for this code. As a result, I have modified it to accept a simple delegate type rather than a generic. It was necessary to check that it was a delegate type, since this couldn't be imposed by a generic constraint. The codeThis code is based on the article by Alexey Popov. His article does a good job of explaining what is going on, so I have not included that discussion. using System;
using System.ComponentModel;
namespace PooreDesign
{
public static class InvocationHelper
{
public static void Invoke(Delegate handler, params object[] arguments)
{
int requiredParameters = handler.Method.GetParameters().Length;
// Check that the correct number of arguments have been supplied
if (requiredParameters != arguments.Length)
{
throw new ArgumentException(string.Format(
"{0} arguments provided when {1} {2} required.",
arguments.Length, requiredParameters,
((requiredParameters == 1) ? "is" : "are")));
}
// Get a local copy of the invocation list in case it changes
Delegate[] invocationList = handler.GetInvocationList();
// Check that it's not null
if (invocationList == null)
return;
// Loop through delegates and check for ISynchronizeInvoke
foreach (Delegate singleCastDelegate in invocationList)
{
ISynchronizeInvoke synchronizeTarget =
singleCastDelegate.Target as ISynchronizeInvoke;
// Check to see if the interface was supported
if (synchronizeTarget == null)
{
// Invoke delegate normally
singleCastDelegate.DynamicInvoke(arguments);
}
else
{
// Invoke through synchronization interface
synchronizeTarget.BeginInvoke(
singleCastDelegate, arguments);
}
}
}
}
}
Invoke (Delegate eventHandler, params object[] arguments)Argument CheckThe next check performed checks that the appropriate number of arguments has been provided. Note that it does not check that the arguments are of the correct type. There are several reasons for this, the chief reason being that it would be quite expensive to check all of the arguments all the time. The reason a check is performed on the number of arguments is that the default exception thrown is quite ambiguous. So, at least this will narrow down problems for the developer. int requiredParameters = handler.Method.GetParameters().Length;
// Check that the correct number of arguments have been supplied
if (requiredParameters != arguments.Length)
{
throw new ArgumentException(
string.Format("{0} arguments provided when {1} {2} required.",
arguments.Length, requiredParameters,
((requiredParameters == 1) ? "is" : "are")));
}
A // Get a local copy of the invocation list in case it changes
Delegate[] invocationList = handler.GetInvocationList();
// Check that it's not null
if (invocationList == null)
{
return;
}
InvocationThe next piece of code deals with finding out if each target object implements ISynchronizeInvoke synchronizeTarget =
singleCastDelegate.Target as ISynchronizeInvoke;
The above line casts the How to useLet's take a similar example to what caused this article. Say that we want to output data received on the private void btnOpenport_Click(object sender, EventArgs e)
{
this.serialPort.Open();
}
private void serialPort_DataReceived(object sender,
SerialDataReceivedEventArgs e)
{
//To Do:
}
Now it's time to write the data to the output textbox. You can try this code inside the this.outputTextBox.Text = this.serialPort.ReadExisting();
However, if you run this from inside the debugger, it will break and say that you're making a cross-thread call. One alternative, as is commonly suggested, is to use the InvocationHelper.Invoke(new EventHandler(delegate (object sender, EventArgs e)
{
this.outputTextBox.Text = this.serialPort.ReadExisting();
}));
OK, I agree that this method doesn't really save much, but where it comes down to it is something like the following. More complex exampleHere's a very basic outline of the NMEA parser, which should demonstrate where this class becomes useful. First of all, there is a public event EventHandler<InvalidDataEventArgs> InvalidData;
public void Parse(string sentence)
{
if (!this.IsValidChecksum(sentence))
{
this.OnInvalidData(new InvalidDataEventArgs(
"Checksum failed", sentence));
}
}
The Now, here is where the magic works. The protected virtual void OnInvalidData(InvalidDataEventArgs e)
{
InvokeHelper.Invoke(this.InvalidData, this, e);
}
This piece of code will now -- if the handler associated with the event implements Credits
History
|
||||||||||||||||||||||