2015-06-18 64 views
3

我有一個ProgressBarTreeView如何在UI線程中工作時更新進度條

我填充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; 
    } 
} 

Singleton類

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是一個WinFormElementHost一個UserControl,我只是放在下面進入WinForm的,所以我可以訪問Application.Current

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

試圖執行第二項建議後, Xavier's:將工作分解爲更小的部分,並使用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); 
    })); 
} 

可惜這沒有奏效,必須做一些錯誤的。

回答

2

WPF中的UI線程使用Dispatcher來調度和處理UI的所有更新。調度員基本上維護一個在線程上運行的任務隊列。如果你壟斷了這個線程,那麼這個隊列就會恢復,直到你給它一次再次運行的機會。

您的問題有多種可能的解決方案。這裏有幾個...

一個單獨的線程

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

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

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

使用調度共享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方法的示例。

+0

所以我可能需要一點幫助,如果我使用'分成小塊和排隊工作'。請你給我舉個例子。 – Hank

+0

我也忘了提及這個WPF是一個WinForm ElementHost中的UserControl,這是否有所作爲? – Hank

+0

我給我的答案增加了一個例子。你不需要一個'Application'實例來訪問一個調度器。只需導入System.Windows.Threading命名空間並使用Dispatcher.CurrentDispatcher。我第一次忘記提及的一件事是,您需要指定一個足夠低的DispatcherPriority來確保任何UI更新都不會卡住,等待所有操作完成。 'DispatcherPriority.Background'通常很好。 – Xavier