从主线程调用WPF窗口中的方法

时间:2013-12-16 19:45:09

标签: c# wpf multithreading

用例
我正在开发一个C#中的小应用程序,该应用程序由另一个应用程序调用以从Internet检索数据。它作为一个进程独立运行,但几乎所有与它的交互都由调用应用程序管理。因此它没有GUI。但是,我想在某些数据检索过程中使用WPF添加进度条,这可能需要一分钟。很容易估计完成了多少工作以及剩下多少工作量,因此我找到了一个合适的进度条。

完成研究
在阅读Albahari的pdf大部分线程(http://www.albahari.info/threading/threading.pdf)之后,我对线程有了一个很好的理解。在此问题上,我还阅读了很多关于SO和MSDN的帖子。大多数帖子建议使用后台工作人员进行耗时的数据检索,同时将GUI保留在主线程中,并因此建议使用后台工作者的解决方案。在这种情况下,这感觉很尴尬,主要任务是数据检索,而不是GUI交互。

我花了一些时间试图理解不同的教程和论坛帖子,同时试图使他们符合我的问题,但我没有成功,现在我几乎回到原点。基本上我想最终得到以下两个类:

ProgressBarWindow

public partial class ProgressBarWindow : Window
{
    public ProgressBarWindow()
    {
        InitializeComponent();
    }

    public void setValue(int value) 
    {
        // This function should be available from the main thread
    }
}

查询器

Public class Querier
{



    public List<Item> getItems()
    {
        // call ProgressBarWindow.setValue(0);
        ...
        // call ProgressBarWindow.setValue(100);
        // call ProgressBarWindow.Close();
    }
}

我的理解是UI必须在单个线程下运行,因此我的ProgressBarWindow对象无法在新线程中实例化,同时可用于主线程(类型)。

Dispatcher.BeginInvoke似乎是我的救世主,但到目前为止,我还未能弄清楚Querier课程应该进入什么以及ProgressBarWindow课程中要做什么。如何让两个线程与ProgressBarWindow的同一个实例进行交互?

请询问您是否需要更多详情,我会尽力澄清。

3 个答案:

答案 0 :(得分:1)

您可以使用Progress类使用长时间运行操作的当前进度来更新UI。

首先在您的用户界面中创建Progress的实例:

Progress<int> progress = new Progress<int>(currentProgress =>
{
    progressBar.Value = currentProgress;
    //todo do other stuff
});

然后将其传递给长时间运行的过程:

public List<Item> getItems(IProgress<int> progress)
{
    progress.Report(0);
    //todo do something
    progress.Report(100);
}

答案 1 :(得分:0)

这是我通常使用的通用函数:

    public static void Invoke(this UIElement element,Action action)
    {
        element.Dispatcher.Invoke(action, null);
    }

要使用它,只需致电:

this.Invoke(() => ProgressBarWindow.SetValue(0));

因此,在getItems()函数中,你会得到以下内容:

public List<Item> getItems()
{
    ProgressBarWindow wnd;
    MainWindow.Invoke(() => wnd = new ProgressBarWindow())
    MainWindow.Invoke(() => wnd.SetValue(0))
    ...
    MainWindow.Invoke(() => wnd.SetValue(100))
    MainWindow.Invoke(() => wnd.Close())
}

确保总是有办法到达主窗口的任何东西(从App.xml或App.Run(...)运行的那个。然后你可以通过它发出任何GUI动作(即使你必须创建一个新的Loader窗口,例如,只要它在主线程中完成)

答案 2 :(得分:0)

  

的App.xaml

public partial class App : Application
{
    private void Application_Startup_1(object sender, StartupEventArgs e)
    {
        Task.Factory.StartNew<List<int>>(() => Querier.GetItems());
    }
}
  

ProgressBarWindow.xaml.cs

public partial class ProgressWindow : Window
{
    public ProgressWindow()
    {
        InitializeComponent();
        Querier.Start +=()=> Visibility = Visibility.Visible;
        Querier.Stop += () => Visibility = Visibility.Collapsed;
        Querier.ReportProgress +=OnReportProgress;
    }

    public void OnReportProgress(int value)
    {
        txtBox.Text = value.ToString();
    }
}
  

ProgressBarWindow.xaml

<Grid>
    <TextBox x:Name="txtBox"></TextBox>
</Grid>
  

查询器

public class Querier
{
    public static event Action Start;

    public static event Action Stop;

    public static event Action<int> ReportProgress;

    public static List<int> GetItems()
    {
        if (Start != null)
            App.Current.Dispatcher.BeginInvoke(Start,null);

        for (int index = 0; index <= 10; index++)
        {
            Thread.Sleep(200);
            if (ReportProgress != null)
                App.Current.Dispatcher.BeginInvoke(ReportProgress, index*10);
        }


        if (Stop != null)
            App.Current.Dispatcher.BeginInvoke(Stop, null);

        return Enumerable.Range(1, 100).ToList();

    }

}

我只想提出一个想法,希望这会有所帮助。