How to check a form is still 'alive' - c#

I am loading a big treeview in a seperate thread. This thread starts at the load event of a form.
All goes well, until an error occurs in the load event. When an error occurs I close the form and the thread that loads my treeview must be aborted. But I don't know how to do this.
The problem is, that the form is closed and the thread is still working, so I get an InvalidOperationException. The program breaks down and this part of the thread is highlighted:
tvQuestionnaire.Invoke((MethodInvoker)delegate
{
tvQuestionnaire.Nodes.Add(catNode);
});
The treeview on my form is called tvQuestionnaire. The whole function (which is called in my background worker) looks like this:
private void SetTreeviewData()
{
// Get all categories
List<Category> categories = _questionnaire.GetCategoriesFromQuestionnaire();
// Get all questions which are retrieved by the question manager
OrderedDictionary all_ordered_questions = _questionManager.AllQuestions;
// Store all the questions in a List<T>
List<Question> all_questions = new List<Question>();
foreach (DictionaryEntry de in all_ordered_questions)
{
Question q = de.Value as Question;
all_questions.Add(q);
}
foreach (Category category in categories)
{
// Create category node
TreeNode catNode = new TreeNode();
catNode.Text = category.Description;
catNode.Tag = category;
catNode.Name = category.Id.ToString();
// Get all questions which belongs to the category
List<Question> questions = all_questions.FindAll(q => q.CategoryId == category.Id);
// Default set the font to bold (Windows issue)
Font font = new Font(tvQuestionnaire.Font, FontStyle.Regular);
foreach (Question question in questions)
{
// Create question node
TreeNode queNode = new TreeNode();
queNode.Text = question.Question;
queNode.Tag = question;
queNode.Name = "Q" + question.Id;
queNode.NodeFont = font;
// Determine which treenode icon to show
SetTreeNodeIcon(ref queNode, question);
// Add node to category node
catNode.Nodes.Add(queNode);
}
if (_closing)
return;
// Add category node to treeview
tvQuestionnaire.Invoke((MethodInvoker)delegate
{
tvQuestionnaire.Nodes.Add(catNode);
// Now the category (and thus the questions) are added to treeview
// Set questions treenode icon
//SetTreeNodeIcon(questions);
});
}
// Set each category under its parent
for (int i = tvQuestionnaire.Nodes.Count - 1; i >= 0; i--)
{
Category category = tvQuestionnaire.Nodes[i].Tag as Category;
TreeNode node = tvQuestionnaire.Nodes[i];
if (IsWindow(this.Handle.ToInt32()) == 0)
return;
tvQuestionnaire.Invoke((MethodInvoker)delegate
{
if (category.ParentId == null)
return;
else
{
// Find parent node
TreeNode[] parentNodes = tvQuestionnaire.Nodes.Find(category.ParentId.ToString(), true);
//Remove current node from treeview
tvQuestionnaire.Nodes.Remove(node);
parentNodes[0].Nodes.Insert(0, node);
}
});
}
}
This is the only method that my background worker calls.
So my question is, how can I prevent that the Exception occurs? How do I check the form where the treeview is on, is still 'alive'?

One solution would be to call the CancelAsync method of the backgroundworker (BGW) when you need to close the form. In the DoWork event handler, check at the beginning of the loop that cancellation has not been requested. If it was, exit the loop (and the DoWork handler).
In the form, wait for the BGW to complete (either success or cancellation)

Why not catch this event, than abort the thread's execution?

Check IsHandleCreated property of a form. If the form is still alive, it will true.

Related

Control.BeginInvoke - how do I *really* use it for threadsafe UIThread updates in winforms

I completely understand the thread logisitics of this question, so I know "what" I want to do but I struggle with C#'s implementation of it.
Here is the scenario: I want to pass an entire control in a thread safe manner to the UIThread for a form to append to a panel. This control needs to be generated somehow. Probably using a BeginInvoke. This would be great if I could get some help.
I have a feeling that if I pass a 3rd party class containing the control data that might be enough: here is the thread code:
/****
* scan the UIThread to decide if the frmVerdict needs to be shown,
* and if neccesary centre it upon the frmMain if that is also shown
* either way post an actionable update to frmVerdict or create it
*/
FormCollection fc = Application.OpenForms;
frmVerdict form = null;
frmMain parent = null;
foreach (Form frm in fc)
{
if (frm is frmVerdict)
{
form = (frmVerdict)frm;
}
if (frm is frmMain)
{
parent = (frmMain)frm;
}
}
if (form == null)
{
form = new frmVerdict();
if (parent != null)
{
form.StartPosition = FormStartPosition.Manual;
form.Location = new Point(parent.Location.X + (parent.Width - form.Width) / 2, parent.Location.Y + (parent.Height - form.Height) / 2);
}
form.ShowDialog(); //f.ShowDialog(this)
}
form.addAction(action);
form.BringToFront();
inside the form I have this code (among the other things inside frmVerdict)
public void addAction(classActionType action)
{
elementCounter++;
TextBox txtbx = new TextBox(); //TextBox txtbx = (TextBox)Controls["txtbx0001"];
txtbx.Text = action.thisFileOrFolder.folder.localFolder;
txtbx.Name = "txtbx" + elementCounter.ToString().PadLeft(4, '0');
txtbx.Location = new Point(10, 15 * elementCounter);
txtbx.Height = 20;
txtbx.Width = 50;
pnlVerdict.Controls.Add(txtbx); //this line crashes with Cross-thread operation not valid: Control 'pnlVerdict' accessed from a thread other than the thread it was created on.
thingsToDo.Add(action);
}
the idea was that the thread class would decide if the UIThread had an instance of the form frmVerdict and if so post an update to it in the form of a control, or if not just pop the frmVerdict up, this was to be a information response/required set. Then a decision required from the user can be queued. (imagine if a form appears and asks you 1 question then fade "growls" up another question, then it growls a third etc as the thread runs doing work in the background.)
The problem is I get Cross-thread operation not valid: Control 'pnlVerdict' accessed from a thread other than the thread it was created on. (which is what I would expect). What I need to do is Control.BeginInvoke the (classActionType action) on the UIThread but how do I do that in C#?

StatusStripLabel dynamically updated

My point is to display number of all child nodes at StatusStripLabel. My point is that I want StatusStripLabel to get updated everytime number of child nodes get's changed - I'll add or delete some of already exists. First, I placed code in Public Form, but it didn't worked as I expected. After some time I came with idea that actually works: I placed the code inside button method. But after that I realized that I'll need to place it in second place, in case of deleting node. So My question is: is there anything that can make it simplier? If my explanation isn't enough, just tell me, I'll try my best.
Code from Public Form (Because I want that counter to work from the beggining, not after I'll press the button)
childNodeCounter();
toolStripStatusLabel1.Text = "Number of games in database: " + NodeCounter.ToString();
Method:
public void childNodeCounter()
{
NodeCounter = 0;
foreach (TreeNode RootNode in treeView1.Nodes)
{
foreach (TreeNode ChildNode in RootNode.Nodes)
{
NodeCounter++;
}
}
toolStripStatusLabel1.Text = "Number of games in database: " + NodeCounter.ToString();
}
Code inside button method:
private void button1_Click(object sender, EventArgs e)
{
NodeCounter = 0;
foreach (TreeNode RootNode in treeView1.Nodes)
{
foreach (TreeNode ChildNode in RootNode.Nodes)
{
NodeCounter++;
}
}
toolStripStatusLabel1.Text = "Number of games in database: " + NodeCounter.ToString();
}
Edit: Thanks to mr. Hans Passant I wrote this and it works very well:
public int childNodeCounter(TreeNodeCollection nodes)
{
int count = 0;
foreach (TreeNode RootNode in nodes)
{
foreach (TreeNode ChildNode in RootNode.Nodes)
count++;
}
return count;
Event handler looks like this:
toolStripStatusLabel1.Text = "Number of games in database: " + childNodeCounter(treeView1.Nodes);
Three tiny optimizations
Rather that iterate the tree yourself, just use ChildNode.Nodes.GetNodeCount
Rather than repeat the same logic in different places, have your button Click events simply call the UpdateNodeCount() method.
The text initializer in the first code fragment is redundant, and can be eliminated: the call to childNodeCounter already does the status label update.
The natural way to traverse a tree structure is by using recursion. That's always a bit hard to reason through but there are lots of resources available. Doing it iteratively is a lot uglier, you have to use a Stack<> to allow you to backtrack again out of a nested node. I'll therefore post the recursion solution:
private static int CountNodes(TreeNodeCollection nodes) {
int count = nodes.Count;
foreach (TreeNode node in nodes) count += CountNodes(node.Nodes);
return count;
}
Then your event handler becomes:
private void button1_Click(object sender, EventArgs e) {
toolStripStatusLabel1.Text = "Number of games in database: " +
CountNodes(treeView1.Nodes);
}
If you're adding and removing "game" nodes to the treeView, you must be having methods like void AddGame(string title) and void RemoveGame(string title) which add/remove (child) nodes - those whose total number you want to count. If I understood well, you want toolStripStatusLabel1.Text to be automatically updated each time the number of child nodes changes. In that case you can add field
private int nodesCount;
to your Form class and have something like this:
void AddGame(string title)
{
if(InvokeRequired)
{
Invoke(new MethodInvoker(delegate() { AddGame(title); }));
}
else
{
AddGameNodeToTreeView(title); // add new game node to desired place in TreeView
nodesCount++; // increase node counter
toolStripStatusLabel1.Text = "Number of games in database: " + nodesCount;
}
}
RemoveGame() would be implemented in the same way (or joined with AddGame() into a single method with one additional argument - bool add). Both methods could be extended if you're adding/removing multiple nodes in which case you'll be passing title array and updating nodesCount accordingly.
The advantage of this approach is that you don't have to count nodes in the tree each time before updating toolStripStatusLabel1.Text. Also, toolStripStatusLabel1.Text is updated automatically, not only when user clicks the button.
Drawback is that nodesCount is somewhat redundant information: total number of nodes of interest is 'hidden' in the treeView. You have to make sure that nodesCount is in sync with the actual number of nodes.

Update treeview from another thread

I'm quite new to threads in c# (WPF) and since I've implemented some label and progressbar update successfully, I do not understand Why when I try to add items to the treeView of my GUI from another class called in a separate thread I get an exception:
An unhandled exception of type 'System.InvalidOperationException'
occurred in WindowsBase.dll
Additional information: The calling thread cannot access this object
because a different thread owns it.
My update treeview code is this:
private void updateTreeView(TreeView tree, List<TreeViewItem> items, Boolean clear) {
tree.Dispatcher.Invoke(new Action(() => {
if (clear) {
tree.Items.Clear();
}
ItemCollection treeitems = tree.Items;
foreach (TreeViewItem item in items) {
treeitems.Dispatcher.Invoke(new Action(() => {
treeitems.Add(item);
}));
}
tree.ItemsSource = treeitems;
}));
}
And the exception points at the line:
treeitems.Add(item);
Thanks in advance.
you can use the following :
delegate void DUpdateTreeView(TreeView tree, List<TreeViewItem> items, Boolean clear);
private void UpdataTreeView(TreeView tree, List<TreeViewItem> items, Boolean clear)
{
if (tree.InvokeRequired)
{
DUpdateTreeView d = new DUpdateTreeView(UpdataTreeView);
// replace this by the main form object if the function doesn't in the main form class
this.Invoke(d, new object[] { tree, items, clear });
}
else
{
if (clear)
{
tree.Items.Clear();
}
else
{
// Here you can add the items to the treeView
/***
ItemCollection treeitems = tree.Items;
foreach (TreeViewItem item in items)
{
treeitems.Dispatcher.Invoke(new Action(() =>
{
treeitems.Add(item);
}));
}
tree.ItemsSource = treeitems;
***/
}
}
}
This is a really old question but I figured I would answer it. You have two dispatchers in your sample. You have a treeview that you are getting its thread and a list that seems to be created in a different thread.
But the code should look more like this. Sorry about the VB in this case I'm using a delegate inside the invoke.
tree.Dispatcher.BeginInvoke(Sub()
Dim node = new TreeViewItem() With {.Header = "Header"}
tree.items.add(node)
End Sub)
I am not jumping out of the UI thread to add the node like in the original question.

Displaying a "User control is loading" message while loading a User Control

I have a Winforms Application with a TabStrip Control. During runtime, UserControls are to be loaded into different tabs dynamically.
I want to present a "User Control xyz is loading" message to the user (setting an existing label to visible and changing its text) before the UserControl is loaded and until the loading is completely finished.
My approaches so far:
Trying to load the User Control in a BackgroundWorker thread. This fails, because I have to access Gui-Controls during the load of the UserControl
Trying to show the message in a BackgroundWorker thread. This obviously fails because the BackgroundWorker thread is not the UI thread ;-)
Show the Message, call DoEvents(), load the UserControl. This leads to different behaviour (flickering, ...) everytime I load a UserControl, and I can not control when and how to set it to invisible again.
To sum it up, I have two questions:
How to ensure the message is visible directly, before loading the User control
How to ensure the message is set to invisible again, just in the moment the UserControl is completely loaded (including all DataBindings, grid formattings, etc.)
what we use is similar to this:
create a new form that has whatever you want to show the user,
implement a static method where you can call this form to be created inside itself, to prevent memory leaks
create a new thread within this form so that form is running in a seperated thread and stays responsive; we use an ajax control that shows a progress bar filling up.
within the method you use to start the thread set its properties to topmost true to ensure it stays on top.
for instance do this in your main form:
loadingForm.ShowLoadingScreen("usercontrollname");
//do something
loadingform.CloseLoadingScreen();
in the loading form class;
public LoadingScreen()
{
InitializeComponent();
}
public static void ShowLoadingScreen(string usercontrollname)
{
// do something with the usercontroll name if desired
if (_LoadingScreenThread == null)
{
_LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
_LoadingScreenThread.IsBackground = true;
_LoadingScreenThread.Start();
}
}
public static void CloseLoadingScreen()
{
if (_ls.InvokeRequired)
{
_ls.Invoke(new MethodInvoker(CloseLoadingScreen));
}
else
{
Application.ExitThread();
_ls.Dispose();
_LoadingScreenThread = null;
}
}
private static void DoShowLoadingScreen()
{
_ls = new LoadingScreen();
_ls.FormBorderStyle = FormBorderStyle.None;
_ls.MinimizeBox = false;
_ls.ControlBox = false;
_ls.MaximizeBox = false;
_ls.TopMost = true;
_ls.StartPosition = FormStartPosition.CenterScreen;
Application.Run(_ls);
}
Try again your second approach:
Trying to show the message in a BackgroundWorker thread. This obviously fails because the BackgroundWorker thread is not the UI thread ;-)
But this time, use the following code in your background thread in order to update your label:
label.Invoke((MethodInvoker) delegate {
label.Text = "User Control xyz is loading";
label.Visible = true;
});
// Load your user control
// ...
label.Invoke((MethodInvoker) delegate {
label.Visible = false;
});
Invoke allows you to update your UI in another thread.
Working from #wterbeek's example, I modified the class for my own purposes:
center it over the loading form
modification of its opacity
sizing it to the parent size
show it as a dialog and block all user interaction
I was required to show a throbber
I received a null error on line:
if (_ls.InvokeRequired)
so I added a _shown condition (if the action completes so fast that the _LoadingScreenThread thread is not even run) to check if the form exists or not.
Also, if the _LoadingScreenThread is not started, Application.Exit will close the main thread.
I thought to post it for it may help someone else. Comments in the code will explain more.
public partial class LoadingScreen : Form {
private static Thread _LoadingScreenThread;
private static LoadingScreen _ls;
//condition required to check if the form has been loaded
private static bool _shown = false;
private static Form _parent;
public LoadingScreen() {
InitializeComponent();
}
//added the parent to the initializer
//CHECKS FOR NULL HAVE NOT BEEN IMPLEMENTED
public static void ShowLoadingScreen(string usercontrollname, Form parent) {
// do something with the usercontroll name if desired
_parent = parent;
if (_LoadingScreenThread == null) {
_LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
_LoadingScreenThread.SetApartmentState(ApartmentState.STA);
_LoadingScreenThread.IsBackground = true;
_LoadingScreenThread.Start();
}
}
public static void CloseLoadingScreen() {
//if the operation is too short, the _ls is not correctly initialized and it throws
//a null error
if (_ls!=null && _ls.InvokeRequired) {
_ls.Invoke(new MethodInvoker(CloseLoadingScreen));
} else {
if (_shown)
{
//if the operation is too short and the thread is not started
//this would close the main thread
_shown = false;
Application.ExitThread();
}
if (_LoadingScreenThread != null)
_LoadingScreenThread.Interrupt();
//this check prevents the appearance of the loader
//or its closing/disposing if shown
//have not found the answer
//if (_ls !=null)
//{
_ls.Close();
_ls.Dispose();
//}
_LoadingScreenThread = null;
}
}
private static void DoShowLoadingScreen() {
_ls = new LoadingScreen();
_ls.FormBorderStyle = FormBorderStyle.None;
_ls.MinimizeBox = false;
_ls.ControlBox = false;
_ls.MaximizeBox = false;
_ls.TopMost = true;
//get the parent size
_ls.Size = _parent.Size;
//get the location of the parent in order to show the form over the
//target form
_ls.Location = _parent.Location;
//in order to use the size and the location specified above
//we need to set the start position to "Manual"
_ls.StartPosition =FormStartPosition.Manual;
//set the opacity
_ls.Opacity = 0.5;
_shown = true;
//Replaced Application.Run with ShowDialog to show as dialog
//Application.Run(_ls);
_ls.ShowDialog();
}
}

Need help implementing multithreading into my TreeView (C#, WPF)

Okay, I have a TreeView that serves as a directory tree for Windows. I have it loading all the directories and functioning correctly, but it is pausing my GUI while it loads directories with many children. I'm trying to implement multithreading, but am new to it and am having no luck.
This is what I have for my TreeView:
private readonly object _dummyNode = null;
public MainWindow()
{
InitializeComponent();
foreach (string drive in Directory.GetLogicalDrives())
{
DriveInfo Drive_Info = new DriveInfo(drive);
if (Drive_Info.IsReady == true)
{
TreeViewItem item = new TreeViewItem();
item.Header = drive;
item.Tag = drive;
item.Items.Add(_dummyNode);
item.Expanded += folder_Expanded;
TreeViewItemProps.SetIsRootLevel(item, true);
Dir_Tree.Items.Add(item);
}
}
}
private void folder_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem item = (TreeViewItem)sender;
if (item.Items.Count == 1 && item.Items[0] == _dummyNode)
{
item.Items.Clear();
try
{
foreach (string dir in Directory.GetDirectories(item.Tag as string))
{
DirectoryInfo tempDirInfo = new DirectoryInfo(dir);
bool isSystem = ((tempDirInfo.Attributes & FileAttributes.System) == FileAttributes.System);
if (!isSystem)
{
TreeViewItem subitem = new TreeViewItem();
subitem.Header = tempDirInfo.Name;
subitem.Tag = dir;
subitem.Items.Add(_dummyNode);
subitem.Expanded += folder_Expanded;
subitem.ToolTip = dir;
item.Items.Add(subitem);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
Whenever I expand a directory that has a large number of subdirectories, the program appears to be frozen for a few seconds. I would like to display a loading message or animation while it's processing, but I'm not sure how to begin with multithreading. I know I have to use the TreeView's Dispatcher.BeginInvoke method, but other than that I'm kinda lost.
Any help would be greatly appreciated!!!
One of the easiest ways to start a async process is to use an anonymous delegate with BeginInvoke. As an example you could move your code in the constructor to a separate method (say RenderTreeView) and then call it asynchronously to begin a new thread as follows:
Action action = RenderTreeView;
action.BeginInvoke(null, null);
The trick to this is that any time you interact with any UI elements from the async process you need to rejoin the main UI thread, otherwise you will get an exception about cross thread access. This is relatively straight forward as well.
In Windows Forms it's:
if (InvokeRequired)
Invoke(new MethodInvoker({item.Items.Add(subitem)}));
else
item.Items.Add(subitem);
In WPF it's:
if (!Dispatcher.CheckAccess())
Dispatcher.Invoke(new Action(() => item.Items.Add(subitem)));
else
item.Items.Add(subitem);
You really need to break up the code to make it more flexible in terms of methods. At the moment everything is bundled in one method which makes it hard to work with and re-factor for async processes.
Update Here you go :)
public partial class MainWindow : Window
{
private readonly object dummyNode = null;
public MainWindow()
{
InitializeComponent();
Action<ItemCollection> action = RenderTreeView;
action.BeginInvoke(treeView1.Items, null, null);
}
private void RenderTreeView(ItemCollection root)
{
foreach (string drive in Directory.GetLogicalDrives())
{
var driveInfo = new DriveInfo(drive);
if (driveInfo.IsReady)
{
CreateAndAppendTreeViewItem(root, drive, drive, drive);
}
}
}
private void FolderExpanded(object sender, RoutedEventArgs e)
{
var item = (TreeViewItem) sender;
if (item.Items.Count == 1 && item.Items[0] == dummyNode)
{
item.Items.Clear();
var directory = item.Tag as string;
if (string.IsNullOrEmpty(directory))
{
return;
}
Action<TreeViewItem, string> action = ExpandTreeViewNode;
action.BeginInvoke(item, directory, null, null);
}
}
private void ExpandTreeViewNode(TreeViewItem item, string directory)
{
foreach (string dir in Directory.GetDirectories(directory))
{
var tempDirInfo = new DirectoryInfo(dir);
bool isSystem = ((tempDirInfo.Attributes & FileAttributes.System) == FileAttributes.System);
if (!isSystem)
{
CreateAndAppendTreeViewItem(item.Items, tempDirInfo.Name, dir, dir);
}
}
}
private void AddChildNodeItem(ItemCollection collection, TreeViewItem subItem)
{
if (Dispatcher.CheckAccess())
{
collection.Add(subItem);
}
else
{
Dispatcher.Invoke(new Action(() => AddChildNodeItem(collection, subItem)));
}
}
private void CreateAndAppendTreeViewItem(ItemCollection items, string header, string tag, string toolTip)
{
if (Dispatcher.CheckAccess())
{
var subitem = CreateTreeViewItem(header, tag, toolTip);
AddChildNodeItem(items, subitem);
}
else
{
Dispatcher.Invoke(new Action(() => CreateAndAppendTreeViewItem(items, header, tag, toolTip)));
}
}
private TreeViewItem CreateTreeViewItem(string header, string tag, string toolTip)
{
var treeViewItem = new TreeViewItem {Header = header, Tag = tag, ToolTip = toolTip};
treeViewItem.Items.Add(dummyNode);
treeViewItem.Expanded += FolderExpanded;
return treeViewItem;
}
}
Multithreading may not help much here because the TreeView has to be updated on it's Dispatcher thread.
TreeViews will pause when loading a large number of entries. One way to get around this is to store the contents into an object that mirrored the TreeView structure, and then programmatically load just the first level of the TreeView.
When a user clicks on a node, load the next level of child nodes and expand the node. When that node is collapsed, delete its child nodes to conserve TreeView memory. This has worked well for me. For exceedingly large structures I've used a local Sqlite database (via System.Data.Sqlite) as my backing store and even then the TreeView loaded quickly and was responsive.
You can also look at using BackgroundWorker; that's easiest way of executing an operation on a separate thread(For me :) ).
BackgroundWorker Component Overview: http://msdn.microsoft.com/en-us/library/8xs8549b.aspx
BackgroundWorker Class: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
You can use it with Command pattern as explained here -
Asynchronous WPF Commands

Categories

Resources