Whenever you want to retrieve data from a .NET control using Ranorex, the data needs to be marshalled across process boundaries. In the Control class Ranorex provides methods – like the GetPropertyValue, SetPropertyValue, InvokeMethod, and InvokeRemotely – that handle the cross process marshalling of data for you. However, to be transferable, the data (parameters and return objects) need to be serializable. All the primitive data types of the .NET framework (int, double, string) and many complex types are serializable, but what if the data you want is not?

First of all, how do we know whether data is serializable or not? If the type of the data object is marked with a SerializableAttribute, then there’s a pretty good chance that the object is serializable; otherwise it is not. Consequently, when you use the GetPropertyValue, SetPropertyValue, or InvokeMethod methods, you have to check if the types of all the parameters and of the return value are serializable. The only exceptions are container and collections types that are themselves serializable (they are marked with the SerializableAttribute), but contain instances of types that aren’t.

So, if your data are all serializable, then just use the methods listed above. Everything will work just fine and there’s absolutely no sense in writing a blog post about that. … Well, usually most of the control developers don’t care if the types their control properties are serializable or not. That’s why we sometimes need to use a workaround to transfer our data from/to the control.

Ranorex V1.5 introduces a new method called Control.InvokeRemotely that allows us to execute code in the process of the control we want to exchange data with. (Note: This method is also available in Ranorex 2.X. This post and the sample code in it apply both to Ranorex 1.5 and 2.X versions.) Using that method, we can break up the unserializable data into serializable chunks in one process, transfer them, and finally reassemble them at the other process.

Assume that we want to get the text of all items in a ListView. Then we can call InvokeRemotely passing a delegate that collects the text values and stores them in a list (which is serializable):

string[] itemTextValues = (string[])listView.InvokeRemotely(
    delegate(System.Windows.Forms.Control control)
    {
        System.Windows.Forms.ListView remoteListView = (System.Windows.Forms.ListView)control;
        List values = new List();
        foreach (System.Windows.Forms.ListViewItem item in remoteListView.Items)
        {
            values.Add(item.Text);
        }
        return values.ToArray();
    });

If you want to retrieve more info about each list item, then you need to create a serializable data container:

[Serializable]
private class SerializableOutputData
{
    public bool Checked;
    public string Text;
}

private static object GetListViewData(System.Windows.Forms.Control control)
{
    System.Windows.Forms.ListView remoteListView = (System.Windows.Forms.ListView)control;
    List<SerializableOutputData> remoteOutputData = new List<SerializableOutputData>();
    foreach (System.Windows.Forms.ListViewItem item in remoteListView.Items)
    {
        SerializableOutputData data = new SerializableOutputData();
        data.Checked = item.Checked;
        data.Text = item.Text;
        remoteOutputData.Add(data);
    }
    return remoteOutputData.ToArray();
}
...
// call the GetListViewData method from the main method
SerializableOutputData[] outputData = (SerializableOutputData[])listView.InvokeRemotely(GetListViewData);

If you are not using anonymous methods, make sure to declare the delegate method static like in the example above. Or make the method member of a serializable type. This is actually necessary if you want to transfer data to the control. For instance, if you want to get information on all the list items which text property begins with a certain prefix:

[Serializable]
private class SerializableInputData
{
    public string Prefix;
    public object GetListViewData(System.Windows.Forms.Control control)
    {
        System.Windows.Forms.ListView remoteListView = (System.Windows.Forms.ListView)control;
        List<SerializableOutputData> remoteOutputData = new List<SerializableOutputData>();
        foreach (System.Windows.Forms.ListViewItem item in remoteListView.Items)
        {
            if (item.Text.StartsWith(Prefix))
            {
                SerializableOutputData data = new SerializableOutputData();
                data.Checked = item.Checked;
                data.Text = item.Text;
                remoteOutputData.Add(data);
            }
        }
        return remoteOutputData.ToArray();
    }
}
... // in the main method
SerializableInputData inputData = new SerializableInputData();
inputData.Prefix = "i";
SerializableOutputData[] outputData = (SerializableOutputData[])listView.InvokeRemotely(inputData.GetListViewData);

I hope that this post illustrates when and how to use the Control.InvokeRemotely method. If the desired data is not serializable, the GetPropertyValue, SetPropertyValue, and InvokeMethod methods won’t work. The InvokeRemotely method provides a convenient way to split the unserializable data into serializable parts and transfer the data that way.

You might also like these articles