I have a Windows Forms C# application which deals with TreeView and uses a separate thread to update it.
Update launched by button click:
private void button3_Click(object sender, EventArgs e)
{
vc.ChangeTree(treeView1);
}
ChangeTree() starts UpdateTree() in a separate thread to update the TreeView asynchronously.
Here I just need to use Thread for this:
public void ChangeTree(TreeView tree)
{
Thread thread4 = new Thread(() => { UpdateTreeView(tree); });
thread4.Name = "Thread 4";
thread4.IsBackground = true;
thread4.Start();
}
UpdateTree() simply invokes the TreeView and adds nodes from another tree to it. Note that there aren't time consuming operations. The GetTree() just returns another TreeView:
private void UpdateTreeView(TreeView tree)
{
if (tree.InvokeRequired)
{
tree.Invoke((MethodInvoker)delegate
{
UpdateTreeView(tree);
});
}
else
{
tree.Nodes.Clear();
foreach (var node in GetTree().Nodes)
{
tree.Nodes.Add(node as TreeNode);
}
//Application.DoEvents(); // doesn't help
}
}
The problem is that it actually adds nodes to the tree, but form does not show it. I checked the treeView1.Nodes after i clicked the button. It contains the nodes I need.
The Application.DoEvents() does not help, even though it may, according to my search. Neither does treeView1.Refresh().
If i click the button twice, treeView1 updates as I planned. But I don't know why and, obviously, I don't need such behavior.
So here is the question. How to make it update with one click?
It turns out that TreeNode can't be used by more than one TreeView.
The solution I found is to use treeNode.Clone():
tree.Nodes.Add((node as TreeNode).Clone() as TreeNode);
But I still don't understand why the initial code worked, even though it took two times for it.
I suspect that one TreeNode cannot be changed from one TreeView to another
This is my test code that works:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button3_Click(object sender, EventArgs e)
{
ChangeTree(treeView1);
}
public void ChangeTree(TreeView tree)
{
Thread thread4 = new Thread(() => { UpdateTreeView(tree); });
thread4.Name = "Thread 4";
thread4.IsBackground = true;
thread4.Start();
}
private void UpdateTreeView(TreeView tree)
{
if (tree.InvokeRequired)
{
tree.Invoke((MethodInvoker)delegate
{
UpdateTreeView(tree);
});
}
else
{
tree.Nodes.Clear();
foreach (var node in GetTree())
{
tree.Nodes.Add(node as TreeNode);
}
}
}
private List<TreeNode> GetTree()
{
var result = new List<TreeNode>();
result.Add(new TreeNode("changed"));
return result;
}
}
}
If I change the GetTree() function into
private TreeView GetTree()
{
var result = new TreeView();
result.Nodes.Add(new TreeNode("changed"));
return result;
}
And revert the UpdateTreeView function into what it was before, it behaves like you described
Did you try setting the focus back to the treeview? With TreeView.Focus() this causes a refresh.
Related
Cannot enter text from class to textbox in form.
We set a keypress event in the MyTreeView class.
The text box cannot contain characters.
What should I do?
*set of textBox1.
*Change Modifiers for textBox1 properties from private to public
*Change keypress event from private to public
*(It didn't work well, so I keep it private now.)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
namespace treeview
{
public partial class Form1 : System.Windows.Forms.Form
{
MyTreeView m_tree_view = new MyTreeView();
public Form1()
{
try
{
InitializeComponent();
System.Windows.Forms.TreeNode[] tree1 = new System.Windows.Forms.TreeNode[2];
m_tree_view.Location = new System.Drawing.Point(0, 0);
m_tree_view.Size = ClientSize;
m_tree_view.AllowDrop = true;
tree1[0] = new System.Windows.Forms.TreeNode("TreeNode1");
tree1[1] = new System.Windows.Forms.TreeNode("TreeNode2");
m_tree_view.Nodes.Add("Node1");
Controls.Add(m_tree_view);
}
catch
{
}
}
//This is the code I added.
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class MyTreeView : System.Windows.Forms.TreeView
{
public MyTreeView()
{
try
{
//This is the code I added.
KeyPress += MyTreeView_KeyPress;
}
catch
{
}
}
//This is the code I added.
private void MyTreeView_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
Console.WriteLine("key_press_ok");
//error code↓
//textBox1.Text = "sample";
}
}
}
If you want just to click an button and then print some text i don't understand why you are making another class.
Will be good to make your code efficient and not complicated.
In the main class
private void SendText_Click(object sender, EventArgs e)
{
textBox1.Text = "hi";
}
But if you want to make it complicated and make a class you shuld return i variable and send it to the other class the you can use it.
Learn how to use Public and private first and then use them.
You shuld have a public class which send data and the private to recive and process.
add (Exception ex) to your try catch.
so do:
try
{
// your code
}
catch (Exception ex)
{
MessageBox.Show(ex, "Error in (add where the error is)");
Console.WriteLine(ex);
}
So you will get a detailed Exception Message, maybe it helps you or maybe you will post it here, so we can see what the problem is.
And because you have System.Windows.Forms in your Using Directive
using System.Windows.Forms;
so
System.Windows.Forms.TreeNode[] tree1 = new System.Windows.Forms.TreeNode[2];
is redundant and can be shortened to:
TreeNode[] tree1 = new TreeNode[2];
I'm trying to get a simple label value to change from another thread, and already tried 3 different threading mechanisms (Tasks, Thread, Background worker) and am now at a loss why the control won't update.
I have a method in an unrelated class like this:
public static void SetOverlayText(string text, bool fade = false)
{
Thread myThread = new Thread(FadeOverlayText);
OverlayForm.SetLabelText(text);
if (fade)
{
myThread.Start();
}
}
and
private static void FadeOverlayText()
{
OverlayForm.ClearLabelText();
}
My form is a regular windows form and has that method:
public void ClearLabelText()
{
this.Invoke((MethodInvoker)delegate
{
StatusText.Text = "Something should happen"
StatusText.Refresh();
});
}
The method appears to be getting called, but nothing happens.
You should not need Refresh.
This should work:
public void ClearLabelText()
{
if (StatusText.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
StatusText.Text = "Something should happen";
});
}
else
{
StatusText.Text = "Something should happen";
}
}
Are you shure, you use the correct control and at no other point the string is changed, so that it seems not to work? Please check every thing.
Also be sure, that you only call ClearLabelText once in your second thread, becuase after ClearLabelText is finished, the thread is not alive anymore.
This will update your text every second, as long as the application runs:
private static void FadeOverlayText()
{
var uiThread = <<Your UI Thread>>;
while(uiThread.IsAlive)
{
OverlayForm.ClearLabelText();
Thread.Sleep(1000);
}
}
EDIT:
here is a simple example i've made and it works. Additionaly to your StatusText label, I've added button1, which change the text too.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace ThreadTest2
{
public partial class Form1 : Form
{
Thread mainThread = null;
public Form1()
{
InitializeComponent();
mainThread = Thread.CurrentThread;
Thread myThread = new Thread(FadeOverlayText);
myThread.Start();
}
private void FadeOverlayText()
{
while (mainThread.IsAlive)
{
ClearLabelText();
Thread.Sleep(1000);
}
}
public void ClearLabelText()
{
if (StatusText.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
StatusText.Text = "Something should happen";
});
}
else
{
StatusText.Text = "Something should happen";
}
}
private void button1_Click(object sender, EventArgs e)
{
StatusText.Text = "It works!";
}
}
}
One way to make this work is to use a timer that does
StatusText.Text= yourstring;
Every n milliseconds, and make your thread update the 'yourstring' variable to whatever you want.
I'm developing a WPF Login form.I have a tab control with two tabs:
tab1) Contains the inputs for login(user name and password text box/labels)
tab2) Contains a custom animation which is used as the progress bar
Once the user captures all the info and clicks Login in the Login button's click event
I set the active tab to tab2 and the progress bar is shown to the user.
If an error occurs during this step I would like to return the user to tab1 and this is where I get the following error:
Invalid Operation Exception (The calling thread cannot access this object because a different thread owns it.)
Please advice how I can kill the thread or any other work around to help fix my problem
My code:
public partial class LogonVM : ILogonVM
{
private IWebService _webService;
private static TabControl loaderTabs;
private string userName = String.Empty;
public string UserName
{
get { return userName; }
set
{
userName = value;
OnPropertyChanged("UserName", true);
}
}
private SecureString password = new SecureString();
public SecureString Password
{
get { return password; }
set
{
password = value;
OnPropertyChanged("Password", true);
}
}
public MinimalLogonViewModel(MinimalLogonView view,IWebService webService)
{
_webService = webService;
View = view;
view.DataContext = this;
loaderTabs = (TabControl)this.View.FindName("loaderTabs");
}
catch (Exception eX)
{
MessageBox.Show(eX.Message);
}
}
protected virtual void OnPropertyChanged(string propertyName, bool raiseCanExecute)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
if (raiseCanExecute)
LogonCommand.RaiseCanExecuteChanged();
}
private void Logon(object parameter)
{
SetActiveTab(TabType.Loader);
_messageBroker.onAuthenticated += new EventHandler(_MessageBroker_onAuthenticated);
Task.Execute((DispatcherWrapper)View.Dispatcher,
() => _webService.Authenticate(userName, password.ConvertToUnsecureString()),
(ex) =>
{
if (ex != null)
{
//This is where I'm having issues
//If an error occurs I want to switch back to the Login tab which will enable the user to try Login again
//This does not throw an error but it also doesn't show the Login tab
SetActiveTab(TabType.Login);
}
else
{
//No error perform additional processing
}
});
}
private void SetActiveTab(TabType type)
{
//If I leave the code as simply:
//loaderTabs.SelectedIndex = (int)type;
//I get an error when seting the tab for the second time:
//Invalid Operation Exception (The calling thread cannot access this object because a different thread owns it.)
loaderTabs.Dispatcher.Invoke((Action)(() =>
{
loaderTabs.SelectedIndex = (int)type;
}));
}
}
I'm no WPF expert but I'm wondering why you would use the dispatcher object for this functionality surely you could just do this?
private void SetActiveTab(TabType type)
{
loaderTabs.SelectedIndex = (int)type;
}
EDIT:
Ok I fully understand now why you would use the dispatcher duh. I tried the bits on your code while processing on a seperate thread and it worked for me.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private BackgroundWorker _worker;
public MainWindow()
{
InitializeComponent();
_worker = new BackgroundWorker();
_worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
}
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Done");
}
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(5000);
}
private void SetActiveTab(TabType type)
{
loaderTabs.Dispatcher.Invoke((Action)(() =>
{
//This is where the error happens when I try set the active tab back to tab1
loaderTabs.SelectedIndex = (int)type;
}));
}
public void Login(string userName, string password)
{
try
{
SetActiveTab(TabType.Loader);
//Processing...
_worker.RunWorkerAsync();
}
catch (Exception)
{
SetActiveTab(TabType.Login);
}
}
enum TabType {Login, Loader};
private void Button_Click(object sender, RoutedEventArgs e)
{
Login("user", "password");
}
}
}
Why are you using a tab control? It is unnecessary.
You can implement this using a Grid and toggling the Visibility property using an IValueConverter.
<Grid>
<Grid x:Name="Login" Visibility="{Binding Path=IsProcessing, Converter={StaticResource InvertBooleanToVisilityConverter}}">
<!-- Your login fields -->
</Grid>
<Grid x:Name="Status" Visibility="{Binding Path=IsProcessing, Converter={StaticResource BooleanToVisilityConverter}}">
<!-- Your login status -->
</Grid>
</Grid>
IsProcessing is a simple boolean property that notifies on property changed and the two IValueConverters are simply converting this boolean to a equivalent Visibility value.
I use this pattern all the time to create complex interfaces that can show multiple states (I achieve this by chaining an EnumToBool converter with a BoolToVisibility).
Also, to directly answer the question regarding thread ownership and it's possible causes. You are most likely calling the SetActiveTab for the second time (e.g. to switch back to the first tab) from another thread. You need to invoke back onto the main thread (the one that created the control) before trying to modify any properties; there is a lot of information on how to do this already on StackOverflow and Google. In nearly every case that I've encounted, the line where the exception is thrown is not where you the problem is, you need to walk back up the callstack.
I have three forms named frmBase(form1),frmGraph(form2) and frmBalloon(form3).
frmGraph(form2) opens if the user clicks on the button named btnShow placed on frmBase(form1).
frmBalloon(form3) opens if the user clicks on the button named btnShow placed on frmGraph(form2).
Now if the user clicks on the button named btnCancel placed on the frmGraph(form2) OR clicks on the button named btnCancel placed on the frmBalloon(form3) every form that is open should be closed except frmBase(form1).
So, every forms should be closed except mainform when user clicks on the button placed on the form2 or form3. So for that is there any solution?
Maintain references of all form objects that needs to be closed on designated event. Create and Call the function in frmBase whenever needed. The function will be responsible to close all registered forms if open.
Looks like an observer pattern case.
It is not a great solution, Application.OpenForms is a bit unreliable, but easy:
public static void CloseAllFormsButMain() {
for (int ix = Application.OpenForms.Count - 1; ix > 0; --ix)
Application.OpenForms[ix].Close();
}
Observer pattern is appropriate with some specializaiton. A more specialized version of the observer pattern for this type of scenario is the EventAggregator pattern. The event aggregator pattern is ideal for this type of scenario.
In short the event aggregator allows you to centralize the publishing/subscription of events. Therefore all subscribers and publishers talk only to the EventAggregator. Subscribers subscribe to events and publishers command the event aggregator to publish something.
The event aggregator pattern also decouples each publisher subscriber from each other. This eliminates the need for the child forms to reference the parent forms.
Jeremy Miller provides a good example in his Build Your Own Cab series. Due to my new membership I cant post the links to the sites but just a do a search for the following items.
EventAggregator by Martin Fowler
Build Your Own CAB series by Jeremy Miller (codebetter.com)
EventAggregator in PRISM
Here is a simple example I cooked up using C# and generics. This is by no means complete. It is just to illustrate a simplified example. For a more complete patter turn to Jeremy Millers example.
[code]
//Sample Custom Event args
using System;
namespace EventAggregatorPatternDemo
{
public class CloseAllFormsEventArgs : EventArgs
{
}
}
//Sample Form Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace EventAggregatorPatternDemo
{
public partial class GraphForm : Form, IListener
{
public GraphForm()
{
InitializeComponent();
}
private void GraphForm_Load(object sender, EventArgs e)
{
EventAggregator.GetTheEventAggregator().Subscribe<CloseAllFormsEventArgs>(this);
}
public void Handle<TEventArgs>(TEventArgs e)
{
if (e.GetType() == typeof(CloseAllFormsEventArgs))
{
this.Close();
}
}
private void btnCloseAll_Click(object sender, EventArgs e)
{
EventAggregator.GetTheEventAggregator().Publish(this, new CloseAllFormsEventArgs());
}
private void GraphForm_FormClosed(object sender, FormClosedEventArgs e)
{
EventAggregator.GetTheEventAggregator().CancelSubscription<CloseAllFormsEventArgs>(this);
}
}
}
//Event Aggregator code
using System;
using System.Collections.Generic;
namespace EventAggregatorPatternDemo
{
public interface IListener
{
void Handle(TEventArgs e);
}
public class EventAggregator
{
static EventAggregator _TheEventAggregator;
readonly Dictionary<Type, List<IListener>> _listeners;
private EventAggregator()
{
_listeners = new Dictionary<Type, List<IListener>>();
}
public static EventAggregator GetTheEventAggregator()
{
if(_TheEventAggregator == null)
{
_TheEventAggregator = new EventAggregator();
}
return _TheEventAggregator;
}
public void Publish<TEventArgs>(object sender, TEventArgs e)
{
if(_listeners.ContainsKey(e.GetType()))
{
var listOfSubscribers = _listeners[e.GetType()];
for(int i = listOfSubscribers.Count - 1; i > -1; i--)
{
listOfSubscribers[i].Handle(e);
}
}
}
public void Subscribe<TEventArgs>(IListener listener)
{
if(_listeners.ContainsKey(typeof(TEventArgs)))
{
_listeners[typeof(TEventArgs)].Add(listener);
}
else
{
List<IListener> newListenerList = new List<IListener>();
newListenerList.Add(listener);
_listeners.Add(typeof(TEventArgs), newListenerList);
}
}
//Cancels all subscriptions
public void CancelSubscription<TEventArgs>(IListener listener)
{
Type eventArgsType = typeof(TEventArgs);
if (_listeners.ContainsKey(eventArgsType))
{
//Remove from the end
for (int i = _listeners[eventArgsType].Count-1; i > -1; i-- )
{
//If the objects are the same
if(ReferenceEquals(_listeners[eventArgsType][i], listener))
{
_listeners[eventArgsType].RemoveAt(i);
}
}
}
}
}
}
[/code]
Have you thought about using a static event? Here is a simple example.
How do I bind a ProgressBar to a property of a class updated in another thread?
The following code example shows my first naive attempt. It doesn't work because I get runtime errors about cross thread communication. I think I need to use Invoke in some way, but I'm not sure how to do it with the Binding class.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;
class ProgressForm : Form
{
private ProgressBar pbProgress;
public ProgressForm(ref LongOp lo)
{
Binding b = new Binding("Value", lo, "Progress");
pbProgress = new ProgressBar();
pbProgress.DataBindings.Add(b);
this.Controls.Add(pbProgress);
}
}
class Program : Form
{
private Button btnStart;
private LongOp lo;
public Program()
{
lo = new LongOp();
btnStart = new Button();
btnStart.Text = "Start long operation";
btnStart.Click += new EventHandler(btnStart_Click);
this.Controls.Add(btnStart);
}
private void btnStart_Click(object sender, EventArgs e)
{
ProgressForm pf = new ProgressForm(ref lo);
lo.DoLongOp();
pf.ShowDialog();
}
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Program());
}
}
class LongOp : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int progress;
public void DoLongOp()
{
Thread thread = new Thread(new ThreadStart(this.run));
thread.Start();
}
public void run()
{
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(1000);
Progress++;
}
}
public int Progress
{
get
{
return progress;
}
set
{
progress = value;
NotifyPropertyChanged("Progress");
}
}
private void NotifyPropertyChanged(String field)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(field));
}
}
}
So how do I bind a ProgressBar to a value updated in another thread?
Thanks in advance
EDIT: I've switched to using the ThreadedBinding implementation Mr. Gravell wrote and linked to. I'm still getting the cross thread exception though. Pressing "Break" in the exception dialog highlights the PropertyChanged(this, new PropertyChangedEventArgs(field)); line as the line causing the exception.
What more do I need to change?
EDIT: Looks like Mr. Gravell's post has been removed. The ThreadedBinding implementation I mentioned can be found at the end of this thread: http://groups.google.com/group/microsoft.public.dotnet.languages.csharp/browse_thread/thread/69d671cd57a2c7ab/2f078656d6f1ee1f?pli=1
I've switched back to plain old Binding in the example for easier compilation by others.
Unfortunately I think the cross-threading issues will make data-binding proper a bit too clumsy to use here, and probably more complexity than you need in any case -- the data only needs to be plumbed one way.
You could just replace the binding with an event handler like this:
private void ProgressPropertyChangedHandler(object sender,
PropertyChangedEventArgs args)
{
// fetch property on event handler thread, stash copy in lambda closure
var progress = LongOp.Progress;
// now update the UI
pbProgress.Invoke(new Action(() => pbProgress.Value = progress));
}