0

我正在开发一个 WPF 登录表单。我有一个带有两个选项卡的选项卡控件:

tab1) 包含登录的输入(用户名和密码文本框/标签)

tab2) 包含一个自定义动画,用作进度条

一旦用户捕获了所有信息并在登录按钮的单击事件中单击登录,我将活动选项卡设置为 tab2 并向用户显示进度条。如果在此步骤中发生错误,我想将用户返回到 tab1,这就是我收到以下错误的地方:

无效操作异常(调用线程无法访问此对象,因为不同的线程拥有它。)

请建议我如何杀死线程或任何其他解决方法来帮助解决我的问题

我的代码:

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

2 回答 2

1

我不是 WPF 专家,但我想知道为什么你会使用调度程序对象来实现这个功能,你肯定可以这样做吗?

private void SetActiveTab(TabType type) 
{
  loaderTabs.SelectedIndex = (int)type; 
}

编辑:

好的,我现在完全理解您为什么要使用调度程序了。我在单独的线程上处理时尝试了您的代码位,它对我有用。

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

   }
}
于 2012-08-29T20:40:35.107 回答
0

为什么要使用选项卡控件?这是不必要的。

您可以使用 a 来实现这一点,并使用 a 来Grid切换Visibility属性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是一个简单的布尔属性,通知属性更改,两者IValueConverters只是将此布尔值转换为等效Visibility值。

我一直使用这种模式来创建可以显示多种状态的复杂界面(我通过将EnumToBool转换器与 a链接来实现这一点BoolToVisibility)。


另外,直接回答有关线程所有权的问题及其可能的原因。您很可能SetActiveTab是第二次从另一个线程调用 (例如切换回第一个选项卡)。在尝试修改任何属性之前,您需要调用回主线程(创建控件的线程);StackOverflow 和 Google 上已经有很多关于如何做到这一点的信息。在我遇到的几乎所有情况下,引发异常的行都不是问题所在,您需要返回调用堆栈。

于 2012-08-29T21:54:57.097 回答