WPF TabControl - cannot access this object because a different thread owns it - c#

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.

Related

Show always active form in winforms

I need to show form that will be always on a top level.
I use TopMost and TopLevel flags and call Activate method after Show.
But another window might steal Activate flag.
How I can fix it so that after creating a window, other windows cannot become active until this window is closed?
upd: it work only if execute app from output folder and don't work if run app with debug from IDE.
The following code will open a form (TestForm) when the program is started. TestForm contains a button, that when clicked, will create a new form (a new instance of TestForm in the code below, but it could be a different form, if desired)--which won't be visible until the current form is closed. When the current form is closed, the new form will become visible and the original form will be disposed.
Try the following to see if it gives you the desired result:
Create a new class (TestFormEventArgs)
In Visual Studio menu, click Project
Select Add New Item
Select Class (Name: TestFormEventArgs.cs)
Click Add
TestFormEventArgs.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Activate2ndFormWhen1stFormCloses
{
public delegate void TestFormNewFormRequestedEventHandler(object sender, TestFormEventArgs e);
public class TestFormEventArgs : System.EventArgs
{
public int CurrentFormNumber { get; private set; } = 1;
public TestForm Frm { get; private set; } = null;
public TestFormEventArgs(TestForm frm, int currentFormNumber)
{
this.Frm = frm;
this.CurrentFormNumber = currentFormNumber;
}
}
}
Create a new Form (TestForm)
In Visual Studio menu, click Project
Select Add New Item
Select Form (Windows Forms) (Name: TestForm.cs)
Click Add
Add button to TestForm (btnOpenNewForm)
In Visual Studio menu, click View
Select Toolbox
Click Button
Drag mouse over the top of TestForm, and click the mouse to add the button to TestForm
In Visual Studio menu, click View
Select Properties Window
Click the button on the form
In the Properties Window, set the following properties: (Name): btnOpenNewForm; Text: Open New Form
On TestForm, double-click btnOpenNewForm which will create btnOpenNewForm_Click event handler.
Double-click TestForm to go to the code. Modify the code for TestForm.cs:
TestForm.cs
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;
namespace Activate2ndFormWhen1stFormCloses
{
public partial class TestForm : Form
{
public event TestFormNewFormRequestedEventHandler NewFormRequested;
private int _currentFormNumber = 1;
TestForm _frmOther = null;
public TestForm()
{
InitializeComponent();
//set value
this.Text = "TestForm 1";
}
public TestForm(string frmText, int currentFormNumber)
{
InitializeComponent();
//set value
this.Text = frmText;
this._currentFormNumber = currentFormNumber;
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void SendUpdates(TestForm frm, int currentFormNumber)
{
//check if there are subscribers
if (NewFormRequested != null)
{
//create a new instance of TestFormEventArgs
TestFormEventArgs valueArgs = new TestFormEventArgs(frm, currentFormNumber);
//raise event
NewFormRequested(this, valueArgs);
}//if
}
private void btnOpenNewForm_Click(object sender, EventArgs e)
{
string frmText = String.Format("TestForm {0}", _currentFormNumber + 1);
//create new instance
_frmOther = new TestForm(frmText, _currentFormNumber + 1);
//set properties
_frmOther.StartPosition = FormStartPosition.CenterScreen;
_frmOther.Visible = false;
SendUpdates(_frmOther, _currentFormNumber);
}
}
}
Change Program.cs code to the following:
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
namespace Activate2ndFormWhen1stFormCloses
{
static class Program
{
static TestForm _currentFrm = null;
static Queue<TestForm> _frmQ = new Queue<TestForm>();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//place form in Queue
_frmQ.Enqueue(new TestForm());
while(_frmQ.Count > 0)
{
//dequeue
_currentFrm = _frmQ.Dequeue();
//subscribe to events
_currentFrm.FormClosed += CurrentFrm_FormClosed;
_currentFrm.NewFormRequested += CurrentFrm_NewFormRequested;
Application.Run(_currentFrm);
}
}
private static void CurrentFrm_NewFormRequested(object sender, TestFormEventArgs e)
{
Debug.WriteLine("CurrentFrm_NewFormRequested: " + e.Frm.Text);
//add form to Queue
_frmQ.Enqueue(e.Frm);
}
private static void CurrentFrm_FormClosed(object sender, FormClosedEventArgs e)
{
TestForm frm = (TestForm)sender;
Debug.WriteLine("CurrentFrm_FormClosed: " + frm.Text);
try
{
//unsubscribe from events
_currentFrm.FormClosed -= CurrentFrm_FormClosed;
_currentFrm.NewFormRequested -= CurrentFrm_NewFormRequested;
_currentFrm.Dispose();
_currentFrm = null;
}
catch(Exception ex)
{
Debug.WriteLine("Error: " + ex.Message);
}
}
}
}
Try ShowDialog() instead of Show() method.
ShowDialog() will make the other form that is open Disabled and the current form is the only one that is Enabled.

Custom Busy overlay on WPF WizardControl using async await

I need load a overlay on a WPF wizardcontrol. I am using a busyIndicator tool from the wpf extended tooklit.
The code for async await works but the gui thread locks . I am trying add a please wait message when the await calls the function
private async void Button1_Click(object sender, RoutedEventArgs e)
{
BusyIndicator.IsBusy = true;
BusyIndicator.IsEnabled = true;
BusyIndicator.BusyContent = "Please wait while Site is provisioned";
await Task.Run(() =>
{
LongRunningFunction();
});
BusyIndicator.IsBusy=false;
}
The XAML for the BusyIndicator is as below.
<xctk:BusyIndicator x:Name="BusyIndicator" IsBusy="False" BusyContent="Please Wait">
</xctk:BusyIndicator>
The LonRunningFunction is a Webservice call which does not update the UI only returns a Bool value
public static bool LongRunningFunction(string URL)
{
bool IsPresent = CallWebservice()
return IsPresent;
}
Issue
1) The BusyIndicator does not seem to fire before the async call instead it seems to be fire when the LongRunning task completes
2) What is the correct process to call a gui overlay when async and await is used.
This is the way I tackled the problem with asynchronous calls.
Context:
Here I am using MvvM to show you good practice when working with WPF
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Threading;
class VM
{
Dispatcher _dispatcher = Dispatcher.CurrentDispatcher;
//Time consuming operation
private void LongTask()
{
Thread.Sleep(5000);
//in here if you need to send something to the UI thread like an event use it like so:
_dispatcher.Invoke(new Action(() =>
{
//some code here to invoke an event
if (ComponentsLoaded != null)
ComponentsLoaded(this, new EventArgs { });
}));
}
private ICommand _command;
//This is the command to be used instead of click event handler
public ICommand Command
{
get { return _command; }
private set { _command = value; }
}
//method associated with ICommand
void commandMethod(object parameter)
{
Busy = true;
ThreadPool.QueueUserWorkItem(new WaitCallback(multiThreadTask));
Busy = false;
}
//the task to be started on another thread
void multiThreadTask(object parameter)
{
LongTask();
}
public event EventHandler ComponentsLoaded;
}
This is what I use when working with multiple threads in WPF.
You can still use this in the code-behind just instantiate the Dispatcher and you're good to go.
If you need any more info just let us know. HTH

TreeView does not update when modified. Windows Forms

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.

Text not updating from another thread

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.

Not Able to Close form

Good morning,
I have several questions but I am not sure which is the important one to ask so I'll first state my overall problem. I can't close my Winform App. I have searched and found many answers but they either don't work, I don't understand or both.
If I do all my work and then call Application.Exit the form never closes. Same results if I put this.Close. However if I place a button on the form and call Application.Exit it closes the form.
I obviously do not understand the flow, I hope it is clear to someone what I am trying to do. As a non-programmer I have been piecing this project together for a few months and this is my last step - Close the form after work is complete if it was run from command line with arguments. I would try longer to work it out but my Visual Studio trial runs out this week so I turn to the experts :)
Thank you,
Todd
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ProgramCSToormTest
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(String[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//add if
if (args.Length == 0)
{
Application.Run(new Form1("Form"));
}
else
{
Application.Run(new Form1(args[0]));
}
}
}
}
Form1.cs
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;
namespace ProgramCSToormTest
{
public partial class Form1 : Form
{
string CLArg1;
string ReturnText;
public Form1(string Arg1)
{
InitializeComponent();
if (Arg1 != null)
{
CLArg1 = Arg1;
textBox1.Text = Display(CLArg1);
//button1.Enabled = false;
}
else
{
textBox1.Text = "click button to start";
}
Application.Exit(); //This seems to be ignored
}
public void button1_Click(object sender, EventArgs e)
{
CLArg1 = null;
textBox1.Text = Display("Hello World");
Application.Exit();
}
public string Display(string DisplayText)
{
if (CLArg1 != null)
{
ReturnText = CLArg1;
return(ReturnText);
}
else
{
ReturnText = DisplayText;
return(ReturnText);
}
}
}
}
See this question. Application.Close() only works if an application has been created. This is done by calling Application.Run(). Now. in your code you call Application.Exit() from the constructor of your form. Which is executed before the Application.Run() that is needed to create the application.
To fix this, either wait until after Application.Run(). Or, if you want to quit the application in the constructor use Environment.Exit(int statusCode). When using Environment.Exit(int statusCode) keep this in mind though.
You can't close the Application when the Form is Loading from the Program class. Try calling the Exit method after Loading the Form:
private void Form1_Load(object sender, EventArgs e)
{
if (CLArg1 != String.Empty)
Application.Exit();
}

Categories