Overview
I have an application, that displays data from an observable collection. The observable collection is (in this debugging setting) created and instanciated only once, then the values stay the same.
The main view of the application contains a ListBox that is bound to said observable collection:
<ListBox x:Name="MainListBox" ItemsSource="{Binding Items}" SelectionChanged="MainListBox_SelectionChanged" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel MinWidth="456" MaxWidth="456" Background="White" Margin="0,0,0,17">
<sparklrControls:SparklrText Post="{Binding Path=.}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<!-- Workaround used to stretch the child elements to the full width -> HorizontalContentAlignment won't work for some reason... -->
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The child items are bound to a UserControl. This UserControl implements a DependancyProperty which the child elements are bound to:
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(object), new PropertyMetadata(textPropertyChanged));
private static void postPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SparklrText control = d as SparklrText;
control.Post = (ItemViewModel)e.NewValue;
}
Binding to the post property configures other variables via the getter of the Post property
public ItemViewModel Post
{
get
{
return post;
}
set
{
if (post != value)
{
this.ImageLocation = value.ImageUrl;
this.Username = value.From;
this.Comments = value.CommentCount;
this.Likes = value.LikesCount;
this.Text = value.Message;
post = value;
}
}
}
This setter configures other which in turn set up elements in the user control. Nothing in the user control is bound, the few updates are done with direct access to the respective Content/Text properties. ImageLocation performs an asynchronous download of an image with
private void loadImage(string value)
{
WebClient wc = new WebClient();
wc.OpenReadCompleted += (sender, e) =>
{
image = new BitmapImage();
image.SetSource(e.Result);
MessageImage.Source = image;
};
wc.OpenReadAsync(new Uri(value));
}
Issue
When I scroll down in the list box and back up, the setter of Post is executed when the owning element comes back into view. The problem: value is a different instance of ItemViewModel. The ListBox ItemsSource is not accessed in any way from outside the class. When scrolling back up, it seems like the wrong Items are bound to the elements, resulting in distorted designs. Are there any issues with the Binding that cause this?
The issue was caused by the ListBox. Elements that are scroll out of view are recycled and appended on the other side. In the code above, a asynchronous operation did not check if the result was still valid, causing wrong display data.
Related
I've a collection of items inside an ObservableCollection, each item have a specific nation name (that's only a string). This is my collection:
private ObservableCollection<League> _leagues = new ObservableCollection<League>();
public ObservableCollection<League> Leagues
{
get
{
return _leagues;
}
set
{
_leagues = value;
OnPropertyChanged();
}
}
the League model have only a Name and a NationName properties.
The Xaml looks like this:
<Controls:DropDownButton Content="Leagues" x:Name="LeagueMenu"
ItemsSource="{Binding Leagues}"
ItemTemplate="{StaticResource CombinedTemplate}" >
<Controls:DropDownButton.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding NationName}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</Controls:DropDownButton.GroupStyle>
</Controls:DropDownButton>
but I doesn't get any header for the NationName property, the items inside the DropDown are organized without header but as list, so without organization.
I'm trying to get this predisposition.
What am I doing wrong?
Preliminaries
Grouping items in an ItemsControl in WPF (which DropDownButton derives from) is fairly simple, and is accomplished in two steps. First you need to set up the items source by tweaking an ICollectionView associated with the source collection. Then you need to populate the ItemsControl.GroupStyle collection with at least one GroupStyle item - otherwise the items are presented in a plain (non-grouped) manner.
Diagnosis
The main issue you're facing is getting the drop-down to present the items in a grouped manner. Unfortunately, unlike setting up the items source, it is not something that is easily accomplished in case of the DropDownButton control. The reason for that stems from the way the control (or, more precisely, its template) is designed - the drop-down is presented inside a ContextMenu attached to a Button which is part of the template (see MahApps.Metro source code). Now ContextMenu also derives from ItemsControl, and most of its properties are bound to corresponding properties of the templated DropDownButton. That is however not the case for its GroupStyle property, because it's a read-only non-dependency property, and cannot be bound or event styled. That means that even if you add items to DropDownButton.GroupStyle collection, the ContextMenu.GroupStyle collection remains empty, hence the items are presented in non-grouped manner.
Solution (workaround)
The most reliable, yet most cumbersome solution would be to re-template the control and add GroupStyle items directly to the ContextMenu.GroupStyle collection. But I can offer you a much more concise workaround.
First of all, let's deal with the first step - setting up the items source. The easiest way (in my opinion) is to use CollectionViewSource in XAML. In your case it would boil down to something along these lines:
<mah:DropDownButton>
<mah:DropDownButton.Resources>
<CollectionViewSource x:Key="LeaguesViewSource" Source="{Binding Leagues}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="NationName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</mah:DropDownButton.Resources>
<mah:DropDownButton.ItemsSource>
<Binding Source="{StaticResource LeaguesViewSource}" />
</mah:DropDownButton.ItemsSource>
</mah:DropDownButton>
Now for the main part - the idea is that we'll create a helper class that will contain one attached dependency property that will assign an owner DropDownButton control to the ContextMenu responsible for presenting its items. Upon changing the owner we'll observe its DropDownButton.GroupStyle collection and use ContextMenu.GroupStyleSelector to feed the ContextMenu with items coming from its owner's collection. Here's the code:
public static class DropDownButtonHelper
{
public static readonly DependencyProperty OwnerProperty =
DependencyProperty.RegisterAttached("Owner", typeof(DropDownButton), typeof(DropDownButtonHelper), new PropertyMetadata(OwnerChanged));
public static DropDownButton GetOwner(ContextMenu menu)
{
return (DropDownButton)menu.GetValue(OwnerProperty);
}
public static void SetOwner(ContextMenu menu, DropDownButton value)
{
menu.SetValue(OwnerProperty, value);
}
private static void OwnerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menu = (ContextMenu)d;
if (e.OldValue != null)
//unsubscribe from the old owner
((DropDownButton)e.OldValue).GroupStyle.CollectionChanged -= menu.OwnerGroupStyleChanged;
if (e.NewValue != null)
{
var button = (DropDownButton)e.NewValue;
//subscribe to new owner
button.GroupStyle.CollectionChanged += menu.OwnerGroupStyleChanged;
menu.GroupStyleSelector = button.SelectGroupStyle;
}
else
menu.GroupStyleSelector = null;
}
private static void OwnerGroupStyleChanged(this ContextMenu menu, object sender, NotifyCollectionChangedEventArgs e)
{
//this method is invoked whenever owners GroupStyle collection is modified,
//so we need to update the GroupStyleSelector
menu.GroupStyleSelector = GetOwner(menu).SelectGroupStyle;
}
private static GroupStyle SelectGroupStyle(this DropDownButton button, CollectionViewGroup group, int level)
{
//we select a proper GroupStyle from the owner's GroupStyle collection
var index = Math.Min(level, button.GroupStyle.Count - 1);
return button.GroupStyle.Any() ? button.GroupStyle[index] : null;
}
}
In order to complete the second step we need to bind the Owner property for the ContextMenu (we'll use DropDownButton.MenuStyle to do that) and add some GroupStyle items to the DropDownButton:
<mah:DropDownButton>
<mah:DropDownButton.MenuStyle>
<Style TargetType="ContextMenu" BasedOn="{StaticResource {x:Type ContextMenu}}">
<Setter Property="local:DropDownButtonHelper.Owner" Value="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
</mah:DropDownButton.MenuStyle>
<mah:DropDownButton.GroupStyle>
<GroupStyle />
</mah:DropDownButton.GroupStyle>
</mah:DropDownButton>
This I think should be enough to achieve your goal.
If you check out the other post you've linked to, the answer has it all - in particular you need to bind to a CollectionView, rather than directly to the collection. Then you can set up grouping on the CollectionView.
So, in your case, define the property:
public ICollectionView LeaguesView { get; private set; }
and then after you've created your Leagues Collection, attach the View to your collection, and while you're at it set up the grouping on the view:
LeaguesView = (ListCollectionView)CollectionViewSource.GetDefaultView(Leagues);
LeaguesView.GroupDesriptions.Add(new PropertyGroupDescription("NationName"));
Then, bind your DropDownButton ItemSource to LeaguesView, and change your HeaderTemplate to bind to "Name" - which is the the name of the group:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
You can also use the ItemCount property in there if you want to show how many items there are in the group.
I have made a User Control, FontSelector, that groups together a ComboBox for FontFamily Selection and three ToggleButtons for Bold, Italics, Underline options. I am having an issue with the ComboBox's SelectedItem property affecting all instances of that User Control within the same Window. For example, changing the ComboBox selection on one, will automatically change the other. For Clarity. I don't want this behavior. I am very surprised that a User Control is implicitly affecting another User Control.
XAML
<Grid x:Name="Grid" Background="White" DataContext="{Binding RelativeSource={RelativeSource AncestorType=local:FontSelector}}">
<ComboBox x:Name="comboBox" Width="135"
SelectedItem="{Binding Path=SelectedFontFamily}" Style="{StaticResource FontChooserComboBoxStyle}"
ItemsSource="{Binding Source={StaticResource SystemFontFamilies}}"/>
</Grid>
Code Behind
The CLR Property that the ComboBox's SelectedItem is Bound to. Code shown here is in the User Control Code Behind File, not a ViewModel.
private FontFamily _SelectedFontFamily;
public FontFamily SelectedFontFamily
{
get
{
return _SelectedFontFamily;
}
set
{
if (_SelectedFontFamily != value)
{
_SelectedFontFamily = value;
// Modify External Dependency Property Value.
if (value != SelectedFont.FontFamily)
{
SelectedFont = new Typeface(value, GetStyle(), GetWeight(), FontStretches.Normal);
}
// Notify.
RaisePropertyChanged(nameof(SelectedFontFamily));
}
}
}
The Dependency Property that updates it's value based on the Value of the ComboBox's SelectedItem Property. It effectively packages the FontFamily value into a Typeface Object.
public Typeface SelectedFont
{
get { return (Typeface)GetValue(SelectedFontProperty); }
set { SetValue(SelectedFontProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedFont. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedFontProperty =
DependencyProperty.Register("SelectedFont", typeof(Typeface), typeof(FontSelector),
new FrameworkPropertyMetadata(new Typeface("Arial"), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedFontPropertyChanged)));
private static void OnSelectedFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as FontSelector;
var newFont = e.NewValue as Typeface;
if (newFont != null)
{
instance.SelectedFontFamily = newFont.FontFamily;
}
}
EDIT
I think I may have figured out what is going on. I can replicate it by Binding the ItemsSource to the Following Collection View Source.
<CollectionViewSource x:Key="SystemFontFamilies" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Source"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
You can then replicate the behavior by placing 2 ComboBoxes and Binding both of them to the CollectionViewSource. They will now, seemingly implicitly track each others SelectedItem. Even without Any Data Binding outside of ItemsSource. It would seem that the CollectionViewSource is somehow playing a part in what the SelectedItem is.
I'd make it a bit different. I'll introduce this solution using only a String, not FontFamily or FontWeight, since I have no VS here right now. (In order to have it working, please change the list of FontFamilies to a list of strings to bind them.)
Your selector UserControl:
- your xaml is ok (but you won't need the x:Name)
- the CodeBehind of the UserControl (later: UC) should change, we will solve it with binding. You should have a DependencyProperty, lets' call it SelectedFontFamily, which will represent the selected string from the ComboBox:
public string SelectedFontFamily
{
get { return (string)GetValue(SelectedFontFamilyProperty); }
set { SetValue(SelectedFontFamilyProperty, value); }
}
public static readonly DependencyProperty SelectedFontFamilyProperty = DependencyProperty.Register("SelectedFontFamily", typeof(string), typeof(YourUC), new PropertyMetadata(string.Empty));
The Window, which contains the UC:
- You should include the namespace of the UC's folder in the opening tag of the window, eg:
<Window
...
xmlns:view="clr-namespace:YourProjectName.Views.UserControls">
- the window's DataContext should have a property with public set option (feel free to implement INotifyPropertyChange on it):
public string FontFamily {get; set;}
- in the Window's xaml you would use the UC this way:
<view:YourUC SelectedFontFamily="{Binding FontFamily, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
It's a two-way binding. You'll find the selected string as the value of the FontFamily property every time you change the SelectedItem.
Edit: you will need View Model class for the Window which is using the UserControl. Create it, make it implement the INotifyPropertyChanged interface, and set it as DataContext for your consumer window. WPF is not like WF, you can find more about it if you Google up "WPF MVVM" or something like that.
Found the problem. I was binding to a CollectionViewSource defined in Application Resources. Until now I was unaware that Binding to a CollectionViewSource will also affect the SelectedItem. The SelectedItem Data gets stored as part of the CollectionViewSource. Setting the IsSynchronizedWithCurrentItem property to False on the ComboBox solved the issue.
Here is an existing answer I have now Found.
Thanks
In my windows phone 8.1 application I have a listbox when initially loading everything I want to show in the listbox everything is fine. However after scrolling some of the elements will be shown incorrectly. This seems completely random.
The forementioned listbox looks like this in xaml:
<ListBox Name="MainPage_List" Grid.Column="0" Background="#EDEDED" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Components:MyUserControl />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see its datatemplate is linked to a usercontrol. In this UserControl I have a DataContextChanged event. Which looks like this:
private void DataContextChanged(object sender, object e)
{
if (mySource == null)
{
mySource = DataContext as Message;
}
if (mySource != null)
{
if (mySource.Source_Type == SourceTypes.Type1)
{
MyGrid.Visibility = Visibility.Collapsed;
MyOtherGrid.Visibility = Visibility.Visible;
}
else if (mySource.Source_Type == SourceTypes.Type2)
{
MyOtherGrid.Visibility = Visibility.Collapsed;
MyGrid.Visibility = Visibility.Visible;
}
}
}
I check multiple types and variables here and depending on that I set other things visible or load other images. This works fine. However when scrolling through the list sometimes some of the elements will be shown differently than they should. Even when I make sure the code that decides which elements will be shown is not used again.
The source of the list is a custom class enheriting from ObservableCollection using a custom class that enherits from INotifyPropertyChanged.
Does anyone know what I am doing wrong here? Or why this happens and how to get around this?
After some discussion with WereWolfBoy we found the problem.
It is related to the default ItemsPanel of the ListBox which is a VirtualizingStackPanel. It reuses the controls from the ItemTemplate of the ListBox and changes their DataContext when necessary to display different items. Unlike normal StackPanel, it does NOT create separate controls for different items.
For this to work, the items must refresh when the DataContext changes. And here's the actual problem. Because of this code:
if (mySource == null)
{
mySource = DataContext as Message;
}
the DataContext was actually loaded just once, and subsequent changes did not affect the UI. Removing the if and getting the DataContext every time it changes fixed the issue.
This may be a result of virtualization.
If your list is long enough, there is a reuse of controls for items in it.
Imagine you have a list with 1000 items in it, but the listbox only has 30 control instances that are re-used when you scroll up and down.
If this is the case, you will see the wrong behavior repeatin ever X items.
In order to solve this problem, I would recommend that instead of using DataContextChanged, you should expose a property as DependencyProperty.
In your data template, bind to this property and this will do the trick for you.
public bool ShowMyGrid
{
get { return (bool)GetValue(ShowMyGridProperty); }
set { SetValue(ShowMyGridProperty, value); }
}
public static readonly DependencyProperty ShowMyGridProperty =
DependencyProperty.Register("ShowMyGrid", typeof(bool), typeof(MyUserControl1), new UIPropertyMetadata(false, ShowMyGridCallback));
static void ShowMyGridCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myControl = d as MyUserControl1;
bool newVal = (bool)e.NewValue;
if (newVal)
{
myControl.MyGrid.Visibility = Visibility.Collapsed;
myControl.MyOtherGrid.Visibility = Visibility.Visible;
}
else
{
myControl.MyGrid.Visibility = Visibility.Visible;
myControl.MyOtherGrid.Visibility = Visibility.Collapsed;
}
}
I have implemented a selection pattern similar to the one described in this post using a ViewModel to store the IsSelected value, and by binding the ListViewItem.IsSelected to the ViewModel IsSelected:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
It works in general, but I encounter a severe problem. By using the a VirtualizingStackPanel as the panel in the list view, only the visible ListViewItem are getting created. If I use "Ctrl+A" to select all items, or by using shortcut combination such "Shift+Ctrl+End" on the first item, all items get selected, but for the non visible items, the ViewModel does not get its IsSelected set to true. That is logical, because if the ListViewItem are not created, the binding can't work.
Anybody experienced the same issue, and found a solution (apart from not using a VirtualizingStackPanel)?
I found another way of handling selection in the MVVM pattern, which solved my issue. Instead of maintaining the selection in the viewmodel, the selection is retrieved from the ListView/ListBox, and passed as a parameter to the Command. All done in XAML:
<ListView
x:Name="_items"
ItemsSource="{Binding Items}" ... />
<Button
Content="Remove Selected"
Command="{Binding RemoveSelectedItemsCommand}"
CommandParameter="{Binding ElementName=_items, Path=SelectedItems}"/>
in my ViewModel:
private void RemoveSelection(object parameter)
{
IList selection = (IList)parameter;
...
}
In my case, I ended up solving this by deriving a ListBoxEx class from ListBox, and adding code to respond to selection changes, enforcing the selection state on the item view models:
private readonly List<IListItemViewModelBase> selectedItems = new List<IListItemViewModelBase>();
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
bool isVirtualizing = VirtualizingStackPanel.GetIsVirtualizing(this);
bool isMultiSelect = (SelectionMode != SelectionMode.Single);
if (isVirtualizing && isMultiSelect)
{
var newSelectedItems = SelectedItems.Cast<IListItemViewModelBase>();
foreach (var deselectedItem in this.selectedItems.Except(newSelectedItems))
{
deselectedItem.IsSelected = false;
}
this.selectedItems.Clear();
this.selectedItems.AddRange(newSelectedItems);
foreach (var newlySelectedItem in this.selectedItems)
{
newlySelectedItem.IsSelected = true;
}
}
}
Apart from not using VirtualizingStackPanel, the only thing I can think of is to capture those keyboard shortcuts and have methods for modifying a certain range of your ViewModel items so that their IsSelected property is set to True (e.g., SelectAll(), SelectFromCurrentToEnd()). Basically bypassing the Binding on ListViewItem for controlling the selection for such cases.
Scenario
I have a TreeView that is bound to ObservableCollection<T>. The collection gets modified every time the end-user modifies their filters. When users modify their filters a call to the database is made (takes 1-2ms tops) and the data returned gets parsed to create a hierarchy. I also have some XAML that ensures each TreeViewItem is expanded, which appears to be part of the problem. Keep in mind that I'm only modifying ~200 objects with a max node depth of 3. I would expect this to instant.
Problem
The problem is that whenever filters get modified and the TreeView hierarchy gets changed the UI hangs for ~1 second.
Here is the XAML responsible for create the TreeView hierarchy.
<TreeView VerticalAlignment="Top" ItemsSource="{Binding Hierarchy}" Width="240"
ScrollViewer.VerticalScrollBarVisibility="Auto" Grid.Row="1">
<TreeView.ItemTemplate>
<!-- Hierarchy template -->
<HierarchicalDataTemplate ItemsSource="{Binding Stations}">
<TextBlock Text="{Binding}" />
<!-- Station template -->
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Locates}">
<TextBlock Text="{Binding Name}" />
<!-- Locate template -->
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TicketNo}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And here is the code for updating the list.
public ObservableCollection<HierarchyViewModel> Hierarchy
{
get { return _hierarchy; }
set { _hierarchy = value; }
}
public void UpdateLocates(IList<string> filterIds)
{
_hierarchy.Clear();
// Returns 200 records max
var locates = _locateRepository.GetLocatesWithFilters(filterIds);
var dates = locates.Select(x => x.DueDate);
foreach (var date in dates)
{
var vm = new HierarchyViewModel
{
DueDate = date
};
var groups = locates.Where(x => x.DueDate.Date.Equals(date.Date)).GroupBy(x => x.SentTo);
// Logic ommited for brevity
_hierarchy.Add(vm);
}
}
I also have <Setter Property="IsExpanded" Value="True" /> as a style. I have tried using a BindingList<T> and disabling notifications, but that didn't help.
Any ideas as to why my UI hangs whenever changes are made to the ObservableCollection<T>?
Partial Solution
With what H.B. said and implementing a BackgroundWorker the update is much more fluid.
The problem is probably the foreach loop. Every time you add an object the CollectionChanged event is fired and the tree is rebuilt.
You do not want to use an ObservableCollection if all you do is clear the whole list and replace it with a new one, use a List and fire a PropertyChanged event once the data is fully loaded.
i.e. just bind to a property like this (requires implementation of INotifyPropertyChanged):
private IEnumerable<HierarchyViewModel> _hierarchy = null;
public IEnumerable<HierarchyViewModel> Hierarchy
{
get { return _hierarchy; }
set
{
if (_hierarchy != value)
{
_hierarchy = value;
NotifyPropertyChanged("Hierarchy");
}
}
}
If you set this property bindings will be notified. Here i use the IEnumerable interface so no-one tries to just add items to it (which would not be noticed by the binding). But this is just one suggestion which may or may not work for your specific scenario.
(Also see sixlettervariable's good comment)
Just a side note, this code:
public ObservableCollection<HierarchyViewModel> Hierarchy
{
get { return _hierarchy; }
set { _hierarchy = value; }
}
is bad, you could overwrite the list and the binding would break because there is no PropertyChanged event being fired in the setter.
If you use an ObservableCollection it normally is used like this:
private readonly ObservableCollection<HierarchyViewModel> _hierarchy =
new ObservableCollection<HierarchyViewModel>();
public ObservableCollection<HierarchyViewModel> Hierarchy
{
get { return _hierarchy; }
}
The easiest thing to do is detach the item you are bound to, make all the changes you need to the list, then reattach it.
For example, set the treeviews ItemsSource to NULL/NOTHING, run through your for each, then set the ItemsSource back to _hierarchy. Your adds will be instant.