在另一个线程上创建WPF进度窗口

7

我正在通过Revit建筑模型软件的API创建一个自定义添加命令。由于我的命令可能需要一些时间才能完成,因此我希望在它工作时向用户显示带有进度条的窗口。

通常,如果我要创建这样的进度窗口,它将位于主UI线程上,而实际的工作将在辅助工作线程上进行。然而,Revit要求任何对API的访问都必须通过调用自定义命令的线程进行。因此,我必须在第二个线程上创建我的进度条。

我找到了这篇关于如何在单独的线程中启动WPF窗口的博客文章,并以此为基础来解决问题。以下是我的自定义命令类。

public class SampleProgressWindowCommand : Autodesk.Revit.UI.IExternalCommand
{

    private ProgressWindow progWindow;
    internal static EventWaitHandle _progressWindowWaitHandle;


    public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
    {
        //Starts New Progress Window Thread
        using (_progressWindowWaitHandle = new AutoResetEvent(false))
        {

            //Starts the progress window thread
            Thread newprogWindowThread = new Thread(new ThreadStart(ShowProgWindow));
            newprogWindowThread.SetApartmentState(ApartmentState.STA);
            newprogWindowThread.IsBackground = true;
            newprogWindowThread.Start();

            //Wait for thread to notify that it has created the window
            _progressWindowWaitHandle.WaitOne();
        }

        //Does some work that takes a long time
        for (int i = 1; i <= 100; i++)
        {
            //Updates Progress
            this.progWindow.UpdateProgress("Item " + i.ToString(), i, 100);

            //Does some fake work
            System.Threading.Thread.Sleep(700);
        }

        //closes the Progress window
        progWindow.Dispatcher.Invoke(new Action(progWindow.Close));

        //Show Result to User
        Autodesk.Revit.UI.TaskDialog.Show("Task", "Task Completed");
        return Result.Succeeded;
    }

    private void ShowProgWindow()
    {
        //creates and shows the progress window
        progWindow = new ProgressWindow();
        progWindow.Show();

        //makes sure dispatcher is shut down when the window is closed
        progWindow.Closed +=new EventHandler(progWindow_Closed);

        //Notifies command thread the window has been created
        _progressWindowWaitHandle.Set();

        //Starts window dispatcher
        System.Windows.Threading.Dispatcher.Run();  
    }
}

这里是我的ProgressWindow类中的UpdateProgress()方法

public void UpdateProgress(string message, int current, int total)
{           
    this.Dispatcher.Invoke(new Action<string, int, int>(

    delegate(string m, int v, int t)
    {
        this.progressBar1.Maximum = System.Convert.ToDouble(t);
        this.progressBar1.Value = System.Convert.ToDouble(v);
        this.messageLbl.Content = m;
    }),
    System.Windows.Threading.DispatcherPriority.Background, 
    message, current, total);
}

我的第一个问题是,总体上我做得对吗?它似乎可以工作,但我知道足够多的关于多线程编程的知识,知道仅因为今天它可以工作,并不意味着明天也可以。
其次,我想在我的进度窗口中添加一个取消按钮,以便能够取消进程。最好的方法是什么?我理解最终我将得到一个“cancelRequested”布尔标志,由工作线程定期检查,但我如何从进度窗口线程设置这个标志?

1 - 你在AutoDesk工作吗?真的吗? 2 - 创建一个适当的ViewModel,并使ViewModel在次要的Dispatcher线程中引发属性更改。 - Federico Berasategui
1
Revit是由Autodesk制作的,但我并不为Autodesk工作。通常我所做的所有WPF都是MVVM,但为了简单起见,在这个例子中我没有为我的进度表单创建ViewModel类。 - Eric Anastas
@EricAnastas 我知道这个帖子有点旧了,但我没有找到更新的内容,而且我的问题非常接近这个主题。有没有办法更新启动外部事件的 GUI? - Thibaud
1个回答

4
我唯一能看到的改进是在设置AutoResetEvent和调用Dispatcher.Run之间存在潜在的竞争条件。我知道这一点,因为我在使用多线程进度UI时遇到了这个问题。
修复方法是在后台DispatcherBeginInvoke调用。这将确保它在Dispatcher开始循环事件之后执行:
System.Windows.Threading.Dispatcher.Current.BeginInvoke(
    new Func<bool>(_progressWindowWaitHandle.Set));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接