I have ObserveCollection (entity) associated with ICollectionView
Everything works fine until I try to delete the entry. After clicking on the 'delete' button, the interface is not updated.
If i set ObserveCollection everything works fine
private ICollectionView _taskview;
public ICollectionView TasksView
{
get { return _taskview; }
set
{
_taskview = value;
OnPropertyChanged("TaskView");
}
}
public ICommand DeleteTask
{
get
{
return new DelegateCommand(() =>
{
_context.Task.Attach(SelectTask);
_context.Task.Remove(SelectTask);
_context.SaveChanges();
Tasks = new ObservableCollection<TaskModel>(_context.Task);
TasksView = CollectionViewSource.GetDefaultView(Tasks);
});
}
}
public HomeViewModel(Window window)
{
this.window = window;
Tasks = new ObservableCollection<TaskModel>(_context.Task);
TasksView = CollectionViewSource.GetDefaultView(Tasks);
}
<ListBox Grid.Row="1" Grid.RowSpan="2" Grid.Column="0"
SelectionMode="Extended"
ItemsSource="{Binding TasksView}"
SelectedItem="{Binding SelectTask}">
</ListBox>
Don't create a new collection after each deletion. This will have negative impact on the performance. This is the reason why you use an ObservableCollection. This way the binding target e.g., a ListBox is able to update the changed items only, instead of recreating/rendering the complete view.
In this this context it also doesn't make sense to expose the data source as ICollectionsView. Rather bind to the ObservableCollection directly.
When the source collection of an ICollectionsView implements INotifyCollectionChanged like ObservableCollection<T> does, then the ICollectionView will automatically update when the source changes.
In this case manipulating the INotifyCollectionChanged collection is sufficient.
When the source collection of an ICollectionsView does not implement INotifyCollectionChanged like List<T>, then the ICollectionView will not automatically update when the source changes.
In this case you must explicitely call ICollectionView.Refresh to force the ICollectionView to update.
Please note that you should never reference any view components in your view model - no exceptions. This eliminates all benefits of MVVM. And it is never necessary, for sure. If your view model requires a reference to a view component that you are designing your code or classes wrong.
To follow this basic and fundamental MVVM design rule you must remove the reference to Window from your HomeViewModel.
You can trigger view behavior by exposing a property on the view model which is the input for a data trigger in the view. Patterns - WPF Apps With The Model-View-ViewModel Design Pattern, The Model-View-ViewModel Pattern.
First Solution (Recommended)
You should bind to the Tasks collection directly.
The moment you need to manipulate the collection's view e.g., to apply a filter retrieve the view using CollectionViewSource.GetDefaultView(Tasks). But don't bind to it.
<ListBox ItemsSource="{Binding Tasks}" />
public HomeViewModel()
{
Tasks = new ObservableCollection<TaskModel>(_context.Task);
Tasks.CollectionChanged += OnTasksChanged;
}
private void OnTasksChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
foreach (TaskModel task in e.NewItems)
{
_context.Task.Add(task);
_context.SaveChanges();
}
break;
}
case NotifyCollectionChangedAction.Remove:
{
foreach (TaskModel task in e.OldItems)
{
_context.Task.Attach(task);
_context.Task.Remove(task);
_context.SaveChanges();
}
break;
}
}
}
// Then simply manipulate the 'Tasks' collection
public ICommand DeleteTaskCommand => new DelegateCommand(() => Tasks.Remove(SelectTask));
Second Solution
If you want to bind to the ICollectionView, you don't need the additional ObservableCollection anymore (except you want to maintain two collections and a ICollectionView on every add/move/remove/reset operation). To update the collection's view call ICollectionView.Refresh.
<ListBox ItemsSource="{Binding TasksView}" />
public HomeViewModel()
{
TasksView = CollectionViewSource.GetDefaultView(_context.Task);
}
// Then simply refresh the 'TasksView':
public ICommand DeleteTask => DelegateCommand(
() =>
{
_context.Task.Attach(SelectTask);
_context.Task.Remove(SelectTask);
_context.SaveChanges();
// Update the view
TasksView.Refresh();
});
Call Refresh() on View property of CollectionViewSource to get it refreshed.
You have a typo on:
public ICollectionView TasksView
{
get { return _taskview; }
set
{
_taskview = value;
OnPropertyChanged("TaskView");
}
}
At OnPropertyChanged("TaskView"); it should be OnPropertyChanged("TasksView");
Related
I have an app that retrieves data from a database and displays it in data grid on the main window. The maximum number of items being displayed is ~5000.
I don't mind a time delay in display the results, but i'd like to display a loading animation whilst this is happening. However, even when using a background worker to update the collection view source the UI freezes before displaying the rows.
Is it possible to add all these rows without freezing the UI? Apply filters to the collection view source also seems to freeze the UI which i'd like to avoid also if possible.
Thanks in advance!
UPDATE 06.01.2023
Updated as per the suggestions from BionicCode and Andy and now everything is running very smoothly - thank you for the help!
XAML for the data grid:
<DataGrid Grid.Column="1" Name="documentDisplay" ItemsSource="{Binding Source={StaticResource cvsDocuments}, UpdateSourceTrigger=PropertyChanged, IsAsync=True}" AutoGenerateColumns="False"
Style="{StaticResource DataGridDefault}" ScrollViewer.CanContentScroll="True"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" ColumnWidth="*">
XAML for collection view source:
<Window.Resources>
<local:Documents x:Key="documents" />
<CollectionViewSource x:Key="cvsDocuments" Source="{StaticResource documents}"
Filter="DocumentFilter">
Code within function being called after retrieving data from database:
Documents _documents = (Documents)this.Resources["documents"];
BindingOperations.EnableCollectionSynchronization(_documents, _itemsLock);
if (!populateDocumentWorker.IsBusy)
{
progressBar.Visibility = Visibility.Visible;
populateDocumentWorker.RunWorkerAsync(jobId);
}
Code within worker:
Documents _documents = (Documents)this.Resources["documents"];
lock (_itemsLock)
{
_documents.Clear();
_documents.AddRange(documentResult.documents);
}
Observable collection class:
public class Documents : ObservableCollection<Document>, INotifyPropertyChanged
{
private bool _surpressNotification = false;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_surpressNotification)
{
base.OnCollectionChanged(e);
}
}
public void AddRange(IEnumerable<Document> list)
{
if(list == null)
{
throw new ArgumentNullException("list");
_surpressNotification = true;
}
foreach(Document[] batch in list.Chunk(25))
{
foreach (Document item in batch)
{
Add(item);
}
_surpressNotification = false;
}
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
Base class for observable collection:
public class Document : INotifyPropertyChanged, IEditableObject
{
public int Id { get; set; }
public string Number { get; set; }
public string Title { get; set; }
public string Revision { get; set; }
public string Discipline { get; set; }
public string Type { get; set; }
public string Status { get; set; }
public DateTime Date { get; set; }
public string IssueDescription { get; set; }
public string Path { get; set; }
public string Extension { get; set; }
// Implement INotifyPropertyChanged interface.
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String info)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private void NotifyPropertyChanged(string propertyName)
{
}
// Implement IEditableObject interface.
public void BeginEdit()
{
}
public void CancelEdit()
{
}
public void EndEdit()
{
}
}
Filter Function:
private void DocumentFilter(object sender, FilterEventArgs e)
{
//Create list of all selected disciplines
List<string> selectedDisciplines = new List<string>();
foreach(var item in disciplineFilters.SelectedItems)
{
selectedDisciplines.Add(item.ToString());
}
//Create list of all select document types
List<string> selectedDocumentTypes = new List<string>();
foreach(var item in docTypeFilters.SelectedItems)
{
selectedDocumentTypes.Add(item.ToString());
}
// Create list of all selected file tpyes
List<string> selectedFileTypes = new List<string>();
foreach(var item in fileTypeFilters.SelectedItems)
{
selectedFileTypes.Add(item.ToString());
}
//Cast event item as document object
Document doc = e.Item as Document;
//Apply filter to select discplines and document types
if( doc != null)
{
if (selectedDisciplines.Contains(doc.Discipline) && selectedDocumentTypes.Contains(doc.Type) && selectedFileTypes.Contains(doc.Extension))
{
e.Accepted = true;
} else
{
e.Accepted = false;
}
}
}
There are a couple of problems with your design here.
The way the filter of a collectionview works is it iterates through the collection one by one and returns true/false.
EDIT:
Experimentation seems to confirm this statement is true. AFAIK virtualisation is purely in creation of UI from the collection. Collectionviewsource > Collectionview > Itemssource. Creation of UI rows is virtualised by the virtualising stackpanel but the whole collection will be read into itemssource.
Your filter is complicated and will take a while per item.
It's running 5000 times.
You should not use that approach to filter.
A rethink and fairly substantial refactor is advisable.
Do all your processing and filtering in a Task you run as a background thread.
Forget all that synchronisation context stuff.
Once you've done your processing, return a List of your finalised data back to the UI thread
async Task<List<Document>> GetMyDocumentsAsync
{
// processing filtering and really expensive stuff.
return myListOfDocuments;
}
If that doesn't get edited or sorted then set a List property your itemssource is bound to.
If it does either then new up an observablecollection
YourDocuments = new Observablecollection<Document>(yourReturnedList);
passing your list as a constructor paremeter and set a observablecollection property your itemssource is bound to.
Hence you do ALL your expensive processing on a background thread.
That is returned to the UI thread as a collection.
You set itemssource to that via binding.
The custom observablecollection is a bad idea. You should just use List or Observablecollection where t is a viewmodel. Any viewmodel should implement inotifypropertychanged. Always.
Two caveats.
Minimise the number of rows you present to the UI.
If it's more than a couple of hundred then consider paging and maybe an intermediate cache.
Remove this out your binding
, UpdateSourceTrigger=PropertyChanged
And never use it again until you know what it does.
Some generic datagrid advice:
Avoid column virtualisation.
Minimise the number of columns you bind.
If you can, have fixed column widths.
Consider the simpler listview rather than datagrid.
The problem is your Filter callback. Currently you iterate over three lists inside the event handler (in order to create the filter predicate collections for lookup).
Since the event handler is invoked per item in the filtered collection, this introduces excessive work load for each filtered item.
For example, if each of the three iterations involves 50 items and the filtered collection contains 5,000 items, you execute a total of 5035000 = 750,000 iterations (150 for each event handler invocation).
I recommend to maintain the collections of selected items outside the Filter event handler, so that it doesn't have to be created for each individual item (event handler invocation). The three collections are only updated when a related SelectedItems property has changed.
To further speed up the lookup in the Filter event handler I also recommend to replace the List<T> with a HashSet<T>.
While List.Contains is an O(n) operation, HashSet.Containsis O(1), which can make a huge difference.
You need to track the SelectedItems that are the source for those collections separately to update them.
The following example should speed up your filtering significantly.
/* Define fast O(1) lookup collections */
private HashSet<string> SelectedDisciplines { get; set; }
private HashSet<string> SelectedDocumentTypes { get; set; }
private HashSet<string> SelectedFileTypes { get; set; }
// Could be invoked from a SelectionChanged event handler
// (what ever the source 'disciplineFilters.SelectedItems' is)
private void OnDisciplineSelectedItemsChanged()
=> this.SelectedDisciplines = new HashSet<string>(this.disciplineFilters.SelectedItems.Select(item => item.ToString()));
// Could be invoked from a SelectionChanged event handler
// (what ever the source 'docTypeFilters.SelectedItems' is)
private void OnDocTypeSelectedItemsChanged()
=> this.SelectedDocumentTypes = new HashSet<string>(this.docTypeFilters.SelectedItems.Select(item => item.ToString()));
// Could be invoked from a SelectionChanged event handler
// (what ever the source 'fileTypeFilters.SelectedItems' is)
private void OnFileTypeSelectedItemsChanged()
=> this.SelectedFileTypes = new HashSet<string>(this.fileTypeFilters.SelectedItems.Select(item => item.ToString()));
private void FilterDocuments(object sender, FilterEventArgs e)
{
// Cast event item as document object
if (e.Item is not Document doc) //if (!(e.Item is Document doc))
{
return;
}
// Apply filter to select discplines and document types
e.Accepted = this.SelectedDisciplines.Contains(doc.Discipline)
&& this.SelectedDocumentTypes.Contains(doc.Type)
&& this.SelectedFileTypes.Contains(doc.Extension);
}
Remarks
You should fix your Documents.AddRange method.
It should use the NotifyCollectionChangedAction.Add. NotifyCollectionChangedAction.Replace will trigger the binding target to completely update itself, which is what you want to avoid.
Use the appropriate NotifyCollectionChangedEventArgs constructor overload to send the complete range of added items with the event:
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list as IList ?? list.ToList()));
Further considerations
Since you said you read the data from a database, you should consider to let the database filter the data for you. Given the correct query, the database will provide far better performance as it is highly optimized for filtering (search queries).
Using the filter feature of the ICollectionView will always block the UI until the collection is filtered. This is because the procedure is not asynchronous. This means you can't display a progress bar as it won't update in real time. Consider to prefilter the items when fetching them from the database. It doesn't make sense to load 5k items when the user can only view 10-50 of them.
If you want to display a progress bar, you better filter the collection directly. This requires a dedicated binding source collection. Since you have already implemented a custom ObservableCollection that exposes a AddRange method, you are good to go (don't forget to fix the CollectionChanged event data).
To add grouping you have to take into consideration that
a) grouping disables row virtualization
b) grouping actually takes place in the UI. The control creates a GroupItem for each group.
To fix a) you need to explicitly enable virtualization while grouping by setting the attached VirtualizingPanel.IsVirtualizingWhenGrouping to true:
<DataGrid VirtualizingPanel.IsVirtualizingWhenGrouping="True" />
To fix b) you could use LINQ grouping, which you could execute on a background thread if necessary:
IEnumerable<IGrouping<string, Document>> groupedDocuments = FilteredItemsSource.DataGridItems.GroupBy(document => document.Author);
dataGrid.ItemsSource = groupedDocuments;
The problem is that DataGrid doesn't know how to display IGrouping. You have to get creative here. Probably extending DataGrid to add this feature would be the best.
If this is not an option, then the only solution I think that is reasonable is to implement data virtualization.
I generally believe that it doesn't make sense to show 5k items at once while the user can only view a fraction.
Just imagine you have 5k items in two groups each of 2.5k items. When the user opens the first, he needs to scroll down 2.5k items before he can see the second group. The UX couldn't get any worse at this point.
If this was my problem to solve, I would reduce the number of items to load. Additionally I would ask myself if the data structure is the correct form to display the data. For example you could create top-down filtering: like first let user select an author, then a created date, etc. Use this filter information to query the database. This should significantly reduce the number of items to display/handle.
Alternatively create an index or use an indexing service like Elastic Search. Such service come with a very advanced query syntax that allow to search/filter the indexed documents more comfortable.
What you are currently doing is not efficient at all and provides a really bad UX.
The following example extends the basic example from above.
You need to bind your DataGrid to the FilteredItemsSource property while you populate the UnfilteredDocuments property with the data from the database.
The example also shows how to replace the CollectionViewSource in XAML with a ICollectionView that is more convenient to handle from C# (code-behind).
it also shows how to gracefully toggle a ProgressBar using the .NET BooleanToVisibilityConverter.
MainWindow.xaml
<Window>
<Window.Resources>
<!-- Use the existing .NET value converter -->
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<StackPanel>
<ProgressBar IsIndeterminate="True"
Height="4"
Visibility="{Binding ElementName=Window, Path=IsFilterInProgress, Converter={StaticResource BooleanToVisibilityConverter}}" />
<DataGrid ItemsSource="{Binding FilteredItemsSource, Mode=OneTine}"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
AutoGenerateColumns="False">
</DataGrid>
</StackPanel>
</Window>
*MainWindow.xaml.cs
// The binding source for the ProgressBar.
// Can be bound to Visibility or used as predicate for a Trigger
// This property must be implemented as dependency property!
public bool IsFilterInProgress { get; private set; }
// Binding source for the ItemsControl
public Documents FilteredItemsSource { get; } = new Documents();
// Structure for the database data
private List<Document> UnfilteredDocuments { get; } = new List<Document>();
/* Define fast O(1) lookup collections */
private HashSet<string> SelectedDisciplines { get; set; }
private HashSet<string> SelectedDocumentTypes { get; set; }
private HashSet<string> SelectedFileTypes { get; set; }
private object SyncLock { get; } = new object();
// Constructor
public MainWindow()
{
InitializeComponent();
// Enable CollectionChanged propagation to the UI thread
// when updating a INotifyCollectionChanged collection from a background thread
BindingOperations.EnableCollectionSynchronization(this.FilteredItemsSource, this.SyncLock);
}
// Could be invoked from a SelectionChanged event handler
// (what ever the source 'disciplineFilters.SelectedItems' is)
private async void OnDisciplineSelectedItemsChanged(object sender, EventArgs e)
{
this.SelectedDisciplines = new HashSet<string>(this.disciplineFilters.SelectedItems.Select(item => item.ToString()));
await ApplyDocumentFilterAsync();
}
// Could be invoked from a SelectionChanged event handler
// (what ever the source 'docTypeFilters.SelectedItems' is)
private async void OnDocTypeSelectedItemsChanged(object sender, EventArgs e)
{
this.SelectedDocumentTypes = new HashSet<string>(this.docTypeFilters.SelectedItems.Select(item => item.ToString()));
await ApplyDocumentFilterAsync();
}
// Could be invoked from a SelectionChanged event handler
// (what ever the source 'fileTypeFilters.SelectedItems' is)
private async void OnFileTypeSelectedItemsChanged(object sender, EventArgs e)
{
this.SelectedFileTypes = new HashSet<string>(this.fileTypeFilters.SelectedItems.Select(item => item.ToString()));
await ApplyDocumentFilterAsync();
}
private async Task ApplyDocumentFilterAsync()
{
// Show the ProgressBar
this.IsFilterInProgress = true;
// Allow displaying of a progress bar (prevent the UI from freezing)
await Task.Run(FilterAndSortDocuments);
// Because grouping is actually happening in the UI (by creating GroupItems)
// we can't group on a background thread.
GroupDocuments();
// Hide the ProgressBar
this.IsFilterInProgress = false;
}
// Improve performance by filtering and sorting in one step.
// Use FilterDocuments() if filtering alone (no sorting) is required.
private void FilterAndSortDocuments()
{
IEnumerable<Document> filteredDocuments = GetFilteredDocuments();
// For example sort descending by the property Document.Id
IOrderedEnumerable<Document> filteredAndSortedDocuments = filteredDocuments
.OrderByDescending(document => document.Id);
this.FilteredItemsSource.AddRange(filteredAndSortedDocuments);
}
private void FilterDocuments()
{
this.FilteredItemsSource.Clear();
IEnumerable<Document> filteredDocuments = GetFilteredDocuments();
this.FilteredItemsSource.AddRange(filteredDocuments);
}
private void GroupDocuments()
{
ICollectionView filteredItemsSourceCollectionView = CollectionViewSource.GetDefaultView(this.FilteredItemsSource);
// Allow multiple GroupDescription.Add() and Clear()
// without raising change notifications every time.
// A single change notification is raised after leaving the using scope.
using (var deferredRefreshContext = filteredItemsSourceCollectionView.DeferResfresh())
{
GroupDescriptions groupDescriptions = filteredItemsSourceCollectionView.GroupDescriptions;
groupDescriptions.Clear();
groupDescriptions.Add(new PropertyGroupDescription(nameof(Document.Author)));
}
}
private IEnumerable<Document> GetFilteredDocuments()
{
IEnumerable<Document> filteredDocuments = this.UnfilteredDocuments.Where(IsDocumentAccepted);
return filteredDocuments;
}
private bool IsDocumentAccepted(Document document)
=> this.SelectedDisciplines.Contains(doc.Discipline)
&& this.SelectedDocumentTypes.Contains(doc.Type)
&& this.SelectedFileTypes.Contains(doc.Extension);
I am trying to get a picker to populate with data. There doesn't seem to be a straight answer anywhere on this. I have tried numerous things, one thing that does work is :
Xaml:
<Picker Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="4"
Title="Driver Name"
ItemsSource="{Binding Drivers}"
SelectedItem="{Binding driverID}"
and in the View Model:
List<string> Drivers = new List<string> { "Steve","Dave" };
This works fine, but its just a dummy capability as in the future these names will be grabbed from a service of some kind.
So in an attempt to copy this I tried separating this list into a mock service and just returning the list to the view model and making it work that way.
But this still returns nothing to the front end even though I can see that the list is not blank when debugging.
Then I tried to create a class of Driver and return instances of the class that has the name in it and access it in the Xaml.
This did not work, I even tried a variation using IList, this did not work either.
I am not sure why this does not work as the list was just separated to essentially a different class. For instance this is what I am trying now:
Xaml:
<Picker Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="4"
Title="Driver Name"
ItemsSource="{Binding Drivers}"
SelectedItem="{Binding driverID}"
/>
View Model:
This is bound to the picker
public List<string> Drivers;
Then this method is called from the constructor:
public async Task FindDriverNames()
{
Drivers = await GetDriverNames();
}
and in the Model:
public async Task<List<string>> GetDriverNames()
{
await Sleep();
List<string> _drivers = new List<string> { "Steve"};
return _drivers;
}
This does not work, but when run through debug it shows that Drivers is populated.
I have wasted a lot of time trying to get this work, does anyone have insights?
You'll need an ObservableCollection, and possibly a INotifyPropertyChanged interface implementation to notify the view for any changes.
public class YourViewModel: INotifyPropertyChanged
{
public YourViewModel()
{
Drivers = new ObservableCollection<string>();
}
private ObservableCollection<string> _drivers;
public ObservableCollection<string> Drivers
{
get { return _drivers; }
set
{
if (Equals(value, _drivers)) return;
_drivers= value;
OnPropertyChanged(nameof(Drivers));
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
By implementing the INotifyPropertyChanged interface, you will allow the view to be updated whenever the whole list changes. The observable collection will notify the UI if a item is added to the collection.
As noted by Roman, youo can also, in this specific case use the observable collection to update the ui.
public async Task FindDriverNames()
{
Drivers.Clear();
Drivers.AddRange(await GetDriverNames());
}
For other bound properties you'll still need the OnPropertyChanged event.
See ObservableCollection<T> and INotifyPropertyChanged
I'm using an MVVM structure with EF 6.0 as my data source and a Datagrid as my user control.
I have:
Viewmodels: a base that implements INotifyPropertChanged, a main viewmodel that holds other viewmodels and a "concrete" viewmodel where data is displayed from.
Commands: I have an ICommand class and use that in my viewmodel.
In short it so far correctly displays data in a datagrid. Now I want to perform CRUD operations. However when I make changes to the datagrid and then hit my save button (which is bound to an ICommand property) none of the changes propagate back to the entity.
Here is my viewmodel:
class SymbolWeightsViewModel : ViewModelBase
{
BenchMarkEntities _context = new BenchMarkEntities();
RelayCommand _updateCommand;
public ObservableCollection<Weight> Weights
{
get;
private set;
}
public BenchMarkEntities Context
{
get { return _context; }
}
public SymbolWeightsViewModel()
{
_context.Weights.Load();
this.Weights = _context.Weights.Local;
}
~SymbolWeightsViewModel()
{
_context.Dispose();
}
public ICommand UpdateCommand
{
get
{
if (_updateCommand == null)
{
_updateCommand = new RelayCommand(param => this.UpdateCommandExecute(),
param => this.UpdateCommandCanExecute);
}
return _updateCommand;
}
}
void UpdateCommandExecute()
{
using (_context = new BenchMarkEntities())
{
_context.SaveChanges();
}
}
bool UpdateCommandCanExecute
{
get {return true;}
}
My opinion is BenchmarkEntities object and Datagrid are not seeing each other. Why I'm not sure but somewhere that binding is failing. Do I need to reinstantiate BenchmarkEntities to perform an update?
And my View:
<StackPanel>
<DataGrid ItemsSource="{Binding Weights, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="True">
</DataGrid>
<Button Name="SaveChanges" Height="32" Command="{Binding UpdateCommand}">
</Button>
</StackPanel>
View related: I think binding the entire Weight object with the DataGrid is probably not going to work. I need to create and bind to individual columns. Is this true or can you perform updates on a part of an entity via a datagrid?
Really appreciate any help or direction.
Thanks
You create a new Context and then SaveChanges on the new Context which has no pending changes:
void UpdateCommandExecute()
{
using (_context = new BenchMarkEntities())
{
_context.SaveChanges();
}
}
As Mike said:
"You create a new Context and then SaveChanges on the new Context which has no pending changes"
It means you shoud not create new context in function UpdateCommandExecute.
So, try modify your UpdateCommandExecute function as following:
void UpdateCommandExecute()
{
_context.SaveChanges();
}
Happy Coding
Is there any way to automatically update a filter on an ICollectionView without having to call Refresh() when a relevant change has been made?
I have the following:
[Notify]
public ICollectionView Workers { get; set; }
The [Notify] attribute in this property just implements INotifyPropertyChanged but it doesn't seem to be doing anything in this situation.
Workers = new CollectionViewSource { Source = DataManager.Data.Workers }.View;
Workers.Filter = w =>
{
Worker worker = w as Worker;
if (w == null)
return false;
return worker.Employer == this;
};
In XAML:
<TextBlock x:Name="WorkersTextBlock"
DataContext="{Binding PlayerGuild}"
FontFamily="Pericles"
Text="{Binding Workers.Count,
StringFormat=Workers : {0},
FallbackValue=Workers : 99}" />
Update: It looks like using ICollectionView is going to be necessary for me, so I'd like to revisit this topic. I'm adding a bounty to this question, the recipient of which will be any person who can provide some insight on how to implement a 'hands-off' ICollectionView that doesn't need to be manually refreshed. At this point I'm open to any ideas.
AFAIK there is no inbuilt support in ICollectionView to refresh collection on any property change in underlying source collection.
But you can subclass ListCollectionView to give it your own implementation to refresh collection on any property changed. Sample -
public class MyCollectionView : ListCollectionView
{
public MyCollectionView(IList sourceCollection) : base(sourceCollection)
{
foreach (var item in sourceCollection)
{
if (item is INotifyPropertyChanged)
{
((INotifyPropertyChanged)item).PropertyChanged +=
(s, e) => Refresh();
}
}
}
}
You can use this in your project like this -
Workers = new MyCollectionView(DataManager.Data.Workers);
This can be reused across your project without having to worry to refresh collection on every PropertyChanged. MyCollectionView will do that automatically for you.
OR
If you are using .Net4.5 you can go with ICollectionViewLiveShaping implementation as described here.
I have posted the implementation part for your problem here - Implementing ICollectionViewLiveShaping.
Working code from that post -
public ICollectionViewLiveShaping WorkersEmployed { get; set; }
ICollectionView workersCV = new CollectionViewSource
{ Source = GameContainer.Game.Workers }.View;
ApplyFilter(workersCV);
WorkersEmployed = workersCV as ICollectionViewLiveShaping;
if (WorkersEmployed.CanChangeLiveFiltering)
{
WorkersEmployed.LiveFilteringProperties.Add("EmployerID");
WorkersEmployed.IsLiveFiltering = true;
}
For .Net 4.5:
There is a new interface which can help to achieve this feature, called : ICollectionViewLiveShaping.
From MSDN link:
When live sorting, grouping, or filtering is enabled, a CollectionView
will rearrange the position of data in the CollectionView when the
data is modified. For example, suppose that an application uses a
DataGrid to list stocks in a stock market and the stocks are sorted by
stock value. If live sorting is enabled on the stocks' CollectionView,
a stock's position in the DataGrid moves when the value of the stock
becomes greater or less than another stock's value.
More Info on above interface:
http://www.jonathanantoine.com/2011/10/05/wpf-4-5-%E2%80%93-part-10-live-shaping/
For .Net 4 and lower:
There is also another post on SO QA which might help you:
CollectionViewSource Filter not refreshed when Source is changed
I've started an MVVM project and now I'm stucking with correct DataBinding.
My project has:
A UserControl whit a ViewModel as DataContext like:
public partial class TestUserControl: UserControl
{
public TestUserControl()
{
this.DataContext = new TestUserControlViewModel();
}
}
ViewModel code is (BaseViewModel class contains PropertyChangedEventHandler):
public class TestUserControlViewModel : BaseViewModel
{
public KrankenkasseControlViewModel()
{}
public IEnumerable<DataItem> GetAllData
{
get
{
IGetTheData src= new DataRepository();
return src.GetData();
}
}
}
IGetTheData is the interface to DataContext:
public interface IGetTheData
{
IEnumerable<DataItem> GetData();
}
}
and finally the DataRepository code:
public class DataRepository : IGetTheData
{
private TestProjectDataContext dax = new TestProjectDataContext();
public IEnumerable<DataItem> GetData()
{
return (from d in this.dax.TestData
select new DataItem
{
ID = d.ID,
SomeOtherData = d.SomeOtherData
});
}
}
My UserControl has a few TextBoxes, but what's the best way to bind correctly?
Thanks for your help, regards.
EDIT: Binding the data against multiple textboxes
After reading your comment, I will elaborate my example for textboxes.
First important thing is that the ViewModel will model the things in the View, so that the View gets all information it needs in the structure it needs. That means, if you have multiple textboses in the View, you will need multiple string Properties in your ViewModel, one for each textbox.
In your XAML you could have something like
<TextBox Text="{Binding ID, Mode=TwoWay}" />
<TextBox Text="{Binding SomeOtherData, Mode=TwoWay}" />
and in your ViewModel
public class TestUserControlViewModel : BaseViewModel {
private string id;
private string someOtherData;
public TestUserControlViewModel() {
DataItem firstItem = new DataRepository().GetData().First();
this.ID = firstItem.ID;
this.SomeOtherData = firstItem.SomeOtherData;
}
public string ID {
get {
return this.id;
}
set {
if (this.id == value) return;
this.id = value;
this.OnPropertyChangedEvent("ID");
}
}
public string SomeOtherData {
get {
return this.someOtherData;
}
set {
if (this.someOtherData == value) return;
this.someOtherData = value;
this.OnPropertyChangedEvent("SomeOtherData");
}
}
}
Here I assume that in your BaseViewModel there is an OnPropertyChangedEvent method to fire the corresponding event. This tells the View that the property has changed and it must update itself.
Note the Mode=TwoWay in the XAML. This means, that it doesn't matter on which side the value changes, the other side will reflect the change immediately. So if the user changes a value in a TwoWay bound TextBox, then the corresponding ViewModel property will automatically change! And also vice versa: if you change the ViewModel property programmatically, the View will refresh.
If you want to show multiple textboxes for more than one data item, then you must introduce more Properties in the ViewModel and bind them accordingly. Maybe a ListBox with a flexible number of TextBoxes inside is a solution then, like #Haspemulator already answered.
Binding the data against a collection control
In the TestUserControl I guess you have a control (like a ListView) to show the list of loaded things. So bind that control against the list in the ViewModel with
<ListView ... ItemsSource="{Binding GetAllData}" ... />
First you must understand that Binding means not "read the data and then forget the ViewModel". Instead you bind the View to the ViewModel (and its Properties) as long as the View lasts. From this point of view, AllData is a much better name than GetAllData (thanks #Malcolm O'Hare).
Now in your code, every time the View reads the AllData property, a new DataRepository is created. Because of the Binding, that is not what you want, instead you want to have one instance of DataRepository for the whole lifetime of the View, which is used to read the initial data and can later be used to update the View, if the underlying database changes (maybe with an event).
To enable such a behavior you should change the type of the AllData property to an ObservableCollection, so that the View can automatically update the list if changes occur.
public class TestUserControlViewModel : BaseViewModel
private ObservableCollection<DataItem> allData;
public TestUserControlViewModel() {
IGetTheData src = new DataRepository();
this.allData = new ObservableCollection<DataItem>(src.GetData());
}
public ObservableCollection<DataItem> AllData {
get {
return this.allData;
}
}
public void AddDataItem(DataItem item) {
this.allData.Add(item);
}
}
Now if you call AddDataItem later, the ListView will update itself automatically.
Your Property Name is bad. You should call it AllData, not GetAllData.
Since you are returning a collection, you probably should be using some sort of list control (ListBox, ListView).
In that case you'd be doing
<ListBox ItemsSource="{Binding GetAllData}" />
Guten Abend. :) As it already mentioned, since you're returning the collection, it's better to use a ListBox. The comment about having ObservableCollection as a cache is also absolutely valid. I would add that if you need to have your data editable, you should use TextBox inside the ItemTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text={Binding SomeOtherData,Mode=TwoWay} />
</DataTemplate>
</ListBox.ItemTemplate>
In this case if user edits the text in the box, data will be updated in your data object, so that it could be saved in the database later.