WPF DataTemplateSelector is not used - c#

I am trying to modify a existing WPF application in a way so that a newer version of some data object can be used alongside the old one. And so i avoid redundant code by extending the existing ViewModel with new fields where the old ones cannot be reused.
public IList<G1VU.PDR> VuG1 { get; set; }
public IList<G2VU.PDR> VuG2 { get; set; }
public PlacesCompound VuP
{
get
{
if (VuG1 != null && VuG2 == null)
{
return new PlacesCompound {
G1 = VuG1,
G2 = null
};
}
if (VuG2 != null && VuG1 == null)
{
return new PlacesCompound {
G1 = null,
G2 = VuG2
};
}
throw new Exception("G1 and G2 data present or no data present");
}
}
VuG1 has existed before and i have added a new property VuG2 for the new data. As you can see these are not the same class so i cannot interchange them. For that reason i've added a property that will return either of the two in a PlacesCompound class, which is just a class with two properties and nothing else.
In the corresponding usercontrol (lets call it ActivitiesView) we have a DataGrid which binds to the ViewModel and somewhere a TabItem that will display a custom UserControl places which binds to VuG1 on the ViewModel. I have copied it and changed it so it will work with VuG2 Data.
And i created a custom DataTemplateSelector which will decide what Template to use based on which variable of PlacesCompound isnt null.
In VUActivitiesResources.xaml i have then declared 2 DataTemplates one for each places UserControl and the DataTemplateSelector.
<activities:VUActivitiesViewDataTemplateSelector x:Key="PlacesTemplateSelector"/>
<DataTemplate x:Key="VuG2Template">
<places:VUPViewG2 DataContext="{Binding VuG2}" HorizontalAlignment="Left"/>
</DataTemplate>
<DataTemplate x:Key="VuG1Template">
<places:VUPViewG1 DataContext="{Binding VuG1}" HorizontalAlignment="Left"/>
</DataTemplate>
VUActivitiesResources.xaml is being referenced in ActivitiesView as UserControl.Resources.
In the ActivitiesView i placed a ItemsControl into the TabItem replacing the custom places UserControl (ive also tried a ListBox instead of a ItemsControl, but neither works)
<TabItem IsEnabled="{Binding PlacesIsVisible}">
...
<ItemsControl
ItemsSource="{Binding VuP}"
ItemTemplateSelector="{StaticResource PlacesTemplateSelector}"></ItemsControl>
</TabItem>
My question: why PlacesTemplateSelector is never used and how do i make it being used? Because right now while debugging i can see that in ViewModel VuP returns a PlacesCompound object correctly but the Selector is never entered. I want one of the two DataTemplates to show up in the TabItem and right now none is showing.

ItemsSource must be a collection (which your PlacesCompound is not), in your case this would be either VuG1 or VuG2. If the two item classes have no common base class, you can still use IEnumerable:
public IEnumerable VuP => (IEnumerable)VuG1 ?? VuG2;
Then, instead of writing a TemplateSelector, just let the WPF DataTemplating mechanism do its work:
<DataTemplate DataType="{x:Type namespace1:PDR}">
<places:VUPViewG2 HorizontalAlignment="Left"/>
</DataTemplate>
<DataTemplate DataType="{x:Type namespace2:PDR}">
<places:VUPViewG1 HorizontalAlignment="Left"/>
</DataTemplate>
<ItemsControl ItemsSource="{Binding VuP}" />

Related

Organize Items in DropDownButton?

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.

Setting binding source properly in XAML

I'd like to have a list of TextBlocks with ComboBoxes next to each of them.
The data source of ComboBoxes should be the same for every ComboBox. Each TextBlock however should contain sequent element of List
Both data source for ComboBoxs and TextBlocks are in my "settings" object. So I set DataContext of the whole window to this settings object.
Here's my problem:
Data source of TextBlock is: List called Fields, which is inside of an object called "Header" of type "Line" (which is of course inside settings object, which is my datacontext).
So, graphically:
settings(type: Settings) - Header(type: CsvLine) - Fields(type: List of string)
Now ComboBox. Data source of every ComboBox should be a List called Tags
Graphically:
settings(type: Settings) - Tags(type: List of string)
I don't know how I should point to these locations, I tried a lot of options, but none of them work. I see just a blank window.
Here's my code:
<Grid>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Headers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Fields}"/>
<ComboBox ItemsSource="{Binding DataContext.Tags,
RelativeSource={RelativeSource AncestorType=ItemsControl}}">
</ComboBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
I have no idea what I should actually pass as ItemsSource to ItemsControl, because I think it should be common source for both TextBoxes and ComboBoxes, but their only common source is settings object - but i already set it as my DataContext.
I have used RelativeSource in ComboBox, but I'm not really sure what it's used for (although I read an article about it on MSDN). I don't know why but it's really hard for me to understand binding - I'm struggling to get anything working.
//EDIT:
Here's my Settings class - which is the type of my settings object:
public class Settings
{
public CsvLine AllHeaders1
{
get
{
return _allHeaders1;
}
}
public CsvLine _allHeaders1 = new CsvLine()
{
Fields = new List<string>()
{
"Header1" , "Header2" , "Header3"
}
};
private List<String> _tags;
public List<String> Tags
{
get
{
return new List<string>() { "Tag1", "Tag2", "Tag3", "Tag4", "Tag5" };
}
set
{
_tags = value;
}
}
}
And here's my CsvLine class:
public class CsvLine
{
public List<string> Fields = new List<string>();
public int LineNumber;
}
So, I'm not 100% sure of what it is you want, but the following should get you started.
Firstly, you need to ensure you bind to public properties - not public members - so the CsvLine.Fields member needs to be changed to public List<string> Fields { get { return _fields; } set { _fields = value; } }. Also not that, if you want changes in the settings object to be reflected in the UI, you will need to implement INotifyPropertyChanged.
Anyway, with this in place and assigned to the DataContext of the grid, the following will display a vertical list of text blocks (showing "Header 1", "Header 2", "Header 3") each with a combo box to the right containing the values "Tag1", "Tag2" ... "Tag5".
<Grid x:Name="SourceGrid">
<ItemsControl ItemsSource="{Binding Path=AllHeaders1.Fields}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<ComboBox ItemsSource="{Binding ElementName=SourceGrid, Path=DataContext.Tags}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Hope it helps.

Binding collection to ComboBox in DataGrid

I have two questions about binding ComboBox to lists objects, when the ComboBox are implemented in DataGrid. But they are so interrelated, that I think two threads are not constructive.
I have a fistful of classes, and I want show their data in a xceed DataGrid. My DataContext is set to ViewModelClass. It has a list of class X objects:
public class ViewModelClass
{
public IList<X> ListX { get; set; }
}
The class X looks something like this. It has a property Id, and a list list of class Y objects.
The list should be my ItemsSource for the ComboBoxes (in DataGrid).
public class X
{
public int Id { get; set; }
// this should be my ItemsSource for the ComboBoxes
public IList<Y> ListY { get; set; }
}
The class Y and Z look something like this. They are some kinds of very simple classes:
public class Y
{
public Z PropZ { get; set; }
}
public class Z
{
public string Name { get; set; }
}
My XAML-Code looks something like this.
<Grid.Resources>
<xcdg:DataGridCollectionViewSource x:Key="ListX" AutoCreateItemProperties="False"
Source="{Binding Path=ListX,
UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}" />
</Grid.Resources>
<p:DataGrid AutoCreateColumns="False"
ItemsSource="{Binding Source={StaticResource ListX},
UpdateSourceTrigger=PropertyChanged}">
<xcdg:Column Title="Id" FieldName="Id" />
<xcdg:Column Title="Functions" **FieldName="ListY"**>
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="PropZ.Name"
**ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type xcdg:DataGridControl}}, Path=ItemsSource.ListY**}" SelectedValuePath="Funktion.FunktionId" />
</DataTemplate>
</xcdg:Column.CellContentTemplate>
</xcdg:Column>
Now I dont know, how can I bind the ItemsSource of the ComboBox, so that I can read the list values of ListY in my X class?
Then I dont know what is in fact my FieldName for the Functions column?
I entered ListY, because it represents the property (IList<>) in my X class. But I think it is probably not right.
Thanks a lot for your help!
To answer your first question - try this
<ComboBox ItemsSource="{Binding Path=DataContext.ListY,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
For your seconds question I am not too sure (it is up to you) but the field name would probably be SelectedFunction or something along those lines
Let's break down your problem into bite sized pieces. You have a ListX collection that is data bound to a DataGrid.ItemsSource property:
<DataGrid ItemsSource="{Binding ListX}" ... />
One thing to note about your code at this stage is that it is pointless setting the Binding.UpdateSourceTrigger property to PropertyChanged on the ItemsSource property. From the linked page:
Bindings that are TwoWay or OneWayToSource listen for changes in the target property and propagate them back to the source. This is known as updating the source. Usually, these updates happen whenever the target property changes. This is fine for check boxes and other simple controls, but it is usually not appropriate for text fields. Updating after every keystroke can diminish performance and it denies the user the usual opportunity to backspace and fix typing errors before committing to the new value. Therefore, the default UpdateSourceTrigger value of the Text property is LostFocus and not PropertyChanged.
You really should know what the code does before you use it.
So anyway, back to your problem... we have a data bound DataGrid and one of its columns has a ComboBox in it. I'm not really sure why you're not using the DataGridComboBoxColumn Class or equivalent, but no matter. Now, you need to understand something about all collection controls:
If a collection of type A is data bound to the ItemsSource property of a collection control, then each item of the collection control will be an instance of type A. This means that the DataContext of each item will be set to that instance of type A. This means that we have access to all of the properties defined in class A from within any DataTemplate that defines what each item should look like.
That means that you have direct access to the ListY property of the X class from within the DataTemplate that defines what your items should look like. Therefore, you should be able to do this:
<DataTemplate>
<ComboBox DisplayMemberPath="PropZ.Name" ItemsSource="{Binding ListY}"
SelectedValuePath="Funktion.FunktionId" />
</DataTemplate>
I can't confirm whether the SelectedValuePath that you set will work, because you didn't mention it anywhere, but if your class Y doesn't have a property named Funktion in it, then it will not work. You'll also have to explain your second problem better, as I didn't really understand it.
I have found a solution, but even that has not proven to be productive. Because the allocation of cell.Content to the comboBox.ItemsSource shows no effect in my View :-(
In XAML, I have the following code
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<p:XDataGridComboBox
DataRow="{Binding RelativeSource={RelativeSource AncestorType={x:Type xcdg:DataRow}}}"
ItemsFieldName="Functions" />
</DataTemplate>
</xcdg:Column.CellContentTemplate>
I have written a custom control in which I explicitly set the data source for each ComboBox:
static XDataGridComboBox()
{
DataRowProperty = DependencyProperty.RegisterAttached(
"DataRow",
typeof(DataRow),
typeof(XDataGridComboBox),
new FrameworkPropertyMetadata(OnChangeDataRow));
ItemsFieldNameProperty = DependencyProperty.RegisterAttached(
"ItemsFieldName",
typeof(string),
typeof(XDataGridComboBox),
new FrameworkPropertyMetadata(OnChangeItemsFieldName));
}
private static void OnChangeDataRow(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var comboBox = d as XDataGridComboBox;
if (comboBox == null)
{
return;
}
var cell =
(from DataCell c in comboBox.DataRow.Cells where c.FieldName == comboBox.ItemsFieldName select c)
.FirstOrDefault();
if (cell == null)
{
return;
}
comboBox.ItemsSource = cell.Content as IEnumerable;
}
The data that I need are available, but the view does not show it. I do not know what I have not considered.

Hiding a sibling listbox when a textblock is clicked

I was wonder if any one knows how to change the visibility of a listbox within a DataTemplate when a sibling is clicked. The DataTemplate is being used on a listbox. The following is an example of the xaml I'm using:
<DataTemplate x:Key="Template1">
<StackPanel Margin="110,0,0,0">
<TextBlock Text="{Binding Name}" />
<TextBlock Name="ShowHide" Text="Hide" Tap="ShowHide_Tap" />
<ListBox Name="Listbox1" ItemsSource="{Binding SecondList}" Visibility="Visible" ItemTemplate="{StaticResource Template2}"/>
</StackPanel>
</DataTemplate>
The following is my attempt but I can't use the FindName
private void ShowHide_Click(object sender, System.Windows.Input.GestureEventArgs e)
{
var item = sender as TextBlock;
ListBox Listbox = null;
if (item != null)
{
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
DataTemplate dataTemplate = templateParent.ContentTemplate;
if (dataTemplate != null && templateParent != null)
{
Listbox = templateParent.FindName("Listbox1") as ListBox;
}
if (Listbox != null)
{
MessageBox.Show(String.Format("ERROR!"));
}
else
Listbox.Visibility = Visibility.Collapsed;
}
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
FrameworkElement child = null;
for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceElement); i++)
{
child = VisualTreeHelper.GetChild(referenceElement, i) as FrameworkElement;
System.Diagnostics.Debug.WriteLine(child);
if (child != null && child.GetType() == typeof(T))
{ break; }
else if (child != null)
{
child = GetFrameworkElementByName<T>(child);
if (child != null && child.GetType() == typeof(T))
{
break;
}
}
}
return child as T;
}
If any one has any insights they would be much appreciated,
Thanks.
It so happens that the Blend SDK provides functionality for this -- you can even use XAML only, no code behind. Just use an EventTrigger (on the "Tap" event) along with a ChangePropertyAction. Here's how it looks:
<TextBlock Name="ShowHide" Text="Hide" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<ei:ChangePropertyAction TargetName="Listbox1"
PropertyName="Visibility" Value="Collapsed" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<ListBox Name="Listbox1" ItemsSource="{Binding SecondList}" Visibility="Visible" />
Note that this requires you to add references to the following Extensions:
System.Windows.Interactivity
Microsoft.Expression.Interactions
Reference them in XAML with the namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Welcome to StackOverflow!
Generally speaking this is not the way to use WPF, especially if you're using DataTemplates. The purpose of the view in WPF is to display the view model and fire off user events, nothing more. By changing the Data Template at run-time you're effectively storing the state of the view inside the view itself. This runs totally against the grain of how WPF was designed to be used.
To do this properly your view model (i.e. the class with the SecondList property) should have an extra property called something like ListBoxVisibility and you would bind your listbox's Visibility member to that. An even cleaner method is to use a bool and then use a converter in the view to convert it from type bool to type Visibility. Either way the view model should also have a property of type ICommand (e.g. OnButtonPressedCommand) that the button invokes when the user presses it. The handler for OnButtonPressedCommand, which should also be in the view model, then sets ListBoxVisible or whatever to the value you want which then propagates through to the list box. Doing things this way keeps good separation of concerns and means the view model can be created and its visibility-changing behavior unit tested independently without having to create the view itself.

UI slow at updating ObservableCollection<T> in TreeView control

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.

Categories