Categories: MSDN / DotNet / Java / Scripts / Linux / PHP Ask - La ask - La Answer

passing multiple parameters with delegates and Invoke...

I am creating a multi-threaded application, i have tried to put a lot of code in a background worker thread that does some DB calls and waits on them but in this code is code to update the GUI, so i am having to create delegates and utilize Invoke to avoid non-thread safe code when updating controls on the form...

My problem is passing more than 1 parameter to a delegate i.e. passing a ListViewItem and a bool indicating if this ListViewItem should be checked or unchecked...

If i define the function SetCountryCheckedStatus(below) to take a ListViewItem and a bool then when it comes to using Invoke with the delegate , I have to pass in an array of objects, so i tried creating params_list[0] with the ListViewItem and params_list[1] with the bool..

delegate void SetBoolDelegate(object[] params_list);

(Invoke takes: method, object[] as parameters)
Invoke(new SetBoolDelegate(SetCountryCheckedStatus), params_list);

but this always comes back to me with the error message 'Parameter count mismatch'

Any ideas?
Thanks in advance!

-Dan

delegate void SetBoolDelegate(object[] params_list);

void SetCountryCheckedStatus(object[] params_list)
{
if (!InvokeRequired)
{
ListViewItem lvi = (ListViewItem)params_list[0];
bool ischecked = (bool)params_list[1];
lvi.Checked = ischecked;
}
else
{
Invoke(new SetBoolDelegate(SetCountryCheckedStatus), params_list);
}
}
[1677 byte] By [djbyrne] at [2007-11-11 10:20:21]
# 1 Re: passing multiple parameters with delegates and Invoke...
Google knows all: http://www.google.com/search?q=c%23+invoke+parameter+count+mismatch
Phil Weber at 2007-11-11 20:48:09 >
# 2 Re: passing multiple parameters with delegates and Invoke...
Thanks Phil,
Although I cannot find anything that has helped me reach a solution from googling I will keep looking
djbyrne at 2007-11-11 20:49:15 >
# 3 Re: passing multiple parameters with delegates and Invoke...
Hi all,
Just incase anyone is having the same problem the solution i found was to use:

this.Invoke(new SetBoolDelegate(SetCountryCheckedStatus), new object[] { params_list });

instead of:

this.Invoke(new SetBoolDelegate(SetCountryCheckedStatus), params_list );

Dont know why this should make any difference but it does..
djbyrne at 2007-11-11 20:50:14 >
# 4 Re: passing multiple parameters with delegates and Invoke...
Hi all,
Just incase anyone is having the same problem the solution i found was to use:

this.Invoke(new SetBoolDelegate(SetCountryCheckedStatus), new object[] { params_list });

instead of:

this.Invoke(new SetBoolDelegate(SetCountryCheckedStatus), params_list );

Dont know why this should make any difference but it does..
I believe the reason it makes a difference is how invoke handles an array. You are allowed to pass an array of objects. Each object in the array gets mapped to a different parameter. So if your delegate takes 3 parameters you would pass in an array of 3 objects and each one would be mapped to the corrisponding parameter. However, in your case you actually wanted to pass an array. Unfortunatly the compiler thinks each item in the array should be mapped to a diferent parameter. Thus you get a parameter mis count error. By wrapping your array inside another array the compiler sees your array as the first item in the parameter array and maps it to the first parameter. Hope that made sense.

What might have gotten lost in all that is the fact that your delegate can take multiple parameters. The objects in the array you pass will be mapped to each parameter in order.
TwoFaced at 2007-11-11 20:51:14 >
# 5 Re: passing multiple parameters with delegates and Invoke...
okay, i have streamlined it a little bit...

I have created a generic delegate that will handle updates to any sort of control and through parameters passed to it will allow you to define what kind of change you want making to it (i.e. make it visible/invisible, checked/unchecked, change the text etc.)

Im sure this is very sloppy and would love for someone to let me know of a better solution but just updating this post so that people can see how i've accomplished it and hopefully it will help somebody.

heres a link to the actual post at my blog incase its not readable here but will post it anyway... because it is a huge piece of code:

http://www.danielbyrne.net/blog/?p=8

does this way of doing it make sense?

code follows:

I have created a generic delegate to handle control interface updates with a signature of:

public delegate void updateControlDelegate(ctlUpdateEventArgs ev);

to use..
whenever you want to update a control but you are in another thread:

UpdateControl(new ctlUpdateEventArgs(txtbox, ctlUpdateEventArgs.ctlTypes.textbox, newstringvalue, ctlUpdateEventArgs.valueTypes.text));

there is a class that holds all of the arguments for this, called ctlUpdateEventArgs. This class allows you to define the control, the type of the control, the value and value type, and is listed here:

the update arguments class

This class holds a reference to the control you are updating, the control type so it can be casted, the value type you are changing, and the new value..

public class ctlUpdateEventArgs

{

public enum ctlTypes { label, textbox, listbox, combobox, radiobutton, checkbox, date, panel, form, listviewitem };

public enum valueTypes { checkable, text, visible, selecteditem, sendtoback, bringtofront, datetime };

public object ctl;
public ctlTypes ctlType;
public object newValue;
public valueTypes newValueType;

public ctlUpdateEventArgs(object ctl, ctlTypes ctlType, object newValue, valueTypes newValType)

{

this.ctl = ctl;
this.ctlType = ctlType;
this.newValue = newValue;
this.newValueType = newValType;

}

}

now.. the meat of the code is the function that is called by the delegate to do the actual work, it consists of a switch statement with numerous cases depending on the type of control (so it can be casted correctly) and the value is then applied to the control passed to it in the ctlUpdateEventArgs ctl parameter)

I know this is a bit messy but hopefully I will come up with a better solution soon

void UpdateControl(ctlUpdateEventArgs ev)

{

if (!InvokeRequired)

{if (ev.newValueType == ctlUpdateEventArgs.valueTypes.sendtoback)

{

System.Windows.Forms.Control tmpCtl = (System.Windows.Forms.Control)ev.ctl;

tmpCtl.SendToBack();

return;

}

else if (ev.newValueType == ctlUpdateEventArgs.valueTypes.bringtofront)

{

System.Windows.Forms.Control tmpCtl = (System.Windows.Forms.Control)ev.ctl;

tmpCtl.BringToFront();

return;

}

switch (ev.ctlType)

{

case ctlUpdateEventArgs.ctlTypes.listviewitem:

ListViewItem tmpLvi = (ListViewItem)ev.ctl;

if (ev.newValueType == ctlUpdateEventArgs.valueTypes.checkable)

{

tmpLvi.Checked = (bool)ev.newValue;

}

break;

case ctlUpdateEventArgs.ctlTypes.checkbox:

break;

case ctlUpdateEventArgs.ctlTypes.combobox:

ComboBox tmpCmb = (ComboBox)ev.ctl;

if (ev.newValueType == ctlUpdateEventArgs.valueTypes.selecteditem)

{

tmpCmb.SelectedItem = ev.newValue;

}

break;

case ctlUpdateEventArgs.ctlTypes.date:

DateTimePicker tmpDate = (DateTimePicker)ev.ctl;

if (ev.newValueType == ctlUpdateEventArgs.valueTypes.datetime)

{

tmpDate.Value = (DateTime)ev.newValue;

}

break;

case ctlUpdateEventArgs.ctlTypes.label:

Label tmpLabel = (Label)ev.ctl;

tmpLabel.Text = (String)ev.newValue;

break;

case ctlUpdateEventArgs.ctlTypes.listbox:

break;

case ctlUpdateEventArgs.ctlTypes.radiobutton:

RadioButton tmpRadio = (RadioButton)ev.ctl;

if (ev.newValueType == ctlUpdateEventArgs.valueTypes.checkable)

{

tmpRadio.Checked = (bool)ev.newValue;

}

else if(ev.newValueType == ctlUpdateEventArgs.valueTypes.text)

{

tmpRadio.Text = (String)ev.newValue;

}

break;

case ctlUpdateEventArgs.ctlTypes.textbox:

TextBox tmpTxt = (TextBox)ev.ctl;

tmpTxt.Text = (String)ev.newValue;

break;

case ctlUpdateEventArgs.ctlTypes.panel:

Panel tmpPanel = (Panel)ev.ctl;

if (ev.newValueType == ctlUpdateEventArgs.valueTypes.visible)

{

tmpPanel.Visible = (bool)ev.newValue;

}

break;

}

}

else

{

Invoke(new updateControlDelegate(UpdateControl), ev);

}

}
djbyrne at 2007-11-11 20:52:14 >
# 6 Re: passing multiple parameters with delegates and Invoke...
I like this topic because I think we're in the exact same boat. I've just begun learning about threading and delegates and have come to the same conclusion you have. Making UI changes isn't so straight forward. So far the only changes I've had to make from a secondary thread are simple, like adding items to a combox. One delegate and one method are all that's needed to do this in a thread safe manner. However, I imagine there are times when more UI changes will be necessary and I think exploring the best way to do this is something I need to do. I've given your problem some thought and I think I have a good suggestion. Hopefully you'll keep posting to this thread about your experience and discoveries and I will do the same. This way we can figure this beast out with a little help from each other.

I took a look at your solution and normally I am all for general procedures but I think you have taken it a few steps to far. The problems I see with your solution are readablity, and flexibility. Specifically adding different changes later or a new control is awkward. For instance if you want to do something later with a different control you would have to add it to your enumerator and possibly add the type of change you want to your second enumerator. Secondly with the way it's set up I could pass in a textbox and also pass in checkable as the type of change. The code my not throw an error but still the fact that I can do this doesn't seem like good practice. Thirdly I have to pass in the type of control and the type of change being made. This seems like a pain and will clutter your code. Also I believe with the way it's set up if you wanted to change the visible property of a textbox you would have to add additional code. Perhaps later you decide to change another controls visible property...again more code changes. Well that's my analysis of your code. Sorry, I hope you don't feel like I'm tearing it apart. That's not my intention. But you yourself said you felt it was sloppy and I feel like an honest assement will help us both learn.

So now that I've given you my opinion on your solution, I'll post a code snippet of mine that I think will work well. Feel free to tear it apart :) Let me know what you think. It's just a sample that uses of few things I noticed you needed to do as examples. I'll post the code and explanation in another post. This one is getting long.
TwoFaced at 2007-11-11 20:53:19 >
# 7 Re: passing multiple parameters with delegates and Invoke...
What I've done is created a class that has all shared methods (static methods in c# I believe). Each method takes a control and a value. One of the methods might be SetText. Because all controls have a text property I don't need to know anything more about it other then the fact that it's a control. The method will see if InvokeRequired and if it is will call invoke to run the code from the GUI thread. For more specific changes that might only apply to certain controls I create a sub class with that controls name. Then I create whatever shared method I like. For instance a checkbox needs to be checked so I have a sub class 'CheckBox' with a method "Check". This method takes a checkbox and a value (true/false). I believe this approach is cleaner and more flexible. To add additional types of changes just add a new method. For specific changes add a sub class for whatever control you want to change. Everything is neatly organized and thus very easy to maintain. Best of all all the delegates that need to be created are hidden away. As far as the rest of your code is concerned your simply calling a method. All the detials are nicely tucked away. Plus the code can easily be used in any other project. Here is the code. I do my work in VB.net so I've used a converter to change it to C# (I believe that's what your code is in, right?). Hopefully the converter did okay but you might need to make some changes (I hope minor ones).public class ThreadSafe {

static void SetText(Control ctrl, string text) {
if (ctrl.InvokeRequired) {
object[] params;
ctrl;
text;
ctrl.Invoke(new SetTextDelegate(new System.EventHandler(this.SetText)), params);
}
else {
ctrl.Text = text;
}
}

static void SetVisible(Control ctrl, bool visible) {
if (ctrl.InvokeRequired) {
object[] params;
ctrl;
visible;
ctrl.Invoke(new SetVisibleDelegate(new System.EventHandler(this.SetVisible)), params);
}
else {
ctrl.Visible = visible;
}
}

public delegate void SetTextDelegate(Control ctrl, string text);

public delegate void SetVisibleDelegate(Control ctrl, bool visible);

public class CheckBox {

static void Check(System.Windows.Forms.CheckBox ctrl, bool value) {
if (ctrl.InvokeRequired) {
object[] params;
ctrl;
value;
ctrl.Invoke(new CheckDelegate(new System.EventHandler(this.Check)), params);
}
else {
ctrl.Checked = value;
}
}

public delegate void CheckDelegate(System.Windows.Forms.CheckBox ctrl, bool value);
}
}Using these methods is very simple. The can be called from any thread and they are guaranteed to run on the GUI thread. For example: 'Change some text
ThreadSafe.SetText(TextBox1, "SomeText")
'Hide a panel
ThreadSafe.SetVisible(Panel1, False)
'Check a checkbox
ThreadSafe.CheckBox.Check(CheckBox1, True)
'Hide a textbox
ThreadSafe.SetVisible(textbox2, False)
TwoFaced at 2007-11-11 20:54:19 >
# 8 Re: passing multiple parameters with delegates and Invoke...
i'm glad I registered here I have learned so much in a short space of time..
Do you have MSN messenger? You sound like the kind of guy it'd be great to bounce ideas from occasionally..

anyway, I like your solution a lot, I am going to try and implement something similar in the next few hours and will post with an update of how its going, from what I can see the code looks like it's valid c# except for the object[] params declaration which ill post the change for in a minute.

cheers for your (very) helpful reply!

p.s.
I didn't realize you had access to delegates in VB.net, guess it is a real language afterall ;) jk
djbyrne at 2007-11-11 20:55:18 >
# 9 Re: passing multiple parameters with delegates and Invoke...
Ok, I have made some slight changes to your code just to make it C# compatible. The code itself is very well thought out and easily maintainable so I am in the process of updating my project with this static class rather than the rather cumbersome method I was using before.

The main change i've made so far is to add a combobox class to access the selecteditem property. I first of all tried making a generic handler that would accept a ListControl which combobox inherits but that only provided selectedvalue.

I have also changed the params declaration (params being a reserved word i changed it to params_list, and changed some small syntax issues)

object[] params_list = new object[] { ctrl, text };

instead of

object[] params;
ctrl;
text;

public class ThreadSafe
{

//generic system.windows.forms.control
public static void SetText(System.Windows.Forms.Control ctrl, string text)
{
if (ctrl.InvokeRequired)
{
object[] params_list = new object[] { ctrl, text };
ctrl.Invoke(new SetTextDelegate(SetText), params_list);
}
else
{
ctrl.Text = text;
}
}
public static void SetVisible(System.Windows.Forms.Control ctrl, bool visible)
{
if (ctrl.InvokeRequired)
{
object[] params_list = new object[] { ctrl, visible };
ctrl.Invoke(new SetVisibleDelegate(SetVisible), params_list);
}
else
{
ctrl.Visible = visible;
}
}

public delegate void SetTextDelegate(System.Windows.Forms.Control ctrl, string text);
public delegate void SetVisibleDelegate(System.Windows.Forms.Control ctrl, bool visible);

//checkbox
public class CheckBox
{

public static void Check(System.Windows.Forms.CheckBox ctrl, bool value)
{
if (ctrl.InvokeRequired)
{
object[] params_list = new object[] { ctrl, value };
ctrl.Invoke(new CheckDelegate(Check), params_list);
}
else
{
ctrl.Checked = value;
}
}

public delegate void CheckDelegate(System.Windows.Forms.CheckBox ctrl, bool value);
}

}

//combobox
public class ComboBox
{

public static void SetSelectedItem(System.Windows.Forms.ComboBox ctrl, object value)
{
if (ctrl.InvokeRequired)
{
object[] params_list = new object[] { ctrl, value };
ctrl.Invoke(new SetSelectedItemDelegate(SetSelectedItem), params_list);
}
else
{
ctrl.SelectedItem = value;
}
}

public delegate void SetSelectedItemDelegate(System.Windows.Forms.ComboBox ctrl, object value);

}

Thanks again for this great solution!
djbyrne at 2007-11-11 20:56:21 >
# 10 Re: passing multiple parameters with delegates and Invoke...
I have found a better way of doing this...

http://www.danielbyrne.net/blog/?p=10

Forget a static class full of delegates, how about this for an easier way to update controls from another thread

using anonymous methods

lstResults.BeginInvoke((MethodInvoker)delegate() { lstResults.Columns.Clear(); }, null);

This means, for the control lstResults run the BeginInvoke method which executes the code defined in this same line in the GUI thread..
Using anonymous methods we can define the delegate inline by creating a new delegate with code inside the curly braces to do our bidding { lstResults.Columns.Clear } or { txtStatus.Text = whatever you want; };

Nice eh?
djbyrne at 2007-11-11 20:57:17 >
# 11 Re: passing multiple parameters with delegates and Invoke...
Very nice! I think you found yourself the best solution. Just one problem...VB.net doesn't support anonymous methods :( I guess I'll stick with my shared methods for now.
TwoFaced at 2007-11-11 20:58:17 >
# 12 Re: passing multiple parameters with delegates and Invoke...
I've found another problem with anonymous methods marshaled to the GUI thread.. they're called asynchronously meaning the code after the GUI update might actually run first which ruins a few of the scenarios I was planning on using this in..

I think a mixture of both solutions will do the job for me

Cheers
Dan
djbyrne at 2007-11-11 20:59:27 >
# 13 Re: passing multiple parameters with delegates and Invoke...
Is it the anonymous method that determines if it runs asynchronously our how you call invoke. In your example you used 'begininvoke' which is asynchronous as far as I know. 'Invoke' will call a routine and wait for it to return. At least thats how it works for normal delegates in vb.net.
TwoFaced at 2007-11-11 21:00:29 >