Note: Part of a series: C#: Accessing form members from another class and How to access form objects from another cs file in C#.
Hello,
The Idea is to notify the user using the memo when a packet is received/sent in a TCP Client.
After couple of fixes,the most suitable solution seemed to be this one
public string TextValue
{
set
{
this.Memo.Text += value + "\n";
}
}
That's how it's being called
var form = Form.ActiveForm as Form1;
if(form != null)
form.TextValue = "Test asdasd";
However,calling the code throws an exception ,because of Unsafe thread call.I found a solution at msdn,but I can't seem to acquire the method they've used there.
This is my remake,which doesn't work.
private void SetTextMemo(string txt)
{
if(this.Memo.InvokeRequired)
{
this.Invoke(SetTextMemo,txt); //error here
}
else
{
this.Memo.Text += txt + "\n";
}
}
errors:
Argument '1': cannot convert from 'method group' to 'System.Delegate'
Argument '2': cannot convert from 'string' to 'object[]'
Basically,I'm trying to access the Memo(or more likely said,add text to the memo) from another thread using Invoke.I never used it before,maybe that's why I misunderstand my mistake.
The easy way is:
this.Invoke((MethodInvoker)delegate {
this.Memo.Text += txt + "\n";
});
Which uses an anonymous method to do the job inline. Since you expect to be on another thread, you may as well just call Invoke - it is safe even from the UI thread.
If you're using C# 3.0 and the 3.5 framework try the following
if ( this.Memo.InvokeRequired ) {
this.Invoke((Action)(() => SetTextMemo(txt)));
}
Your implementation assumes that the method will not infinitely recurse because the behavior of the InvokeRequired property will prevent it. This assumption may proove to be true, but there's no problem coding the function to avoid this possibility entirely. Here's what I suggest:
private void SetMemo(string txt)
{
Memo.Text = txt;
}
private delegate void MemoSetter(string txt);
public void ThreadSafeSet(string txt)
{
Invoke(new MemoSetter(SetMemo), txt);
}
I used to handle all this cross-thread business, but recently I went with AOP, where you simply decorate a method to execute on the UI thread. Here's an example (from PostSharp):
public class FormsThreadAttribute : OnMethodInvocationAspect
{
public override void OnInvocation(MethodInvocationEventArgs eventArgs)
{
Form f = (Form)eventArgs.Delegate.Target;
if (f.InvokeRequired)
f.Invoke(eventArgs.Delegate, eventArgs.GetArgumentArray());
else
eventArgs.Proceed();
}
}
Related
In an MVVM application I have a long running calculation that runs
in legacy code.
That legacy code shows a MessageBox to ask the user if it shall continue.
Now I want this code to stick to MVVM as easy as possible and thought
about handing in a callback to show the MessageBox and evaluating the
result inside.
How can this be done the easiest?
Have often seen Action for callbacks, but I have no idea how
to work with the bool inside the legacy code.
I want to pass the string to show in the MessageBox from the legacy code
and return the decision (a bool) to the legacy code.
Please note: I do not have to do a bigger refactoring right now, but want
to get rid of the MessageBox inside the legacy code right now.
Perhaps I can use a function like
private bool ShowMessageBox(string text)
{
var result = MessageBox.Show(text, "", MessageBoxButton.YesNo);
if (result.Equals(MessageBoxResult.Yes))
{
return true;
}
return false;
}
-edit-
Should I use some
Action<string, Action<bool>>
for the method signature?
How can I access the bool in the legacy code?
Maybe you can use a delegate?
For the method you showed, you can create a delegate like this:
public delegate bool ShowMessageBoxDelegate(string text);
Then let's say you have a property using the delegate as the type:
public ShowMessageBoxDelegate ShowMessageBoxDelegateProperty { get; set; }
Now if your ShowMessageBox method matches the signature of this delegate...
public bool ShowMessageBox(string text)
{
var result = MessageBox.Show(text, "", MessageBoxButton.YesNo);
if (result.Equals(MessageBoxResult.Yes))
{
return true;
}
return false;
}
... then you could set it as the value of the ShowMessageBoxDelegateProperty property:
ShowMessageBoxDelegateProperty = ShowMessageBox;
Note the missing parenthesis. A delegate can also be multicast, which simply means that they can have more than one method attached to them:
ShowMessageBoxDelegateProperty += ShowMessageBox;
You can also use them as parameters in methods:
public void ProxyShowMessageBox(ShowMessageBoxDelegate showMessageBoxDelegate)
{
if (showMessageBoxDelegate != null)
{
bool result = showMessageBoxDelegate("MessageBox message");
}
}
You would then call it like this:
ProxyShowMessageBox(ShowMessageBox);
You can find out more from the Delegates Tutorial page at MSDN.
I am trying to translate the following C# snippet to VB:
public bool ShowHandlerDialog(string message)
{
Message = message;
Visibility = Visibility.Visible;
_parent.IsEnabled = false;
_hideRequest = false;
while (!_hideRequest)
{
// HACK: Stop the thread if the application is about to close
if (this.Dispatcher.HasShutdownStarted ||
this.Dispatcher.HasShutdownFinished)
{
break;
}
// HACK: Simulate "DoEvents"
this.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { }));
Thread.Sleep(20);
}
return _result;
}
But the translation is giving an error on this line:
this.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { }));
The translation is:
Me.Dispatcher.Invoke(DispatcherPriority.Background, New ThreadStart(Function() Do End Function))
Which doesnt seem to convert correctly the bit after New ThreadStart. Can somebody please explain what 'delegate {}' does in
new ThreadStart(delegate {}));
and how I might correct the translation error? Thanks for any advice!
That line simply fires up a new thread and waits for it to finish. The "delegate { }" code is simply an anonymous/inline method (I don't think that is supported in VB.NET); just as if you would point to an empty method basically. For instance, in c# event-handlers can be bound to anonymous (inline) delegate methods as so:
this.OnClick += (EventHandler)delegate(object sender, EventArgs ea) {
MessageBox.Show("Click!");
};
The comment above says [// HACK: Simulate "DoEvents"]. Just replace the two lines with DoEvents for VB.NET and you should be set. That then allows other threads to do their work before continuing, thus improving responsiveness.
Hope this helps!
So, the documentation that I've found online so far regarding the Invoke property doesn't seem to be particularly good, which is actually kind of annoying, believe it or not. I understand what Invoke does - you can't safely access the GUI directly, so an invoke does it in a safe way. That's fine, but I don't understand the variables that go into the method. If I wanted to, for instance, remove text from a listbox, how would I do that? I get about this far before I get a bit lost.
private void DoStuff(string TextIWouldBeRemoving)
{
if (listboxname.InvokeRequired)
{
listboxname.Invoke(SomeMysteriousParamaters, new object[] { TextIWouldBeRemoving )};
}
}
The first parameter is the method you want to safely invoke, the second parameter is an object array of the arguments to that method
So you would write:
private void DoStuff(string TextIWouldBeRemoving)
{
if (listboxname.InvokeRequired)
{
listboxname.Invoke(DoStuff, new object[] { TextIWouldBeRemoving )};
}
else
{
// Actually remove the text here!
}
}
Invoke is all about threading.
You need to do an invoke whenever you have created a separate thread in your code, and you need to update the User Interface elements from withing the code, that is executing in that newly create thread.
You can use a BeginInvoke, instead of a synchronous Invoke method. This article has a good example:
http://msdn.microsoft.com/en-us/library/0b1bf3y3.aspx
private void button1_Click(object sender, EventArgs e)
{
if (listBox1.InvokeRequired)
{
Action<string> d = DoAnything;
listBox1.Invoke(d, new object[] { "Item 1" });
}
else
DoAnything("Item 1");
}
void DoAnything(string itemText)
{
listBox1.Items.Remove(itemText);
}
I'm getting a error in my code
Cross-thread operation not valid:
Control '' accessed from a thread
other than the thread it was created
on.
I don't know why it is happening. Can someone explain this to me?
That is hapening because you are accessing to a control in your Windows Form application from another thread.
Could you share your code?
Let's suppose you are accessing to a TextBox (which name is textBox1):
textBox1.Text = "Modified text"
Instead of doing that, you must do:
MethodInvoker m = () => { textBox1.Text = "Modified text"; };
if (InvokeRequired) {
BeginInvoke(m);
}
else {
m.Invoke();
}
Of course, that was a simple example. You can encapsulate the Invoking part in in a method so you don't repeat the same code over and over. Something like:
public void InvokeSafe(MethodInvoker m) {
if (InvokeRequired) {
BeginInvoke(m);
}
else {
m.Invoke();
}
}
so all you have do to is:
MethodInvoker m = () => { textBox1.Text = "Modified text"; };
InvokeSafe(m);
Common reason is if you are trying to access data available for UI thread from your background thread. Verify that you are not accessing data across threads.
You need to post more details.
The message is quite clear. Cross-thread calls can make the application very unstable thats why it is not valid.
Here is some documentation how to solve this:
http://www.vcskicks.com/cross-thread.php
http://www.dreamincode.net/forums/topic/35616-cross-thread-communication-in-c%23/
I have been searching stackoverflow for ways to marshal data back from a thread to the UI thread, and have found various ways to do it in 3.5.
One of the more elegant solutions to me; although I am still learning lambdas and closures is this solution Control.Invoke with input Parameters.
I don't completely understand the code, but I do understand how to use it and it doesn't quite solve my problem.
I would like to call invoke and pass a string to another method (DisplayStatusUpdate(msg)). Any pointers would be appreciated.
private void FireEventAppender_OnMessageLogged(object sender, MessageLoggedEventArgs e)
{
DisplayStatusUpdate(e.LoggingEvent.RenderedMessage);
}
private void DisplayStatusUpdate(string text)
{
_StatusTextBox.Text = _StatusTextBox.Text + text;
_StatusTextBox.Text = String.Format("{0}\r\n", _StatusTextBox.Text);
_StatusTextBox.SelectionStart = _StatusTextBox.Text.Length - 1;
_StatusTextBox.ScrollToCaret();
}
You can do this with as many parameters as you like, for example:
private void FireEventAppender_OnMessageLogged(object sender, MessageLoggedEventArgs e)
{
DisplayStatusUpdate(e.LoggingEvent.RenderedMessage);
}
private delegate void DisplayStatusUpdateDelegate(string text);
private void DisplayStatusUpdate(string text)
{
if(InvokeRequired)
this.Invoke(new DisplayStatusUpdateDelegate(DisplayStatusUpdate), text);
else
{
_StatusTextBox.Text = _StatusTextBox.Text + text;
_StatusTextBox.Text = String.Format("{0}\r\n", _StatusTextBox.Text);
_StatusTextBox.SelectionStart = _StatusTextBox.Text.Length - 1;
_StatusTextBox.ScrollToCaret();
}
}
There is another method that I have been using in a current project that can reduct the code significantly. Details are here.
If you implement this method, you would need to:
Create the form using the AOP factory:
Form f = AOPFactory.Create<Form1>();
Application.Run(f);
And then you just decorate the event handler with the [RunInUIThread] attribute. It uses Castle's method interception model to automatically invoke if need be.
Thus, the above code becomes:
private void FireEventAppender_OnMessageLogged(object sender, MessageLoggedEventArgs e)
{
_StatusTextBox.Text = _StatusTextBox.Text + text;
_StatusTextBox.Text = String.Format("{0}\r\n", _StatusTextBox.Text);
_StatusTextBox.SelectionStart = _StatusTextBox.Text.Length - 1;
_StatusTextBox.ScrollToCaret();
}
Performance wise it seems to be comparable, slightly slower, but depending on what you are doing it may be ok, it definitely takes less code.
If you are using C# 3.5 (or later) you can use captured variables, which may simplify the code:
private void DisplayStatusUpdate(string text)
{
this.Invoke(new MethodInvoker(() =>
{
_StatusTextBox.Text = _StatusTextBox.Text + text;
_StatusTextBox.Text = String.Format("{0}\r\n", _StatusTextBox.Text);
_StatusTextBox.SelectionStart = _StatusTextBox.Text.Length - 1;
_StatusTextBox.ScrollToCaret();
}));
}
What the compiler will do behind the scenes here is to generate a class to hold the text, create a MethodInvoker delegate, and pass that delegate and an instance of the generated class to the Invoke method. In the above design this will of course do the unnecessary work of creating a delegate and calling Invoke even if the code is already running on the same thread; but I believe that a method like this should not be called that often that it would be a performance issue.
If you want to you can replace the use of MethodInvoker with the parameter-less Action delegate, or any other parameter-less delegate that returns void.