Binding to ICommand with MVVM - wpf

I'm totally new with MVVM and its stuff.
Could you help me to correctly bind the WPF button to ICommand.
I'm binding the button:
<Button Command="{Binding OpenWindow}" >
In the ViewModel:
Public Sub New()
OpenWindow = New RelayCommand(New Action(Of Object)(AddressOf ShowWindow))
End Sub
Private Sub ShowWindow()
Dim win As New SecondWindow()
win.Show()
End Sub
And I have the class RelayCommand as:
Public Class RelayCommand
Implements ICommand
Private ReadOnly _CanExecute As Func(Of Boolean)
Private ReadOnly _Execute As Action
Public Sub New(ByVal execute As Action)
Me.New(execute, Nothing)
End Sub
Public Sub New(ByVal execute As Action, ByVal canExecute As Func(Of Boolean))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_Execute = execute
_CanExecute = canExecute
End Sub
Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If _CanExecute IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If _CanExecute IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
If _CanExecute Is Nothing Then
Return True
Else
Return _CanExecute.Invoke
End If
End Function
Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
_Execute.Invoke()
End Sub
End Class
With the above, I have an exception in the ViewModel constructor part saying "Nested sub does not have a signature that is compatible with delegate 'Delegate Sub Action()'. What am I doing wrong?

Change it to this:
OpenWindow = New RelayCommand(New Action(AddressOf ShowWindow))
The RelayCommand requires an action without parameters. Your method ShowWindow also is a method without parameters. But you declare the action with one parameter of type Object.

Related

How to bind button to method in VB.net

I am doing some research online to find out how to do this but I have fallen short so far. I think there is a knowledge gap that I have to overcome.
I have a wpf button that when clicked would execute a sub inside of the object it is bound too.
I would like to know how to get the button to execute exampleObject.displayMessage().
VB.NET
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Dim exObject As New exampleObject
Grid1.DataContext = exObject
End Sub
Public Class exampleObject
Public ReadOnly Property testMessage As String
Get
Return "this is a test"
End Get
End Property
Public Sub displayMessage()
MsgBox(testMessage)
End Sub
End Class
WPF
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid Name="Grid1">
<Button Content="Button" Name="Button1"/>
</Grid>
</Grid>
</Window>
To execute a method you need to use Commands. Refer below implementation.
<Grid>
<Grid>
<Grid Name="Grid1">
<Button Content="Button" Name="Button1" Command="{Binding TestCommand}"/>
</Grid>
</Grid>
</Grid>
Class MainWindow
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
Dim exObject As New exampleObject
Grid1.DataContext = exObject
End Sub
End Class
Public Class exampleObject
Private m_TestCommand As ICommand
Public Property TestCommand As ICommand
Get
Return m_TestCommand
End Get
Set(ByVal value As ICommand)
m_TestCommand = value
End Set
End Property
Public Sub New()
m_TestCommand = New DelegateCommand(AddressOf displayMessage, AddressOf CandisplayMessage)
End Sub
Public Sub displayMessage(ByVal param As Object)
MsgBox(testMessage)
End Sub
Private Function CandisplayMessage(ByVal param As Object) As Boolean
Return True
End Function
Public ReadOnly Property testMessage As String
Get
Return "this is a test"
End Get
End Property
End Class
Public Class DelegateCommand
Implements ICommand
Private m_canExecute As Func(Of Object, Boolean)
Private m_executeAction As Action(Of Object)
Private m_canExecuteCache As Boolean
Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs) Implements ICommand.CanExecuteChanged
Public Sub New(ByVal executeAction As Action(Of Object), ByVal canExecute As Func(Of Object, Boolean))
Me.m_executeAction = executeAction
Me.m_canExecute = canExecute
End Sub
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Dim temp As Boolean = m_canExecute(parameter)
If m_canExecuteCache <> temp Then
m_canExecuteCache = temp
RaiseEvent CanExecuteChanged(Me, New EventArgs())
End If
Return m_canExecuteCache
End Function
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
m_executeAction(parameter)
End Sub
End Class
I have got the Delegate command from http://www.paulspatterson.com/mvvm-and-wpf-for-vb-net-%E2%80%93-part-5-%E2%80%93-delegating-commands/. That site gives very detailed information.

Tracking the CurrentChanged event of an ICollectionView declared as a dependencyProperty

I have an ICollectionView declared like so;
Public Shared ReadOnly DataIcvProperty As DependencyProperty = DependencyProperty.Register("DataIcv", GetType(ICollectionView), GetType(DataNavigator), New PropertyMetadata(Nothing))
<Description("The CollectionView (as an ICollectionView) to be passed to the DataNavigator control"), Category("Navigation Data Source")>
Public Property DataIcv As ICollectionView
Get
Return GetValue(DataIcvProperty)
End Get
Set(ByVal Value As ICollectionView)
SetValue(DataIcvProperty, Value)
End Set
End Property
On which I would like to keep track of the CurrentChanged event. So in the constructor of a wpf userControl (in which this property is declared I have added the following:
AddHandler DataIcv.CurrentChanged, AddressOf OnDataICVCurrentChanged
and then added the following
Public Sub OnDataICVCurrentChanged(ByVal sender As Object, e As EventArgs) Handles internalIcv.CurrentChanged
'Do whatever needs doing when the record in the ICollectionView changes
End Sub
When I then compile the usercontrol and use it in a separate project I get a NullReferenceException on the AddHandler line in the Constructor of the control (without any inner exception details).
What do I need to do to keep track of the current Changed event of the DataIcv so that I can have elements of my user control react properly to those changes?
Thanks
EDIT
This relates to the issue I described here. Essentially I want the navigator control to reflect the fact that the end user might select rows on the grid independently of the navigator.
The problem is, when you try to subscribe to the CurrentChanged event in your constructor, the DependencyProperty DataIcvProperty has a default value Nothing (as you specified in your property registration). So the NullReferenceException.
You could solve this with a PropertyChangeCallback (see documentation).
I come from the C# world and cannot guarantee the right syntax for VB.NET, but this approach surely will work:
Public Shared ReadOnly DataIcvProperty As DependencyProperty =
DependencyProperty.Register("DataIcv",
GetType(ICollectionView), GetType(DataNavigator),
New FrameworkPropertyMetadata(
Nothing,
New PropertyChangedCallback(AddressOf OnDataIcvChanged)))
Then you need to implement the OnDataIcvChanged static method, which will be called every time the property value changes.
Private Shared Sub OnDataIcvChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If e.OldValue IsNot Nothing
RemoveHandler (e.OldValue As ICollectionView).CurrentChanged, AddressOf OnDataICVCurrentChanged
EndIf
If e.NewValue IsNot Nothing
AddHandler (e.NewValue As ICollectionView).CurrentChanged, AddressOf OnDataICVCurrentChanged
EndIf
End Sub
Update:
If your event handler method is not static, then you should access it via the object instance:
Private Shared Sub OnDataIcvChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim DataNavigator instance = d As DataNavigator
If e.OldValue IsNot Nothing
RemoveHandler (e.OldValue As ICollectionView).CurrentChanged, AddressOf instance.OnDataICVCurrentChanged
EndIf
If e.NewValue IsNot Nothing
AddHandler (e.NewValue As ICollectionView).CurrentChanged, AddressOf instance.OnDataICVCurrentChanged
EndIf
End Sub
Edit: Code That eventually worked:
Public Shared ReadOnly DataIcvProperty As DependencyProperty = DependencyProperty.Register("DataIcv", GetType(ICollectionView), GetType(DataNavigator), New FrameworkPropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnDataIcvChanged)))
<Description("The CollectionView (as an ICollectionView) to be passed to the DataNavigator control"), Category("Navigation Data Source")>
Public Property DataIcv As ICollectionView
Get
Return CType(GetValue(DataIcvProperty), ICollectionView)
End Get
Set(ByVal Value As ICollectionView)
SetValue(DataIcvProperty, Value)
End Set
End Property
Private Shared Sub OnDataIcvChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim dn As DataNavigator = CType(d, DataNavigator)
If e.OldValue IsNot Nothing Then
RemoveHandler dn.DataIcv.CurrentChanged, AddressOf dn.OnDataICVCurrentChanged
End If
If e.NewValue IsNot Nothing Then
AddHandler dn.DataIcv.CurrentChanged, AddressOf dn.OnDataICVCurrentChanged
End If
End Sub
Private Sub OnDataICVCurrentChanged(ByVal sender As Object, ByVal e As EventArgs)
Record.Text = (DataIcv.CurrentPosition + 1).ToString
End Sub

Do you need all those properties in MVVM?

I recently started using MVVM and just figured out how to work with Commands with the help of http://www.dotmaniac.net/wpf-karl-shifletts-relaycommand/ and http://www.codeproject.com/Articles/126249/MVVM-Pattern-in-WPF-A-Simple-Tutorial-for-Absolute. I succeeded to get a simple test to work.
Below here is the code I put together taken from these resources I just mentioned. So to clarify, the code I post IS WORKING. I just think it's not compact to be useful. I have an application in progress that already has all it Properties linked to TextBoxes,Labels,Buttons,DataGrids, ... but the Events if you click on a button aren't yet. Therefore the code below. The reason why I post my code is as follow:
Can the code for Private _oShowMsgBox As ICommand be shorter? From the looks of it I need 2 Subs or Functions to do the same what I could do in 1.
Private _oShowMsgBox As ICommand = New RelayCommand(New Action(Of Object)(AddressOf ShowMsgBoxSub),
New Predicate(Of Object)(Function() If(TextboxText = "", False, True)))
Is it necessary to have all those Properties in your file (properties for the Commands and binding data to the controls)? The application I'm working on has over 150 controls (TextBox,Label,Button,DataGrid) so the code is getting big pretty fast and looks like it's very inefficient.
Below the line is a snapshot of working code in my project. See it as a tiny part of what I have in total right now.
My xaml:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MVVM_Test" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MainWindow" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:ViewModel x:Key="ViewModelDataSource" d:IsDataSource="True" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModelDataSource}}">
<Button Content="{Binding TextboxText}" Command="{Binding ShowMsgBox}"/>
<TextBox Text="{Binding TextboxText, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
For the first time I also succeeded to have NO code in my code behind file.
This is the RelayCommand I came up with:
Public Class RelayCommand
Implements ICommand
#Region "Fields"
Private ReadOnly _execute As Action(Of Object)
Private ReadOnly _canExecute As Predicate(Of Object)
#End Region
#Region "Constructors"
Public Sub New(ByVal execute As Action(Of Object))
Me.New(execute, Nothing)
End Sub
Public Sub New(ByVal execute As Action(Of Object), ByVal canExecute As Predicate(Of Object))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_execute = execute
_canExecute = canExecute
End Sub
#End Region
#Region "ICommand Members"
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return If(_canExecute Is Nothing, True, _canExecute(parameter))
End Function
Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
AddHandler CommandManager.RequerySuggested, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler CommandManager.RequerySuggested, value
End RemoveHandler
RaiseEvent(ByVal sender As System.Object, ByVal e As System.EventArgs)
End RaiseEvent
End Event
Public Sub Execute(parameter As Object) Implements ICommand.Execute
_execute(parameter)
End Sub
#End Region
End Class
Then in my ViewModel I have this:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class ViewModel
Implements INotifyPropertyChanged
Private _sText As String
Public Property TextboxText As String
Get
Return _sText
End Get
Set(ByVal value As String)
_sText = value
RaisePropertyChanged()
End Set
End Property
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Protected Sub RaisePropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private _oShowMsgBox As ICommand = New RelayCommand(New Action(Of Object)(AddressOf ShowMsgBoxSub), New Predicate(Of Object)(Function() If(TextboxText = "", False, True)))
Public Property ShowMsgBox As ICommand
Get
Return _oShowMsgBox
End Get
Set(ByVal value As ICommand)
_oShowMsgBox = value
End Set
End Property
Public Sub ShowMsgBoxSub()
MessageBox.Show(TextboxText)
End Sub
End Class
For commands, which I suspect in your case are readonly, you should be able to use VB.Net's auto property feature:
Public Property ShowMsgBox _
As New RelayCommand( _
New Action(Of Object)(AddressOf ShowMsgBoxSub), _
New Predicate(Of Object)(Function() If(TextboxText = "", False, True)))
For bindable properties that change you will need to use the verbose property syntax and signal that the property has changed in the setter by firing the PropertyChanged event on your view model class that implements INotifyPropertyChanged.

ICommand CanExecuteChanged not updating

I am trying for MVVM pattern basic level and got struck at ICommand CanExecute changed. I have XAML binding as follows:
<ListBox ItemsSource="{Binding Contact.Addresses}" x:Name="AddressCollections" Height="152" SelectedValue="{Binding SelectedAddress}"
DockPanel.Dock="Top" />
<Button Content="Add" Command="{Binding AddAddressCommand}" DockPanel.Dock="Top" />
<Button Content="Remove" Command="{Binding DeleteAddressCommand}" DockPanel.Dock="Bottom" />
Commands:
Public Class DeleteCommand
Implements ICommand
Private method As Object
Private methodname As String
Public Sub New(ByVal Controlname As String, ByVal mee As Object)
methodname = Controlname
method = mee
End Sub
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Select Case methodname
Case "Address"
Return TryCast(method, ModelView.Contacts.ContactMV).CanDeleteAddress()
Case "Numbers"
Return TryCast(method, ModelView.Contacts.ContactMV).CanDeleteNumbers
Case Else : Return False
End Select
End Function
Public Event CanExecuteChanged(sender As Object, e As EventArgs) Implements ICommand.CanExecuteChanged
Public Sub Execute(parameter As Object) Implements ICommand.Execute
Select Case methodname
Case "Address"
TryCast(method, ModelView.Contacts.ContactMV).DeleteAddress()
Case "Numbers"
TryCast(method, ModelView.Contacts.ContactMV).DeleteNumbers()
Case Else
End Select
End Sub
End Class
My ModelView:
Public Class ContactMV
Property Contact As Model.Contacts.ContactMod
Property AddAddressCommand As New Commands.AddCommand("Address", Me)
Property DeleteAddressCommand As New Commands.DeleteCommand("Address", Me)
Property SelectedAddress As Model.Contacts.AddressModel
Public Sub AddAddress()
If Contact.Addresses.Count = 0 Then
Contact.Addresses.Add(New Model.Contacts.AddressModel(Contact.Primary.ID, True))
Else
Contact.Addresses.Add(New Model.Contacts.AddressModel(Contact.Primary.ID, False))
End If
End Sub
Public Sub DeleteAddress()
If IsNothing(SelectedAddress) = False Then
Try
Contact.Addresses.Remove(SelectedAddress)
Catch ex As Exception
MsgBox("Address not found")
End Try
End If
End Sub
Public Function CanDeleteAddress()
If IsNothing(SelectedAddress) Then
Return False
Else
Return Contact.Addresses.Contains(SelectedAddress)
End If
End Function
End Class
The problem is that the Canexecutechanged is firing only at start, I actually want to get the delete button enabled only when something in the listbox is selected, and I want to get it done by MVVM - ICommand binding method. Could you please explain where i went wrong or miss understood the ICommand implementation.
Thank you.
Updated Relay iCommand code I use:
Public Class RelayCommand
Implements ICommand
''' <summary>
''' A command whose sole purpose is to relay its functionality to other objects by invoking delegates. The default return value for the CanExecute method is 'true'.
''' </summary>
''' <remarks></remarks>
#Region "Declarations"
Private ReadOnly _CanExecute As Func(Of Boolean)
Private ReadOnly _Execute As Action
#End Region
#Region "Constructors"
Public Sub New(ByVal execute As Action)
Me.New(execute, Nothing)
End Sub
Public Sub New(ByVal execute As Action, ByVal canExecute As Func(Of Boolean))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_Execute = execute
_CanExecute = canExecute
End Sub
#End Region
#Region "ICommand"
Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If _CanExecute IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If _CanExecute IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
'This is the RaiseEvent block
'CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
If _CanExecute Is Nothing Then
Return True
Else
Return _CanExecute.Invoke
End If
End Function
Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
_Execute.Invoke()
End Sub
#End Region
End Class
Most of the code is a copy, but I understood the working by below comments.
As Raul OtaƱo has pointed out, you can raise the CanExecuteChanged. However, not all MVVM frameworks provide a RaiseCanExecuteChanged method. It's also worth noting that the actual event CanExecuteChanged must be called on the UI thread. So, if you're expecting a callback from some thread in your model, you need to invoke it back to the UI thread, like this:
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
Application.Current.Dispatcher.Invoke((Action)(() => { CanExecuteChanged(this, EventArgs.Empty); }));
}
}
I would very much recommend against calling CommandManager.InvalidateRequerySuggested() because although this works functionally, and is ok for small applications, it is indiscriminate and will potentially re-query every command! In a large system with lots of commands, this can be very very slow!
You must have in your ICommand implementation some method like RaiseCanExecuteChanged that fires the event CanExecuteChanged. Then every time that the selected item in the list changed, in your view model you execute the RaiseCanExecuteChanged from the command you want. Any way I suggest you to use any generic command, like the RelayCommand of GalaSoft MVVMLite library, or any implementation of DelegateCommand.
Hope this helps...

Two-way WPF binding to Properties of a class inside of an ObservableCollection

I've searched as best as I can, and I can't find an answer to this specific problem that I have... WPF binding seems to be great and all, but I end up banging my head against the wall more often than not.
Okay, I have a singleton class which is ultimately the one that I'm binding to:
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Public Class AmandaSeyfried
Implements INotifyPropertyChanged
Shared _config As New config
Public Event PropertyChanged(sender As Object, E As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private Shared _thisInstance As AmandaSeyfried
Protected Sub New()
' initialization goes here
End Sub
Public Shared Function GetSingleton() As AmandaSeyfried
' initialize object if it hasn't already been done
If _thisInstance Is Nothing Then
_thisInstance = New AmandaSeyfried
End If
' return the initialized instance
Return _thisInstance
End Function
Public Class CountryTranslation
Implements INotifyPropertyChanged
Private Property _englishCountryName As String = ""
Public Property EnglishCountryName As String
Get
Return _EnglishCountryName
End Get
Set(value As String)
If _englishCountryName <> value Then
_englishCountryName = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("EnglishCountryName"))
End If
End Set
End Property
Private Property _foreignCountryName As String = ""
Public Property ForeignCountryName As String
Get
Return _foreignCountryName
End Get
Set(value As String)
If _foreignCountryName <> value Then
_foreignCountryName = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ForeignCountryName"))
End If
End Set
End Property
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
Private WithEvents _countryTranslations As New ObservableCollection(Of CountryTranslation)
Public Property CountryTranslations As ObservableCollection(Of CountryTranslation)
Get
If _config.GetKeyTextValue("countryTranslations") <> "" Then
Dim reader As New IO.StringReader(_config.GetKeyTextValue("countryTranslations"))
Dim Serializer As New Xml.Serialization.XmlSerializer(_countryTranslations.GetType)
_countryTranslations = Serializer.Deserialize(reader)
End If
Return _countryTranslations
End Get
Set(value As ObservableCollection(Of CountryTranslation))
_countryTranslations = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CountryTranslations"))
End Set
End Property
Private Sub CountryTranslationCollectionChanged(sender As Object, e As Specialized.NotifyCollectionChangedEventArgs) Handles _countryTranslations.CollectionChanged
Dim newStringWriter As New IO.StringWriter
Dim NewSerializer As New Xml.Serialization.XmlSerializer(_countryTranslations.GetType)
NewSerializer.Serialize(newStringWriter, _countryTranslations)
_config.SaveKeyTextValue("countryTranslations", newStringWriter.ToString)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CountryTranslations"))
End Sub
End Class
_config is a misnamed helper class that stores and retrieves data from a local SqlCe instance. Essentially the object is serialized, stored in the DB, and then pulled out of the DB any times it's needed and deserialized back into an object. All in all, it seems to be working fairly well.
My problem is that although I can bind to the object, and I can monitor when a row is added in a WPF DataGrid via the CollectionChangedMethod handler, I don't get any notification when either of the two properties of CountryTranslation are changed.
The rest of my related code is... XAML... there's obviously more, but I don't believe the XAML portion of the binding is to blame, so I'll trim it to the relevant:
<toolkit:DataGrid Margin="12,12,12,12" ItemsSource="{Binding Path=KarenSmith.CountryTranslations, Mode=TwoWay}" AutoGenerateColumns="False" HorizontalAlignment="Stretch" Width="Auto" SelectionMode="Single">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn Width="283" Binding="{Binding EnglishCountryName,Mode=TwoWay}" />
<toolkit:DataGridTextColumn Width="283" Binding="{Binding ForeignCountryName,Mode=TwoWay}" />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
And the nice and simple code-behind:
Public Class Preferences
Public Property KarenSmith As AmandaSeyfried = AmandaSeyfried.GetSingleton
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
DataContext = Me
End Sub
Private Sub Close_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
Me.Close()
End Sub
End Class
If I throw some break points on the Getter and Setters of the CountryTranslation class, I can monitor when they're being changed (via the datagrid, so binding is working), but try as I might I can't figure out how to raise an event based upon that back in the main class to subsequently update the datastore to show the changes.
Usually I add a CollectionChanged event to the ObservableCollection, which attaches a PropertyChanged event to it's items when they get added, and that event listener listens for changes and would handle them as needed.
Here's an example: (hope the syntax is correct since I just ran it through a C# to VB.Net converter)
Public Sub New()
AddHandler MyCollection.CollectionChanged, AddressOf MyCollection_CollectionChanged
End Sub
Private Sub MyCollection_CollectionChanged(sender As Object, e As CollectionChangedEventArgs)
If e.NewItems IsNot Nothing Then
For Each item As MyItem In e.NewItems
AddHandler item.PropertyChanged, AddressOf MyItem_PropertyChanged
Next
End If
If e.OldItems IsNot Nothing Then
For Each item As MyItem In e.OldItems
RemoveHandler item.PropertyChanged, AddressOf MyItem_PropertyChanged
Next
End If
End Sub
Private Sub MyItem_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
If e.PropertyName = "Some Property" Then
DoWork()
End If
End Sub
The C# version looks like this:
public MyViewModel()
{
MyCollection.CollectionChanged += MyCollection_CollectionChanged;
}
void MyCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(MyItem item in e.NewItems)
item.PropertyChanged += MyItem_PropertyChanged;
if (e.OldItems != null)
foreach(MyItem item in e.OldItems)
item.PropertyChanged -= MyItem_PropertyChanged;
}
void MyItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Some Property")
DoWork();
}

Resources