Refresh and Notify about related collection changes - wpf

I will try to simplify as much as possible so:
I have following EF6 entity:
public class ParentObject
{
ICollection<ChildObject> Children {get; set ;}
}
I have ViewModel where is
ObservableCollection<ParentObject> ParentCollection { get; set; }
ParentObject SelectedParent { get; set; }
ChildObject SelectedChild { get; set; }
Also I have two ListViews
<ListView Name="lvParents" Margin="5,5,0,5" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Path=ParentCollection}" SelectedItem="{Binding Path=SelectedParent}">
<ListView Name="lvChildren" Margin="5,5,0,5" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Path=SelectedParent.Children}" SelectedItem="{Binding Path=SelectedChild}">
All EF6 Entities have INotifyPropertyChanged implemented,
Also my ViewModel has INotifyPropertyChanged Implemented.
Everything is working well, if I'm changing selected item in lvParents. Children in lvChildren are changing.
Once I delete the children, I cannot refresh the Children:
(item is deleted from DB, but cannot refresh just lvChildren.
Here is method I tried to use:
_dataContext.ChildrenObjects.Remove(SelectedChild);
_dataContext.SaveChanges();
SelectedParent.Children.Remove(SelectedChild);
//here the SelectedParent does not contain the value I removed but no way how to refresh the ItemsSource of lvChildren.
NotifyPropertyChanged("SelectedChild");
NotifyPropertyChanged("SelectedParent.Children");
NotifyPropertyChanged("ParentCollection");
My question is, what is the correct way to bind depended (related) collections and refresh them after they are changed. Do I need overload the EF6 ICollection and create my own ObservableCollection ???
Is it enough?

My question is, what is the correct way to bind depended (related) collections and refresh them after they are changed?
The Children property of the ParentObject should return an ObservableCollection:
public class ParentObject
{
ObservableCollection<ChildObject> Children {get; set ;}
}
Then the ItemsSource will be automatically refreshed when you remove an item from the collection:
SelectedParent.Children.Remove(SelectedChild);
That would be the most "correct" way of implementing this according the MVVM design pattern.
Using and binding to auto-generated entity types "as-is" is rarely a good idea in WPF. You should bind to ObservableCollection<T> properties rather than ICollection<T> ones if you want to be able to dynamically modify your collections at runtime.

Related

TreeView incorrectly shows items as expanded when using VirtualizationMode.Recycling

Whenever I used TreeView I always had just few nodes and each of them usually had less than 100 items. I never really needed any kind of ui virtualization for that but now for the first time I need it.
The problem appears when using ui virtualization with recycling mode the TreeView seems to expand items even though I never expanded them manually.
I googled the issue and as far I understood recycling mode of virtualization in TreeView the containers get reused.
So I assume that the cause might be applying already expanded reused container to an item which wasn't expanded before.
Here is a simple example:
https://github.com/devhedgehog/wpf/
For those who cannot download code for whatever reason here is basically what I have tried to do with the TreeView.
This is what I have in XAML.
<Grid>
<TreeView ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Parts}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
And this is code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
IList<Car> list = new List<Car>();
for (int i = 0; i < 5000; i ++)
{
list.Add(new Car() { Name = "test1" + i });
}
foreach (var car in list)
{
car.Parts = new List<string>();
for (int i = 0; i < 500; i++)
{
car.Parts.Add("asdf" + i);
}
}
this.DataContext = list;
}
}
public class Car
{
public string Name
{
get;
set;
}
public List<string> Parts
{
get;
set;
}
}
I hope somebody can provide me a solution to this issue. Is this a known bug?
I am sorry in case its a duplicate. Futhermore I hope you guys tell me what I did wrong since this is my first post before you downgrade the question.
As you probably know, this problem can be solved easily by using standard recycling mode:
<TreeView VirtualizingStackPanel.VirtualizationMode="Standard" ...>
This shouldn't have too much of an impact on your TreeView's performance, as the tree will still be virtualized and a container will only be created for visible items. The benefits of the recycling mode only come into play when scrolling (when items are both being virtualized and realized), and usually the standard virtualization mode is good enough.
However, in case performance is really critical (or if you really want a solution for this while keeping the recycling mode, or if you're looking to do things the right way), you can use backing data and data binding to solve this problem.
The reason why this problem occurs in the first place is this:
Let's say you have a TreeViewItem which has its IsExpanded property set to true. When it's being recycled, i.e. its data is replaced, its IsExpanded property remains the same because it has no way to know whether it should be expanded or not, because that data is not available anywhere. The only place where it exists is the IsExpanded property of the TreeViewItem, and it's not going to be relevant because that item is being reused along with its properties.
If however you have a viewmodel for each tree item you'll be able to bind each TreeViewItem to the IsExpanded property in your TreeViewItemViewModel (you will have a view model for each tree item) and you will always get the correct value because you've made that data available and bound each item to it.
Your TreeView's ItemsSource will be bound to a collection of TreeViewItemViewModel objects, and your TreeViewItemViewModel class will look something like this:
class TreeViewItemViewModel : INotifyPropertyChanged
{
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
TreeViewItemViewModel Parent { get; }
ObservableCollection<TreeViewItemViewModel> Children { get; }
}
You can find more information on how exactly to create such view model in Josh Smith's excellent article Simplifying the WPF TreeView by Using the ViewModel Pattern.

WPF DataGrid: How do I databind the properties of the SelectedItem to trigger INotifyPropertyChangedEvents?

I'm trying to do this as MVVM as possible:
My Model (InterestTypeEntity) implements INotifyPropertyChanged.
My ViewModel (InterestTypeAllViewModel) has an ObservableCollection that binds to a DataGrid. When changes are made to it, it sends those changes (add/remove) to the Database.
the problem is, I want to also be able to update the database when the properties of the objects within the collection change. I'm not sure how to do that? Here's my code so far...
XAML:
<DataGrid Name="TestGrid" Grid.Row="3" Grid.ColumnSpan="2" AutoGenerateColumns="False"
ItemsSource="{Binding IntTypes}" SelectedItem="{Binding CurrentIntType}">
<DataGrid.Columns>
<DataGridTextColumn Header="Interest ID" Binding="{Binding IntType}" />
<DataGridTextColumn Header="Interested Parties Description" Binding="{Binding Description}" MaxWidth="500" />
</DataGrid.Columns>
</DataGrid>
ViewModel Code:
public ObservableCollection<InterestTypeEntity> IntTypes
{
get { return DataRepository.InterestTypeEntities; }
}
public InterestTypeEntity CurrentIntType { get; set; }
public Int16 IntType
{
get { return CurrentIntType.IntType; }
set
{
if (value != CurrentIntType.IntType)
{
CurrentIntType.IntType = value;
OnPropertyChanged("IntType");
}
}
}
public String Description
{
get { return CurrentIntType.Description; }
set
{
if (value != CurrentIntType.Description)
{
CurrentIntType.Description = value;
OnPropertyChanged("Description");
}
}
}
Don't create a collection of model objects, and don't implement IntType and Description properties on your (current) view model. And unless you have some other reason to do so, don't implement property-change notification in your model.
Instead, make IntTypes a collection of InterestTypeEntityViewModel objects.
This class wraps InterestTypeEntity. It exposes IntType and Description properties that a) wrap the underlying InterestTypeEntity properties and b) performs property change notification. If you make its constructor take an InterestTypeEntity argument, it's easy to populate in your view model:
IntTypes = new ObservableCollection<InterestTypeEntityViewModel>(
DataRepository.InterestTypeEntities.Select(x => new InterestTypeEntityViewModel(x));
Bind the ItemsSource to this collection. (Also, make CurrentIntType a property of type InterestTypeEntityViewModel and raise PropertyChanged when it changes.)
Edit:
If the owning view model needs to be notified when properties change on the items in its collection, it's pretty simple to make it handle the PropertyChanged events they're raising. In your constructor, add:
foreach (InterestTypeEntityViewModel vm in IntTypes)
{
vm.PropertyChanged += InterestTypeEntityViewModel_PropertyChanged;
}
and this method:
private void InterestTypeEntityViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
InterestTypeEntityViewModel vm = (InterestTypeEntityViewModel) sender;
// check e.PropertyName and do whatever you need to do here.
}
Don't forget to unregister the event handler if you remove an object from your collection; otherwise, the child view model objects won't get disposed until the parent one does.
Note, by the way, that by implementing the view models this way, you can exercise a lot of control over your updates to the underlying entity model. For instance, you can implement a command in your parent view model that does all of the updates in a single operation, and another one that lets the user discard all of the changes they've made in the UI without performing an update. And all of this logic is very nicely decoupled from the actual presentation layer.
Please see my answer here. It will give you an observable collection that tells you when the collection changes, or when an item within the collection changes.
General strategy:
Add TwoWay to your binding:
SelectedItem="{Binding CurrentIntType,Mode=TwoWay}"
And then subscribe to the changed event of the observable collection in your ViewModel. When the collection changes, send it to your model/DAL and persist it.

WPF TreeView bound to ObservableCollection not updating root nodes

Sorry - my question is almost identical to this one but since it didn't receive a viable answer, I am hoping that someone else has some fresh ideas.
I have a WPF TreeView that is bound to a hierarchy of a single type:
public class Entity
{
public string Title { get; set; }
public ObservableCollection<Entity> Children { get; set; }
}
The Entity class implements INotifyPropertyChanged, but I have omitted this code for clarity.
The TreeView is bound to an ObservableCollection<Entity> and each Entity instance exposes a set of contained Entity instances via its Children property:
<TreeView ItemsSource="{Binding Path=Entities}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Entity}" ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Initially the TreeView binds as expected and correctly displays a multi-level hierarchy. Also, when the membership of one of the Children collections is programmatically modified, the changes are correctly reflected in the TreeView.
However, changes to the membership of the root member level ObservableCollection<Entity> are not reflected in the TreeView.
Any suggestions would be appreciated.
Thanks,
Tim
My initial guess is that you have something like the following for the root node:
public ObservableCollection<Entity> Entities
{
get;
set;
}
Then, instead of doing something [good] like the following:
Entities.Clear();
foreach (var item in someSetOfItems)
Entities.Add(item);
You are doing something [bad] like this:
Entities = new ObservableCollection<Entity>(someSetOfItems);
You should be able to track down the issue by making the backing field of the Entities property readonly:
private readonly ObservableCollection<Entity> _entities
= new ObservableCollection<Entity>();
public ObservableCollection<Entity> Entities
{
get
{
return _entities;
}
}
Further explanation, long time for answer to come, but I believe that if you do the binding in XAML, and then in code assign a new object to the property you break the binding, so you would have to redo the binding in code for it to work. Hence the solution with the readonly backing field. If doing like that you will not be able to assign a new ObservableCollection and you won't break the binding by assigning a new object to the backing field.

Sending data from view to viewmodel with command binding

Question:
How do send data to a view model when using command binding? So that, for example, when i click a button, it sends the "currently selected index" of a list so that it can perform an operation on that item of the list
Further Information:
I'm working on a program where i have a list of shipments, and each shipment has a list of pallets. I want to make a button that will allow me to add a new pallet to the currently selected shipment. >Edit> And to through another wrench into the works, each pallet has a list of products. so not only do i need to know what shipment i'm on, but i also need to know what pallet of what shipment I'm on.
When I do a command binding, I have no idea how to send the data to the ViewModel. I would like to keep this pure MVVM so i don't want to have the ViewModel checking the view for anything.
~N
Edits:
11/04/09 - I removed the section of the question about the instantiation of the ViewModel. I'll ask that again in a different question as this one is well on track for solving the other question. And I made a few other edits to the question to clearify in the direction i want. as well as changed some grammatical bits so that it wasn't talking about two questions when there is only one.
I usually expose a CollectionView from the view model and set the IsSynchronizedWithCurrentItem property on the ItemsControl displaying the list in the view. Then when the command is executed, I can inspect the CollectionView.CurrrentItem propety to see what is currently selected.
EDIT: This answer addresses the first question in your, um, question. Rather than your view sending the currently selected item to the ViewModel, the ViewModel keeps track of the currently selected item. So using this technique you don't need to work out how to send that information.
Something like this in your view model:
class ApplicationViewModel
{
// Exposes a list of ShipmentViewModels.
public CollectionView Shipments { get; private set; }
// A DelegateCommand or similar, that when executed calls AddPallet().
public ICommand AddPalletCommand { get; private set; }
void AddPallet()
{
ShipmentViewModel shipment = (ShipmentViewModel)Shipments.CurrentItem;
shipment.Pallets.Add(new PalletViewModel(...));
}
}
And then this in your xaml:
<ListBox ItemsSource="{Binding Shipments}" IsSynchronizedWithCurrentItem="True"/>
<Button Command="{Binding AddPalletCommand}>Add Pallet</Button>
This way you can also track the selection of the Shipments collection from your ViewModel and update the command's CanExecute state.
Does that help any?
For keeping track of the currently selected item I do something similar to Groky, maybe this example make a little more sense.
In your ViewModel that contains the collection that your list is bound to (I'm using a ListBox in this example) expose a property that relates to the selected item.
// Assuming your using the MVVM template from Microsoft
public class PalletListViewModel : ViewModelBase
{
// The collection our list is bound to
private ObservableCollection<Pallet> _palletList;
// The current selected item
private Pallet _selectedPallet;
// Our command bound to the button
private DelegateCommand _processCommand;
public ObservableCollection<Pallet> PalletList
{
get { return _palletList; }
}
public Pallet SelectedPallet
{
get { return _selectedPallet; }
set
{
if(value == _selectedPallet)
return;
_selectedPallet = value;
// INotifyPropertyChanged Method for updating the binding
OnPropertyChanged("SelectedPallet");
}
}
public ICommand ProcessCommand
{
get
{
if(_processCommand == null)
_processCommand = new DelegateCommand(Process);
return _processCommand;
}
}
private void Process()
{
// Process the SelectedPallet
}
}
<Window ...>
<Grid x:Name="LayoutRoot">
<Button Content="Process Pallet" Command="{Binding ProcessCommand}" />
<ListBox ItemsSource="{Binding PalletList}" SelectedItem="{Binding SelectedPallet}">
...
</ListBox>
</Grid>
</Window>
Hopefully this is what your looking for.

WPF - Using databinding to bind listbox of items and textboxes of detail

I am trying set up databinding as described in the title.
The problem I having is binding to a generic list.
Any examples out there.
I can't use BindingListCollectionView on a generic list so have to use
CollectionView.
The issue I am puzzled about is to add a new item
on click of Add button I add a new item to the
generic list and refresh the View. But if user
does not follow through the list now has empty
item.
I know this is basic but how is that handled normally?
Malcolm
I see two questions here and I'll try to answer them step by step.
Binding list of items with detail view
Given these ViewModel classes (imagine everyone implements INotifyPRopertyChanged):
public class DataView {
public Item SelectedItem {get; set; }
public List<Item> Items { get; private set; }
}
public class Item {
public string Title { get; set; }
}
Putting a Data instance into the DataContext, a minimal View might look like this:
<StackPanel>
<ListView Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<TextBox Text="{Binding SelectedItem.SelectedItem.Title}" />
</StackPanel>
Adding new items
To be able to create a new Item without immediately adding it to the list, you might want to split off the newly created object into its own area. Visually you can either have it in a new pop up or integrated into the list, but actually it'll be only added to the list on the next try to add or acknowledge the parent dialog. At this point you can also verify whether the Item is valid enough to add it to the list.

Resources