How to notify property changed from delegate and update UI - wpf

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.

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.

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.

Model - ViewModel sync and filtered ObservableCollections

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.

Mvvm - Cancel changes in Wpf Listbox, vb.net

I have a wpv/mvvm-light/vb.net application with a master/detail view. In this view there is a listbox of clients and a detail view of the client's details where they user can view and edit the customers.
I wanted to add a function where users would be prompted to save changes when a new client is selected in the listbox. If the user chooses yes from the messagebox then save changes and if no then discard changes and return previous selected item back to its original value. I have this all working fine.
My problem is that when the user selects a new client and the messagebox asks them to save changes, the listbox goes out of sync. Meaning that the listbox shows the new client selected but the detail view still shows the previous client. The odd thing is that it works properly on rare occasions.
The following is my view:
<UserControl x:Class="FTC.View.ClientListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FTC_Application"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="900">
<ListBox
Grid.Column="1"
Width="350"
Style="{DynamicResource FTC_ListBox}"
ItemTemplate="{DynamicResource FTC_ClientListTemplate}"
ItemContainerStyle="{DynamicResource FTC_ListItem}"
ItemsSource="{Binding ClientViewSource.View}"
SelectedItem="{Binding Path=Selection, Mode=TwoWay}"
/>
<ContentControl DataContext="{Binding Path=Selection, Mode=TwoWay}" >
<!--all the display stuff goes here for the detail view-->
</ContentControl>
</UserControl>
the following is the property in the viewmodel that the selecteditem of the listbox is bound to. It is also the binding for the content control that displays the details.
Public Property Selection As client
Get
Return Me._Selection
End Get
Set(ByVal value As client)
''capture current value of selection
_PreviousClient = _Selection
''If they are the same,
If value Is _PreviousClient Then
Return
End If
' Note that we actually change the value for now.This is necessary because WPF seems to query the
' value after the change. The list box likes to know that the value did change.
If Me._Selection.HasChanges = True And _Selection.HasErrors = False Then
'If HasChangesPrompt(value) = True Then
' ''user rejects saving changes, exit property
' Return
'End If
If FTCMessageBox.Show("Do you want to save your changes", "Unsaved Changes", MessageBoxButton.YesNo, MessageBoxImage.Warning) = MessageBoxResult.No Then
''SELECTION IS CANCELLED
' change the value back, but do so after the UI has finished it's current context operation.
Application.Current.Dispatcher.BeginInvoke(New Action(Sub()
'' revert the current selected item to its original values and reset its HasCHanges tracking
objHelper.CopyProperties(_OriginalClient, _Selection)
_Selection.HasChanges = False
RaisePropertyChanged(ClientSelectedPropertyName)
''continue with listbox selection changing to the new value for selection
_ClientCollectionViewSource.View.MoveCurrentTo(value)
End Sub), DispatcherPriority.Normal, Nothing)
Return
Else
''save changes to database
SaveExecute()
End If
End If
_Selection = value
_Selection.HasChanges = False
RaisePropertyChanged(ClientSelectedPropertyName)
''clone the unchanged version of the current selected client on na original variable
objHelper.CopyProperties(_Selection, _OriginalClient)
End Set
End Property
SO the idea is that if the user does not want to save changes, an original value of the client is copied (using reflection) over the current value, then the ui is updated and the selection continues on to the new value chosen by the user. However, like I said above, the listbox does not reflect this change even though I tired to hard code it with the following line:
''continue with listbox selection changing to the new value for selection
_ClientCollectionViewSource.View.MoveCurrentTo(value)
I got this solution by working customizing the solution posted HERE
can anyone help me figure out why my listbox goes out of sync when this happens.
Thanks in advance
First:
I can't find the real Problem in your solution, but you have definitly - and I repeat - definitly too much code and logic in your Property Setter. Try move it to other methods and validate your implementation of those many ´if else´ blocks.
Second:
The Setter gets only fired when you select a new Item in your Listbox, but you Raise a Property changes for ´ClientSelectedPropertyName´ and not for ´Selection´ as its supposed to be. Move the property changed alsways to the end of your setter.
Try this. I hope it helps :)
So I have a working example that I think follows the MVVM-Light standard. There is a lot going on so I will try to keep it short and precise.
I ended up using EventToCommand bound to the SelectionChanged event with a ListView(instead of listbox). The EventToCommand required new namespace references as is shown below. I then bound the EventToCommand to a RelayCommand in the view model which in turn calls a private sub that handles the client validation and saves/cancels/ and updates the listview seleceditem as required.
For further information, I have a navigation service that is used to navigat between views in my wpf application. I used the MVVM-Light messanger to send a navigationstarting message that is "recieved" by this view model. Then the same client validation functions are performed and naviagtion is canceled/allowed based on user response to the dialog message thrown. I will no include all of hte navigation code unless requested. The following is the code needed to solve my original question.
<UserControl x:Class="FTC.View.ClientListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FTC_Application"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="900">
<ListView
Grid.Column="1"
Width="350"
Style="{DynamicResource FTC_ListView}"
ItemTemplate="{DynamicResource FTC_ClientListTemplate}"
ItemContainerStyle="{DynamicResource FTC_ListViewItem}"
ItemsSource="{Binding ClientViewSource.View}"
SelectedItem="{Binding Path=Selection, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
<ContentControl DataContext="{Binding Path=Selection, Mode=TwoWay}" >
<!-- Display stuff and bound controls go here -->
</ContentControl>
</Grid>
</UserControl>
Then the following is the relevant code (I removed as much code as possible to keep it clear) in my view model:
Imports System.Data
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Imports System.Windows.Threading
Imports GalaSoft.MvvmLight
Imports GalaSoft.MvvmLight.Command
Imports GalaSoft.MvvmLight.Messaging
Imports FTCModel
Imports FTC_Application.FTC.Model
Imports FTC_Application.FTC.View
Imports FTC_Application.FTC.ViewModel
Imports FTC_Application.FTC.MessageBox
Imports FTC_Application.FTC.Helpers
Imports FTC_Application.FTC.MessengerHelper
Namespace FTC.ViewModel
Public Class ClientListViewModel
Inherits ViewModelBase
Implements IDataErrorInfo
#Region "DECLARATIONS"
Public Const ClientCollectionPropertyName As String = "ClientCollection"
Public Const ClientSelectedPropertyName As String = "Selection"
Public Const ClientDetailCollectionPropertyName As String = "ClientDetailCollection"
Public Const ClientPropertyName As String = "Client"
''gets the data from LINQ to ENT Model
Private _Clients As New ObservableCollection(Of client)
''creats holder for the selected item two way binding
Private _Selection As New client
''the following is used to track changes for unding and canceling selection changed
Private _PreviousClient As New client
Private _PreviousOriginalClient As New client
Private _OriginalClient As New client
''Recieves observable collection and provicdes sorting and filtering function
Private _ClientCollectionViewSource As New CollectionViewSource
''RELAY COMMANDS declarations
Private _SaveCommand As RelayCommand
Private _SelectedItemChangedCommand As RelayCommand
''gets the VML for getting the data service
Private vml As ViewModelLocator = TryCast(Application.Current.Resources("Locator"), ViewModelLocator)
''this is a holder for the client data service
Private _ClientAccess As IClientDataService = vml.Client_Service
'' has functions using reflection for copying objects
Dim objHelper As New ObjectHelper
''tracks if client validation is coming from navigation or listview selecteditemchanged
Private bNavigatingFlag As Boolean = False
#End Region
#Region "PROPERTIES"
Public ReadOnly Property ClientViewSource As CollectionViewSource
Get
Return Me._ClientCollectionViewSource
End Get
End Property
Private Property Clients As ObservableCollection(Of client)
Get
Return Me._Clients
End Get
Set(ByVal value As ObservableCollection(Of client))
Me._Clients = value
_Clients = value
RaisePropertyChanged(ClientCollectionPropertyName)
End Set
End Property
Public Property Selection As client
Get
Return Me._Selection
End Get
Set(ByVal value As client)
''capture current value of selection
_PreviousClient = _Selection
objHelper.CopyProperties(_OriginalClient, _PreviousOriginalClient)
''If they are the same,
If value Is _PreviousClient Then
Return
End If
_Selection = value
_Selection.HasChanges = False
RaisePropertyChanged(ClientSelectedPropertyName)
''clone the unchanged version of the current selected client on na original variable
objHelper.CopyProperties(_Selection, _OriginalClient)
End Set
End Property
#End Region
#Region "COMMANDS"
Public ReadOnly Property SelectedItemChangedCommand() As RelayCommand
Get
If _SelectedItemChangedCommand Is Nothing Then
_SelectedItemChangedCommand = New RelayCommand(AddressOf SelectionChangedValidate)
End If
Return _SelectedItemChangedCommand
End Get
End Property
#End Region
#Region "METHODS"
Private Sub SelectionChangedValidate()
''Uses falg to tell if validation request triggered by navigation event or listview selecteditemchanged event
''use previous client for listview event and current client for navigating event
Dim _ClientToValidate As client
If bNavigatingFlag = True Then
_ClientToValidate = _Selection
Else
_ClientToValidate = _PreviousClient
End If
If _ClientToValidate.HasChanges = True And _ClientToValidate.HasErrors = False Then
Dim message = New DialogMessage(_ClientToValidate.chrCompany.ToString + " has been changed." + vbCrLf + "Do you want to save your changes?", AddressOf SavePreviousResponse) With { _
.Button = MessageBoxButton.YesNo, _
.Caption = "Unsaved Changes" _
}
Messenger.[Default].Send(message)
Exit Sub
End If
If _ClientToValidate.HasErrors = True Then
Dim message = New DialogMessage(_ClientToValidate.chrCompany.ToString + " has errors." + vbCrLf + "You must correct these errors before you can continue.", AddressOf HasErrorsResponse) With { _
.Button = MessageBoxButton.OK, _
.Caption = "Validation Error" _
}
Messenger.[Default].Send(message)
Exit Sub
End If
''reset the navigation flag
bNavigatingFlag = False
End Sub
Private Sub SavePreviousResponse(result As MessageBoxResult)
If result = MessageBoxResult.No Then
objHelper.CopyProperties(_PreviousOriginalClient, _PreviousClient)
_PreviousClient.HasChanges = False
Else
''user wants to save changes, save changes to database
SaveExecute()
End If
End Sub
Private Sub HasErrorsResponse(result As MessageBoxResult)
Selection = _PreviousClient
''_ClientCollectionViewSource.View.MoveCurrentTo(_PreviousClient)
End Sub
Private Function HasChangesPrompt(value As client) As Boolean
If FTCMessageBox.Show("Do you want to save your changes", "Unsaved Changes", MessageBoxButton.YesNo, MessageBoxImage.Warning) = MessageBoxResult.No Then
'' change the selected client back to its original value, but do so after the UI has finished its current context operation.
Application.Current.Dispatcher.BeginInvoke(New Action(Sub()
'' revert the current selected item to its original values and reset its HasCHanges tracking
objHelper.CopyProperties(_OriginalClient, _Selection)
_Selection.HasChanges = False
RaisePropertyChanged(ClientSelectedPropertyName)
''continue with listbox selection changing to the new value for selection
_ClientCollectionViewSource.View.MoveCurrentTo(value)
End Sub), DispatcherPriority.Normal, Nothing)
Return True
Else
''user wants to save changes, save changes to database
Return False
SaveExecute()
End If
End Function
#End Region
Public Sub New()
Clients = _ClientAccess.GetClient_All
''Sets the observable collection as the source of the CollectionViewSource
_ClientCollectionViewSource.Source = Clients
If Selection.idClient = 0 Then
Selection = Clients.Item(0)
End If
''register for messages
Messenger.[Default].Register(Of String)(Me, AddressOf HandleMessage)
End Sub
End Class
End Namespace
INXS, you will notice that the selection property setter has way less code/logic. Also, I think each part of the view model is testable and there is no direct coupling between my view and viewmodel. But this is my first WPF/MVVM application so I sitll don't fully grasp all the concepts.
I hope that this can help someone as it took my quite a while to figure it out.

Dillema with newing an item in WPF MVVM

I have built a form that displays the properties of items contained in an observable collection. This observable collection is in a ViewModel to which the datacontext of the View is bound to.
When the New button on the form gets clicked, a new Item gets added to the observable collection, and with the help of the collectionview is set to the current item. The same form is used. This time, because of the new item, the fields are empty.
Now, I want there to be only one new item in the observable collection. Because of this, I need to somehow distinguish between an Item and a New Item.
So, before the New requirement I had the following Observable Collection in my ViewModel:
public ObservableCollection<ItemViewModel> ItemTypes { get; private set; }
But now I need to be able to distinguish between a New Item and an old item. I've been trying to do a couple of things like building a base class, of which I derive Item and New Item, but this seems really wastfull since I can't think of anything to put in the derived classes, they are really only there to help to see if there's one Empty Item in the observable.
What can I do?
Edit
private void CreateNewItemType()
{
if (DoesNewItemTypeAlreadyExist())
{
return;
}
Model.ItemType itemType = new Model.ItemType();
ItemTypeViewModel itemTypeViewModel = new ItemTypeViewModel(itemType)
{
IsNew = true
};
ItemTypes.Add(itemTypeViewModel);
itemTypesCollectionView.MoveCurrentTo(itemTypeViewModel);
}
private bool DoesNewItemTypeAlreadyExist()
{
return ItemTypes.Any(a => a.IsNew == true);
}
Create a bool property on your item (IsDirty for example). When new, IsDirty is False. The first time something is changed, set it to true. Then you can have a check on the command property for your Add button that creates the new item, to check and see if there are any IsDirty = False items in the collection. If so, then set that item to selected. If not, then add a new item and select it.
In the past what I have done to do change detection is create a dictionary of String, String. The dictionary contains the name of the property and the original value. In the Changing event handler for the property in my model class, I add an add or remove call that checks to see if an item for that property is there, if so, check current value with changing value. Call OnPropertyChanged("IsDirty") as appropriate.
Is dirty checks to see if there are any items in the dictionary. The beauty is that if you change the values back to their original values, the record will regain it's IsDirty=False status.
Example of implementation
Private Sub OnAddress1Changing(ByVal value As String)
If Not m_dirtyFields.ContainsKey("Address1") Then
AddDirtyField("Address1", Address1)
Else
If m_dirtyFields("Address1") = value Then RemoveDirtyField("Address1")
End If
End Sub
Public m_dirtyFields As New Dictionary(Of String, String)
Private Sub AddDirtyField(ByVal ColName As String, ByVal OrigValue As String)
If Not m_dirtyFields.ContainsKey(ColName) Then
m_dirtyFields.Add(ColName, OrigValue)
OnPropertyChanged("IsDirty")
End If
End Sub
Private Sub RemoveDirtyField(ByVal ColName As String)
If m_dirtyFields.ContainsKey(ColName) Then
m_dirtyFields.Remove(ColName)
End If
OnPropertyChanged("IsDirty")
End Sub
Public ReadOnly Property IsDirty() As Boolean
Get
If m_dirtyFields.Count > 0 Then 'There are items so record is dirty
Return True
Else
Return False
End If
End Get
End Property

Resources