簡體   English   中英

如何在UI線程中工作時更新進度條

[英]How to update progress bar while working in the UI thread

我有一個ProgressBar和一個TreeView

我用一堆數據填充TreeView ,一旦應用,我運行TreeViewvisual tree基本上強制它生成每個TreeViewItems 我希望ProgressBar能夠展示這是如何發展的。

這是我運行以創建TreeViewItems的行為代碼。 一旦ItemsLoaded屬性設置為true,它就開始處理項目。 它反過來更新單例類中的屬性以更新進度。

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 thread上內聯工作,這將是問題。

所以我的問題是,如果這兩個工作在同一個線程上,我怎么能讓這個ProgressBar更新。

[編輯]

這個WPF是WinForm ElementHostUserControl ,我只是將以下內容放入WinForm中,以便我可以訪問Application.Current

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

在嘗試實現Xavier的第二個建議之后:將工作分成更小的部分,並使用BeginInvoke將調度程序單獨排隊(例如,將循環體轉換為調度程序調用)

所以在for循環中我堅持以下內容:

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

不幸的是,這沒有用,必須做錯事。

WPF中的UI線程使用Dispatcher來計划和處理UI的所有更新。 調度程序基本上維護一個在線程上運行的任務隊列。 如果您獨占線程,隊列將只備份,直到您有機會再次運行。

您的問題有多種潛在的解決方案。 這是一對......

在一個單獨的線程上工作

我可能首先考慮的解決方案是將長時間運行的任務轉移到另一個線程而不是接管UI線程。 您需要從該線程對UI進行的任何更新都可以通過使用BeginInvoke方法遍歷UI線程的Dispatcher來完成。 例如,如果我想將1添加到進度條的值,我可能會這樣做:

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

注意:確保您的工作線程有一種方法從UI線程引用調度程序。 不要從工作線程調用Dispatcher.CurrentDispatcher ,否則您將獲得該線程的調度程序,該調度程序無法訪問UI。 相反,您可以將調度程序傳遞給線程,或者通過從UI線程設置的成員或屬性訪問它。

使用Dispatcher共享UI線程

如果您真的想要出於某種原因在UI線程上執行所有工作(如果您正在進行大量可視樹行走或其他以UI為中心的任務,則可能會這樣做),請考慮以下其中一項:

  • 將工作分成更小的部分,並使用BeginInvoke與調度程序分別對齊這些部分。 確保優先級足夠低,以便UI更新不會等到最后。 例如:

     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); } 
  • 在長時間運行的操作期間根據需要處理調度程序隊列。 有一個在PushFrame方法文檔的“備注”部分中為此目的創建DoEvents方法的示例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM