在UI线程中工作时如何更新进度栏 [英] How to update progress bar while working in the UI thread

查看:67
本文介绍了在UI线程中工作时如何更新进度栏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 ProgressBar 和一个 TreeView .

我用一堆数据填充了 TreeView ,一旦应用了它们,我便遍历了 TreeView visual tree 生成每个 TreeViewItems .我希望 ProgressBar 展示进度如何.

I populated the TreeView with a bunch of data, once it is applied I run through the visual tree of the TreeView basically forcing it to generate each of the TreeViewItems. I would like the ProgressBar to show how this is progressing.

这是我用来创建 TreeViewItems 的行为代码.一旦 ItemsLoaded 属性设置为true,它将开始处理项目.依次更新单例类中的属性以更新进度.

This is the behavior code that I run to create the TreeViewItems. It starts processing the items once ItemsLoaded property is set to true. It in turn updates a property in a singleton class to update the progress.

public class TreeViewBehaviors
{
    public static readonly DependencyProperty ItemsLoadedProperty =
        DependencyProperty.RegisterAttached("ItemsLoaded", typeof(bool), typeof(TreeViewBehaviors),
        new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnItemsLoadedPropertyChanged)));

    public static bool GetItemsLoaded(DependencyObject obj)
    {
        return (bool)obj.GetValue(ItemsLoadedProperty);
    }

    public static void SetItemsLoaded(DependencyObject obj, bool value)
    {
        obj.SetValue(ItemsLoadedProperty, value);
    }

    private static void OnItemsLoadedPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue)
        {
            GetTotalNTreeViewItems((TreeView)sender, sender);
        }
    }

    public static readonly DependencyProperty NodesProcessedProperty =
        DependencyProperty.RegisterAttached("NodesProcessed", typeof(int), typeof(TreeViewBehaviors),
        new FrameworkPropertyMetadata(default(int), new PropertyChangedCallback(OnNodesProcessedPropertyChanged)));

    public static int GetNodesProcessed(DependencyObject obj)
    {
        return (int)obj.GetValue(NodesProcessedProperty);
    }

    public static void SetNodesProcessed(DependencyObject obj, int value)
    {
        if (GetNodesProcessed(obj) != value)
        {
            obj.SetValue(NodesProcessedProperty, value);
        }
    }

    private static void OnNodesProcessedPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue != null)
        {
            double trouble = Math.Round(((GetProgressMaximum(sender) / GetTotalNodesToProcess(sender)) * (int)e.NewValue), 1);
            TreeViewSingletonClass.Instance.DisplayProgress = trouble;
        }
    }

    public static readonly DependencyProperty TotalNodesToProcessProperty =
        DependencyProperty.RegisterAttached("TotalNodesToProcess", typeof(double), typeof(TreeViewBehaviors),
        new FrameworkPropertyMetadata(default(double)));

    public static double GetTotalNodesToProcess(DependencyObject obj)
    {
        return (double)obj.GetValue(TotalNodesToProcessProperty);
    }

    public static void SetTotalNodesToProcess(DependencyObject obj, double value)
    {
        obj.SetValue(TotalNodesToProcessProperty, value);
    }


    public static readonly DependencyProperty ProgressMaximumProperty =
        DependencyProperty.RegisterAttached("ProgressMaximum", typeof(double), typeof(TreeViewBehaviors),
        new FrameworkPropertyMetadata(default(double)));

    public static double GetProgressMaximum(DependencyObject obj)
    {
        return (double)obj.GetValue(ProgressMaximumProperty);
    }

    public static void SetProgressMaximum(DependencyObject obj, double value)
    {
        obj.SetValue(ProgressMaximumProperty, value);
    }

    private static void GetTotalNTreeViewItems(ItemsControl container, DependencyObject sender)
    {
        if (container != null)
        {
            container.ApplyTemplate();
            ItemsPresenter itemsPresenter = (ItemsPresenter)container.Template.FindName("ItemsHost", container);
            if (itemsPresenter != null)
            {
                itemsPresenter.ApplyTemplate();
            }
            else
            {
                // The Tree template has not named the ItemsPresenter, 
                // so walk the descendents and find the child.
                itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                if (itemsPresenter == null)
                {
                    container.UpdateLayout();
                    itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                }
            }

            Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);

            // Ensure that the generator for this panel has been created.
            UIElementCollection children = itemsHostPanel.Children;
            for (int i = 0, count = container.Items.Count; i < count; i++)
            {
                TreeViewItem subContainer = (TreeViewItem)container.ItemContainerGenerator.ContainerFromIndex(i);
                GetTotalNTreeViewItems(subContainer, sender);
                SetNodesProcessed(sender, GetNodesProcessed(sender) + 1);
            }
        }
    }

    private static T FindVisualChild<T>(Visual visual) where T : Visual
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
        {
            Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
            if (child != null)
            {
                T correctlyTyped = child as T;
                if (correctlyTyped != null)
                    return correctlyTyped;

                T descendent = FindVisualChild<T>(child);
                if (descendent != null)
                    return descendent;
            }
        }
        return null;
    }
}

单班

public class TreeViewSingletonClass : INotifyPropertyChanged
{
    private static double m_DisplayProgress = 0;
    public double DisplayProgress
    {
        get { return m_DisplayProgress; }
        set
        {
            if (m_DisplayProgress == value)
                return;
            m_DisplayProgress = value;
            NotifyPropertyChanged();
        }
    }

    private static TreeViewSingletonClass m_Instance;
    public static TreeViewSingletonClass Instance
    {
        get
        {
            if (m_Instance == null)
                m_Instance = new TreeViewSingletonClass();
            return m_Instance;
        }
    }

    private TreeViewSingletonClass(){}

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

XAML:

<ProgressBar Grid.Column="2" Grid.Row="1" Margin="5" 
             Width="20" Height="150" 
             VerticalAlignment="Top" 
             Value="{Binding Source={x:Static helpers:TreeViewSingletonClass.Instance}, Path=DisplayProgress}" 
             Maximum="{Binding ProgressMaximum}"  />

我的问题是,每件事都得到正确处理,只是 ProgressBar 直到最后才更新.我意识到两者都在同一个 UI线程上内联工作,因此将是问题所在.

My issue is, every thing is being processed correctly, just the ProgressBar is not updating until the very end. I realise that the two are both working inline on the same UI thread so that will be the problem.

所以我的问题是,这两个问题都在同一个线程上运行时,如何才能更新此 ProgressBar .

So my question, with both of these working on the same thread how can I get this ProgressBar to update.

此WPF是 WinForm ElementHost 中的 UserControl ,我将以下内容放入WinForm中,以便可以访问 Application.当前

This WPF is a UserControl in a WinForm ElementHost, I just placed the following into the WinForm so I can access Application.Current

if ( null == System.Windows.Application.Current )
{
   new System.Windows.Application();
}

尝试实施Xavier的第二条建议后:将工作拆分为较小的部分,并使用BeginInvoke将这些部分分别与调度程序排队(例如,将循环的主体转换为调度程序调用)

After trying to implement the second suggestion of Xavier's: Split up the work into smaller pieces and queue those pieces up individually with the dispatcher using BeginInvoke (for example, convert the body of a loop into a dispatcher call)

因此在 for 循环中,我粘贴了以下内容:

So inside the for loop I stuck the following:

for (int i = 0, count = container.Items.Count; i < count; i++)
{
    Application.Current.Dispatcher.BeginInvoke(new Action(delegate()
    {
        TreeViewItem subContainer = (TreeViewItem)container.ItemContainerGenerator.ContainerFromIndex(i);
        GetTotalNTreeViewItems(subContainer, sender);
        SetNodesProcessed(sender, GetNodesProcessed(sender) + 1); 
    }));
}

不幸的是,这没有用,一定是做错了事.

Unfortunately this has not worked, must be doing something wrong.

推荐答案

WPF中的UI线程使用

The UI thread in WPF uses the Dispatcher to schedule and process all updating of the UI. The dispatcher basically maintains a queue of tasks to run on the thread. If you are monopolizing the thread, the queue will just back up until you give it a chance to run again.

有多种可能的解决方案来解决您的问题.这是一对...

There are multiple potential solutions to your issue. Here are a couple...

在单独的线程上工作

我可能首先考虑的解决方案是将您长期运行的任务移至另一个线程,而不是接管UI线程.您可以使用

The solution I would likely consider first is moving your long-running task to another thread instead of taking over the UI thread. Any updates you need to make to the UI from that thread can be done by going through the Dispatcher for the UI thread using the BeginInvoke method. For example, if I wanted to add 1 to the value of a progress bar, I might do something like this:

Dispatcher.BeginInvoke(new Action(delegate() { mProgress.Value += 1.0; }));

注意:确保您的工作线程具有从UI线程引用调度程序的方法.不要从辅助线程调用 Dispatcher.CurrentDispatcher ,否则您将获得该线程的调度程序,该调度程序无法访问UI.相反,您可以将调度程序传递给线程,或通过从UI线程设置的成员或属性来访问它.

Note: Make sure your worker thread has a way to reference the dispatcher from the UI thread. Don't call Dispatcher.CurrentDispatcher from the worker thread or you will get a dispatcher for that thread instead, which cannot access the UI. Instead, you can pass in the dispatcher to the thread, or access it via a member or property that was setup from the UI thread.

使用分派器共享UI线程

如果您确实出于某种原因(或由于执行大量可视化树遍历或其他针对UI的任务而可能在UI线程上执行所有工作),请考虑以下其中一项:

If you really want to perform all of the work on the UI thread for one reason or another (which you might if you are doing a lot of visual tree walking or other UI-focused tasks), consider one of the following:

  • Split up the work into smaller pieces and queue those pieces up individually with the dispatcher using BeginInvoke. Make sure the priority is low enough that the UI updates will not get stuck waiting until the end. For example:

for (int i = 0; i < 100; ++i)
{
    Dispatcher.CurrentDispatcher.BeginInvoke(new Action(delegate()
    {
        mProgress.Value += 1.0;
        // Only sleeping to artificially simulate a long running operation
        Thread.Sleep(100);
    }), DispatcherPriority.Background);
}

  • 在长时间运行的操作期间,根据需要处理调度程序队列. 查看全文

  • 登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆