Model - ViewModel sync and filtered ObservableCollections - wpf

I am working with EF and MVVM.
My Model contains this:
Public Property Visits As DbSet(Of Visit)
The Visit class implements INotifyPropertyChanged and its properties properly raise PropertyChanged events.
My ViewModel contains these properties, used for my views binding (context is my model):
Private _BareVisits As ObservableCollection(Of Visit)
Public Property BareVisits As ObservableCollection(Of Visit)
Get
If _BareVisits Is Nothing Then
_BareVisits = New ObservableCollection(Of Visit)(context.Visits)
AddHandler _BareVisits.CollectionChanged, AddressOf BareVisits_CollectionChanged
End If
Return _BareVisits
End Get
Set(value As ObservableCollection(Of Visit))
_BareVisits = value
End Set
End Property
Private _Visits As ObservableCollection(Of Visit)
Public Property Visits As ObservableCollection(Of Visit)
Get
If _Visits Is Nothing Then
_Visits = New ObservableCollection(Of Visit)(
BareVisits.Where(
Function(V) Not V.IsMediGenerated AndAlso V.IsHistoryParseComplete
)
)
End If
Return _Visits
End Get
Set(value As ObservableCollection(Of Visit))
_Visits = value
End Set
End Property
Private Sub BareVisits_CollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
Select Case e.Action
Case NotifyCollectionChangedAction.Add
context.Visits.AddRange(e.NewItems.OfType(Of Visit))
context.SaveChanges()
Case NotifyCollectionChangedAction.Remove
context.Visits.RemoveRange(e.OldItems.OfType(Of Visit))
context.SaveChanges()
Case Else
Exit Sub
End Select
End Sub
As you see, the BareVisits property is just the DB collection of my Visits.
The Visits property (the one my view is bound to) depends on BareVisits to return filtered records.
Questions:
1) Which is the best method to save a new visit? By using the event handler posted? Or should I talk to the Model directly and somehow bubble this changes to the ViewModel? If I hook the CollectionChanged handler to the Visits property, do I need this BareVisits property at all?
2) Suppose the changes take place in the Model itself, I suppose I should also perform the record adds / delete to the BareVisits property also. What happens to the Visits property? (the filtered one) I have tried everything and its CollectionChanged event is never fired. Should I tamper with it also by hand?
I just want my view to depict the data changes and I am at a loss.
EDIT: I have also tried to use ICollectionView to filter the raw data (instead of a new ObservableCollection property) but it is not a choice since the data in my datagrid is rendered un-editable this way.
Thank you in advance. Any help will be greatly appreciated.

Related

ListView with grouping and sorting not refresh while INotifyPropertyChanged used

I have my own class which uses INotifyPropertyChanged correctly (Raising updates events), but when a property of type DateTime updated and called (View.Run) the listView not updating untill another property changing (not this property)
Now with the code:
Public Class EntryInfo
Implements INotifyPropertyChanged
ReadOnly Property DateAccessed As Date
Get
.......
Return _Access
End Get
End Property
Readonly Property Property1 as object
Get
.......
Return _Property1
End Get
End Property
Friend Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
RaiseEvent ApropertyHasChanged()
End Sub
Then when I need to Change the "DateAccessProperty" I use this code:
Friend Sub SetAccessTime(Dt As Date)
_Access = Dt
NotifyPropertyChanged("DateAccessed")
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''
After this I have a ListView named "LV1"
Dim Coll as new observableCollection(Of EntryInfo)
....... filing "Coll" with items (EntryInfo)
Lv1.ItemsSource =Coll
Then I do the following:
Do some sort and group operations.
Changing "DateAccessed" value. so that the "ApropertyHasChanged" event fired and at this point I used the following code
Private Sub RefreshViewNow()
Dim _view As ListCollectionView = TryCast(CollectionViewSource.GetDefaultView(LV1.ItemsSource), ListCollectionView)
If _view IsNot Nothing Then _view.Refresh()
'\\\ Items.Refresh()
End Sub
But _view not refreshed.
But if the property "Property1" changed the _View refreshed.
Any help?
The solution is by set the following "_view" properties:
_view.IsLiveFiltering = True
_view.IsLiveGrouping = True
_view.IsLiveSorting = True
or at least one of them if you want one of them only to be activated.

Filter observablecollection (mvvm) [duplicate]

This question already has answers here:
Filter an observable collection
(3 answers)
Closed 4 years ago.
I've got an ObservableCollection(Of PdfMarkupAnnotationDataWrapper) which is bound to a ListBoxEdit. In addition I've got a textbox which should work as a filter.
Now when the user types something in the textbox the ObservableCollection in my Viewmodel should be filtered by the input of the textbox.
Here is my collection
Private Property _annotationList As ObservableCollection(Of PdfMarkupAnnotationDataWrapper)
Public Property AnnotationList As ObservableCollection(Of
PdfMarkupAnnotationDataWrapper)
Get
Return _annotationList
End Get
Set(value As ObservableCollection(Of PdfMarkupAnnotationDataWrapper))
_annotationList = value
OnPropertyChanged()
End Set
End Property
Is there any way to accomplish this task?
I was thinking about copying the collection but there has to be better solution.
I was thinking about copying the collection but there has to be better solution.
That would be to remove or add items from it. But you still need to store the original unfiltered items in another collection.
You could use LINQ to do the filtering, e.g.:
Private _unFilteredAnnotationList As ObservableCollection(Of PdfMarkupAnnotationDataWrapper) 'make sure to populate this one
Private _annotationList As ObservableCollection(Of PdfMarkupAnnotationDataWrapper)
Public Property AnnotationList As ObservableCollection(Of PdfMarkupAnnotationDataWrapper)
Get
Return _annotationList
End Get
Set(value As ObservableCollection(Of PdfMarkupAnnotationDataWrapper))
_annotationList = value
OnPropertyChanged()
End Set
End Property
Private _text As String
Public Property Text As String
Get
Return _text
End Get
Set(value As String)
_text = value
OnPropertyChanged()
'filter
AnnotationList = New ObservableCollection(Of PdfMarkupAnnotationDataWrapper)(_unFilteredAnnotationList.Where(Function(x)
Return x.YourStringProperty.StartsWith(_text)
End Function))
End Set
End Property

InotifyPropertyChanged : expression recursively calls the containing property

On Set Property if certain condition match then i want to change the value of Set Property.
E.g. Item Gross Wt. = 5.55 Item Sales Rate = 2.892 Sales Value = 16.05 (Sales Value should rounded to 2 decimal) But actually (Sales Value / Gross Wt) <> 2.892. It should be 2.89189189 (Rate should be round upto 8 Decimal)
So whenever i enter rate 2.892 and it will set the Rate property and will set the Sales Value Property and again from there it will set the Rate Property.
By doing above property side there is no issue but my UI is not updating with 2.89189189 in Sales Rate TextBox.
See the code what i want to do this is not related to above example but having same issue.
Imports System.ComponentModel
Public Class TextViewModel
Implements INotifyPropertyChanged
Dim _View As MainWindow
Public Sub New(ByVal View As MainWindow)
_View = View
End Sub
Private Property _MyValue As String
Public Property MyValue As String
Get
Return _MyValue
End Get
Set(value As String)
_MyValue = value
NotifyPropertyChanged("MyValue")
If value = "ABC" Then
MyValue = "PQR"
End If
End Set
End Property
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Protected Sub NotifyPropertyChanged(info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
End Class
As described if i write ABC and tab then PQR should display in same textbox.
Is it possible?
But there is no need to call the Set recursively
Extra calls to NotifyPropertyChanged is just extra traffic
Set(value As String)
If _MyValue = value then
return
End If
_MyValue = value
If _MyValue = "ABC" Then
_MyValue = "PQR"
End If
NotifyPropertyChanged("MyValue")
End Set
You are Notifying of the property change too early. You first notify that it has changed, then change it. You should change it, then notify. Hope that helps!
My comment was too long so I'm adding it here: I think I understand what you are saying; in your CODE EXAMPLE, you are recursively calling the setter from the setter. But in your WRITTEN COMMENT, you are setting the properties back and forth (likely an infinite loop). THESE ARE TWO DIFFERENT PROBLEMS. In your CODE EXAMPLE, if you move the NotifyPropertyChanged event to the very end of the setter, it will work as you want. It will NotifyPropertyChanged TWICE though AFTER everything has changed (once for the "child" setter call and once for the "parent" setter call). In your WRITTEN example, first, update _ItemRate, then set ItemValue. Within ItemValue, update _ItemValue, then update _ItemRate (NOT ItemRate), and NotifyPropertyChanged for both ItemValue AND ItemRate WITHIN THE SETTER OF ItemValue. This will get you out of your infinite loop.

How to notify property changed from delegate and update UI

I'm using caliburn micro in a wpf app in which I have a list and detail viewmodels and their views side by side. Select an item from the list on the left and see the detail on the right in the detail vm.
If the detail IsDirty (changed, not saved) and they select a new item from the list, I want to notify the user of that fact. I have that working fine. But if the user clicks "no" to stay on their dirty item, I want the list view to stay on their current item. Here's what I have so far:
ListViewModel:
Private _selectedItem As Library.VEntityStatusInfo
Public Property SelectedItem As Library.VEntityStatusInfo
Get
Return _selectedItem
End Get
Set(value As Library.VEntityStatusInfo)
Events.Publish(New SelectionChangingEvent With {.Sender = Me,
.Identification = value.Identification,
.Callback = Sub(id As Integer)
_selectedItem = (From m In Model Where m.Identification = id).FirstOrDefault
NotifyOfPropertyChange(Function() SelectedItem)
End Sub})
End Set
End Property
DetailViewModel:
Public Sub Handle(message As SelectionChangingEvent) Implements IHandle(Of SelectionChangingEvent).Handle
If TryCast(message.Sender, EntityList.EntityListViewModel) Is Nothing Then Return
If Me.Model Is Nothing OrElse Me.Model.Identification <> message.Identification Then
CanChange(message.Identification, message.Callback)
End If
End Sub
Private Sub CanChange(identification As Integer, eventCallback As System.Action(Of Integer))
If Me.Model IsNot Nothing AndAlso Me.Model.IsDirty Then
Dialogs.ShowMessageBox(
"You have unsaved data. Are you sure you want to change employee's? All changes will be lost.",
"Unsaved Changes",
MessageBoxOptions.YesNo,
Sub(box)
If box.WasSelected(MessageBoxOptions.Yes) Then
If String.IsNullOrEmpty(identification) Then
Me.Model = Nothing
Me.OnRefreshed()
Else
BeginRefresh("GetByIdentificationAsync", identification)
End If
eventCallback(identification)
Else
eventCallback(Model.Identification)
End If
End Sub)
Else
eventCallback(identification)
BeginRefresh("GetByIdentificationAsync", identification)
End If
End Sub
SelectedItem is bound to the ListBox SelectedItem and that works properly. When I put breakpoints in each step, they were all hit, including the property Get after NotifyOfPropertyChanged. But the UI fails to update.

Dependency Property Changed Event not fireing on Collection Property in design Time

I have a custom control that has a property that is an ObservableCollection of another custom control. I can't seem to get the DependencyProperty change event to fire in design time. I have tried to use CoerceValueCallback this doesn't fire either. can anyone give me some direction. Every thing else is working just fine in runtime i just can't get this to fire so i can update the control in designtime. Many thanks in advance.
Public Shared ReadOnly ArcsProperty As DependencyProperty = DependencyProperty.Register("Arcs", GetType(ObservableCollection(Of OPCWPF.OPCWPFArcControl)), GetType(OPCWPFPie), New PropertyMetadata(New ObservableCollection(Of OPCWPF.OPCWPFArcControl), New PropertyChangedCallback(AddressOf ArcsPropertyChanged), New CoerceValueCallback(AddressOf CoerceArcs)))
' Arc Quantity
<Description("Collection of Arcs"), _
Category("Common Properties")> _
Public Property Arcs() As ObservableCollection(Of OPCWPF.OPCWPFArcControl)
Get
Return DirectCast(Me.GetValue(ArcsProperty), ObservableCollection(Of OPCWPF.OPCWPFArcControl))
End Get
Set(ByVal value As ObservableCollection(Of OPCWPF.OPCWPFArcControl))
Me.SetValue(ArcsProperty, value)
End Set
End Property
Via the event you register with the dependency property metadata you are just subscribing change on the property itself, not changes to the collection (adding/removing items) that need to be subscribed by registering for the event CollectionChanged exposed by ObservableCollection<T>
The simple solution (maybe not the best) but it got me what i was looking for.
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
AddHandler Arcs.CollectionChanged, AddressOf UpdateControl
End Sub

Resources