I have a ProgressBar
and a TreeView
.
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.
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;
}
}
Singleton Class
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}" />
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.
So my question, with both of these working on the same thread how can I get this ProgressBar
to update.
[EDIT]
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();
}
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)
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.
See Question&Answers more detail:
os