0
votes

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;
        }));
    }
}
2
Which line does the exception happen on? - Dan Puzey
Do you start a Thread or Task somewhere ? - Henk Holterman

2 Answers

1
votes

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");
        }

   }
}
0
votes

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.