StackOverflowExcpetion When Executing Command - wpf

I have created a Class which Inherits ICommand
Public Class RelayCommand(Of T)
Implements ICommand
Private ReadOnly m_oExecute As Action(Of T)
Private ReadOnly m_fCanExecute As Predicate(Of T)
Public Sub New(execute As Action(Of T))
Me.New(execute, Nothing)
End Sub
Public Sub New(execute As Action(Of T), canExecute As Predicate(Of T))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
Me.m_oExecute = execute
Me.m_fCanExecute = canExecute
End Sub
Public Function CanExecute(parameter As T) As Boolean
Return IIf(Me.m_fCanExecute Is Nothing, True, Me.CanExecute(parameter))
End Function
Public Sub Execute(parameter As T)
Me.execute(parameter)
End Sub
Private Function ICommand_CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return Me.CanExecute(CType(parameter, T))
End Function
Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
AddHandler(value As EventHandler)
AddHandler CommandManager.RequerySuggested, value
End AddHandler
RemoveHandler(value As EventHandler)
RemoveHandler CommandManager.RequerySuggested, value
End RemoveHandler
RaiseEvent(sender As Object, e As EventArgs)
End RaiseEvent
End Event
Private Sub ICommand_Execute(parameter As Object) Implements ICommand.Execute
Me.Execute(CType(parameter, T))
End Sub
End Class
Public Class RelayCommand
Inherits RelayCommand(Of Object)
Public Sub New(execute As Action(Of Object))
MyBase.New(execute)
End Sub
Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object))
MyBase.New(execute, canExecute)
End Sub
End Class
In the .xaml file I have wrote this code:
<ComboBox x:Name="cboDatabases" Width="150">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DropDownOpened">
<i:InvokeCommandAction Command="{Binding DataContext.DropDownOpenedCommand,ElementName=cboDatabases}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
And the Command Object in ViewModel
Private m_oDropDownOpenedCommand As RelayCommand
Public ReadOnly Property DropDownOpenedCommand As ICommand
Get
If IsNothing(m_oDropDownOpenedCommand) Then
Me.m_oDropDownOpenedCommand = New RelayCommand(AddressOf Me.DropDownOpened, Function(param) True)
End If
Return Me.m_oDropDownOpenedCommand
End Get
End Property
Private Sub DropDownOpened()
MessageBox.Show("i did it")
End Sub
When I create a new Instance of the Window the code inserts the Property DropDownOpenedCommand as expected. When I click the combo so as to trigger the event then
the code calls many times this Function in the RelayCommand Class
Public Function CanExecute(parameter As T) As Boolean
Return IIf(Me.m_fCanExecute Is Nothing, True, Me.CanExecute(parameter))
End Function
As a result I get a StackOverflowException at that Line. The CanExecute Returns false after the first time that it gets to that line. BUT The parameter field is always nothing. I think that the parameter being nothing is a problem but I can't figure how to pass the right argument.
Any Ideas??
I know that I can handle the event in the xaml.vb but I want it as a Command in the Viewmodel.

In addition to the answer by #Tom Studee, this method might cause you troubles too:
Public Sub Execute(parameter As T)
Me.execute(parameter)
End Sub

Public Function CanExecute(parameter As T) As Boolean
Return IIf(Me.m_fCanExecute Is Nothing, True, Me.CanExecute(parameter))
End Function
If Me.m_fCanExecute is ever not null, the function recurses on itself by evaluating Me.CanExecute() again. That's where the stackoverflowexception is being generated.
edit:
So the first time you come into this method and Me.m_fCanExecute is not null, it again executes Me.CanExecute(parameter), then Me.m_fCanExecute is null again, so it calls Me.CanExecute(), then Me.m_fCanExecute is null again, so it calls Me.CanExecute(), then Me.m_fCanExecute is null again, so it calls Me.CanExecute(), then Me.m_fCanExecute is null again, so it calls Me.CanExecute(), then Me.m_fCanExecute is null again, so it calls Me.CanExecute() ...
and then you get a StackOverFlowException. It's an infinite loop.

Related

ItemsControl bound to ObservableColellection not updating UI on property change

After a lot of headache and late hours, I've given up on trying to solve the problem to this answer myself. While there is a lot of literature for very similar issues that can be found, I haven't been able to find an exact solution to my particular problem.
I am having trouble getting my ItemsControl using a canvas as the ItemsPanel to update the UI after a property of an item within its ItemsSource has been modified.
I have created a very clean sample application to demonstrate exactly what's going on.
In my sample application, I have a view 'MainWindow.xaml', a viewmodel 'MainWindowViewModel.vb' which is inheriting 'ViewModelBase.vb', and lastly a command delegate 'DelegateCommand.vb' which is used to create RelayCommands to update the ItemSource of my ItemsControl.
First, MainWindow.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleApp"
x:Class="MainWindow" Title="MainWindow" Height="347" Width="525" Background="Black">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<!-- LINE SEGMENTS -->
<ItemsControl x:Name="ic1" ItemsSource="{Binding LineData, Mode=OneWay, NotifyOnTargetUpdated=True}" HorizontalAlignment="Left" Height="246" VerticalAlignment="Top" Width="517" Background="#FF191919" BorderBrush="#FF444444">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" Stroke="White" StrokeThickness="6"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Refresh Canvas" HorizontalAlignment="Left" Margin="350,261,0,0" VerticalAlignment="Top" Width="124" Height="40" FontFamily="Verdana" FontWeight="Bold" Click="Button_Click"/>
<Button Content="Command 1" Command="{Binding Command1}" HorizontalAlignment="Left" Margin="45,261,0,0" VerticalAlignment="Top" Width="124" Height="40" FontFamily="Verdana" FontWeight="Bold"/>
<Button Content="Command 2" Command="{Binding Command2}" HorizontalAlignment="Left" Margin="198,261,0,0" VerticalAlignment="Top" Width="124" Height="40" FontWeight="Bold" FontFamily="Verdana"/>
</Grid>
</Window>
As you can see, the DataContext of my Window is MainWindowViewModel, and the binding of the ItemSource is LineData (located within that VM).
In addition, I have three buttons. The first two buttons execute ICommands, while the third button executes a behind-code refresh of the ItemsControl (This is for debugging purposes, to prove that a bound property within the ItemSource is being updated while the UI is not). More on that later.
The first button is bound to Command1 in the VM, while the second button is bound to Command2 in the VM.
Next, MainWindowViewModel.vb:
Imports System.Collections.ObjectModel
Public Class MainWindowViewModel
Inherits ViewModelBase
' Sample line data variable
Private _LineData As ObservableCollection(Of LineStructure) = GetLineData()
Public Property LineData As ObservableCollection(Of LineStructure)
Get
Return _LineData
End Get
Set(value As ObservableCollection(Of LineStructure))
_LineData = value
OnPropertyChanged("LineData")
End Set
End Property
' ICommands
Private _Command1 As ICommand
Public ReadOnly Property Command1 As ICommand
Get
If _Command1 Is Nothing Then
_Command1 = New MVVM.RelayCommand(AddressOf ExecuteCommand1)
End If
Return _Command1
End Get
End Property
Private _Command2 As ICommand
Public ReadOnly Property Command2 As ICommand
Get
If _Command2 Is Nothing Then
_Command2 = New MVVM.RelayCommand(AddressOf ExecuteCommand2)
End If
Return _Command2
End Get
End Property
' ICommand Methods
Private Sub ExecuteCommand1()
' Re-arrange LineData(0) to make a plus sign on the canvas
' This works - Assigning a new value to an item of the collection updates the canvas
LineData(0) = New LineStructure With {.X1 = "175", .Y1 = "50", .X2 = "175", .Y2 = "150"}
End Sub
Private Sub ExecuteCommand2()
' Put LineData(0) back into its original position
' This doesn't work - Modifying the PROPERTY of an item in the collection does not update the canvas.. even with INotifyPropertyChange being called
LineData(0).X1 = "50"
LineData(0).Y1 = "50"
LineData(0).X2 = "300"
LineData(0).Y2 = "50"
OnPropertyChanged("LineData")
End Sub
' Misc methods
Private Function GetLineData() As ObservableCollection(Of LineStructure)
Dim tmpList As New ObservableCollection(Of LineStructure)
' Create two horizontal parallel lines
tmpList.Add(New LineStructure With {.X1 = "50", .Y1 = "50", .X2 = "300", .Y2 = "50"})
tmpList.Add(New LineStructure With {.X1 = "50", .Y1 = "100", .X2 = "300", .Y2 = "100"})
Return tmpList
End Function
End Class
Public Class LineStructure
Public Property X1
Public Property Y1
Public Property X2
Public Property Y2
End Class
In my viewmodel, I have defined LineData immediately (this is what my ItemsSource is bound to), so we have some data for our ItemSource ready to be displayed in the canvas upon execution. It is defined by a GetLineData() function, which simply returns a populated ObservableCollection of 2 lines.
When the application first starts, there are two horizontal, parallel lines displayed.
The LineData variable is an ObservableObject of a LineStructure class that I have defined, which simply contains X1, Y1, X2, Y2 strings for the respective objects to bind to and display within the canvas.
Command1 (again, this is bound to the first button) assigns a new LineStructure to the first index of LineData. When this is executed, everything works fantastic; the UI updates as expected and everyone is happy. This makes the lines appear as a plus sign on the canvas.
Here's where the problem begins:
Command2 is not going to assign a new LineStructure to the first LineData index like Command1 does, instead it's going to re-define the properties within the first LineData index individually. If this were to work, it would re-arrange the first line, and both lines on the canvas would be horizontally parallel again.
This however does not update the canvas/UI - and I can't figure out why. I have read numerous articles and tried many different solutions to no avail.
If anyone can explain why the binding does not update upon modifying a property rather than re-declaring the LineStructure index all together, please let me know, I would greatly appreciate it.
One final thing to note, I have managed to find a solution which will get what I need done, however I don't believe I should have to use it.. I would think the bindings should be able to take care of detecting any property changes.
For anyone interested, see the following snippet for a makeshift solution to update the canvas on a property change.
I have added NotifyOnTargetUpdated=True and TargetUpdated="RefreshCanvas" to my ItemsControl declaration in xaml.
What this does is calls a method named RefreshCanvas(), which executes ic1.Items.Refresh() from the MainWindow's code-behind (you can find the code-behind at the end of this post). This refreshes the ItemsControl items, and thus the canvas is refreshed and displays updates to the bound collection.
<ItemsControl x:Name="ic1" TargetUpdated="RefreshCanvas" ItemsSource="{Binding LineData, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, NotifyOnTargetUpdated=True}" HorizontalAlignment="Left" Height="246" VerticalAlignment="Top" Width="517" Background="#FF191919" BorderBrush="#FF444444">
I will include my other files just for reference, since it might be relevant:
ViewModelBase.vb:
Imports System.ComponentModel
Public MustInherit Class ViewModelBase
Implements INotifyPropertyChanged, IDisposable
#Region "Constructor"
Protected Sub New()
End Sub
#End Region ' Constructor
#Region "DisplayName"
' Returns the user-friendly name of this object.
' Child classes can set this property to a new value, or override it to determine the value on-demand.
Private privateDisplayName As String
Public Overridable Property DisplayName() As String
Get
Return privateDisplayName
End Get
Protected Set(ByVal value As String)
privateDisplayName = value
End Set
End Property
#End Region ' DisplayName
#Region "Debugging Aids"
' Warns the developer if this object does not have a public property with the specified name.
' This method does not exist in a Release build.
<Conditional("DEBUG"), DebuggerStepThrough()> _
Public Sub VerifyPropertyName(ByVal propertyName As String)
' Verify that the property name matches a real, public, instance property on this object.
If TypeDescriptor.GetProperties(Me)(propertyName) Is Nothing Then
Dim msg As String = "Invalid property name: " & propertyName
If Me.ThrowOnInvalidPropertyName Then
Throw New Exception(msg)
Else
Debug.Fail(msg)
End If
End If
End Sub
' Returns whether an exception is thrown, or if a Debug.Fail() is used when an invalid property name is passed to the VerifyPropertyName method.
' The default value is false, but subclasses used by unit tests might override this property's getter to return true.
Private privateThrowOnInvalidPropertyName As Boolean
Protected Overridable Property ThrowOnInvalidPropertyName() As Boolean
Get
Return privateThrowOnInvalidPropertyName
End Get
Set(ByVal value As Boolean)
privateThrowOnInvalidPropertyName = value
End Set
End Property
#End Region ' Debugging Aides
#Region "INotifyPropertyChanged Members"
' Raised when a property on this object has a new value.
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
' Raises this object's PropertyChanged event.
' <param name="propertyName">The property that has a new value.</param>
Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
Me.VerifyPropertyName(propertyName)
Dim handler As PropertyChangedEventHandler = Me.PropertyChangedEvent
If handler IsNot Nothing Then
Dim e = New PropertyChangedEventArgs(propertyName)
handler(Me, e)
End If
End Sub
#End Region ' INotifyPropertyChanged Members
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
' TODO: set large fields to null.
End If
Me.disposedValue = True
End Sub
' Invoked when this object is being removed from the application and will be subject to garbage collection.
Public Sub Dispose() Implements IDisposable.Dispose
Me.OnDispose()
End Sub
' Child classes can override this method to perform clean-up logic, such as removing event handlers.
Protected Overridable Sub OnDispose()
End Sub
' Controla el tancament del ViewModel.
' <returns></returns>
' <remarks></remarks>
Public Overridable Function CanClose() As Boolean
Return Nothing
End Function
#If DEBUG Then
' Useful for ensuring that ViewModel objects are properly garbage collected.
Protected Overrides Sub Finalize()
Dim msg As String = String.Format("{0} ({1}) ({2}) Finalized", Me.GetType().Name, Me.DisplayName, Me.GetHashCode())
System.Diagnostics.Debug.WriteLine(msg)
End Sub
#End If
#End Region
End Class
DelegateCommand.vb:
Imports System.Windows.Input
Namespace MVVM
Public NotInheritable Class RelayCommand
Implements ICommand
#Region " Declarations "
Private ReadOnly _objCanExecuteMethod As Predicate(Of Object) = Nothing
Private ReadOnly _objExecuteMethod As Action(Of Object) = Nothing
#End Region
#Region " Events "
Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If _objCanExecuteMethod IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If _objCanExecuteMethod IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
If _objCanExecuteMethod IsNot Nothing Then
CommandManager.InvalidateRequerySuggested()
End If
End RaiseEvent
End Event
#End Region
#Region " Constructor "
Public Sub New(ByVal objExecuteMethod As Action(Of Object))
Me.New(objExecuteMethod, Nothing)
End Sub
Public Sub New(ByVal objExecuteMethod As Action(Of Object), ByVal objCanExecuteMethod As Predicate(Of Object))
If objExecuteMethod Is Nothing Then
Throw New ArgumentNullException("objExecuteMethod", "Delegate comamnds can not be null")
End If
_objExecuteMethod = objExecuteMethod
_objCanExecuteMethod = objCanExecuteMethod
End Sub
#End Region
#Region " Methods "
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
If _objCanExecuteMethod Is Nothing Then
Return True
Else
Return _objCanExecuteMethod(parameter)
End If
End Function
Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
If _objExecuteMethod Is Nothing Then
Return
Else
_objExecuteMethod(parameter)
End If
End Sub
#End Region
End Class
End Namespace
Namespace MVVM
Public NotInheritable Class RelayCommand(Of T)
Implements ICommand
#Region " Declarations "
Private ReadOnly _objCanExecuteMethod As Predicate(Of T) = Nothing
Private ReadOnly _objExecuteMethod As Action(Of T) = Nothing
#End Region
#Region " Events "
Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If _objCanExecuteMethod IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If _objCanExecuteMethod IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
If _objCanExecuteMethod IsNot Nothing Then
CommandManager.InvalidateRequerySuggested()
End If
End RaiseEvent
End Event
#End Region
#Region " Constructors "
Public Sub New(ByVal objExecuteMethod As Action(Of T))
Me.New(objExecuteMethod, Nothing)
End Sub
Public Sub New(ByVal objExecuteMethod As Action(Of T), ByVal objCanExecuteMethod As Predicate(Of T))
If objExecuteMethod Is Nothing Then
Throw New ArgumentNullException("objExecuteMethod", "Delegate comamnds can not be null")
End If
_objExecuteMethod = objExecuteMethod
_objCanExecuteMethod = objCanExecuteMethod
End Sub
#End Region
#Region " Methods "
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
If _objCanExecuteMethod Is Nothing Then
Return True
Else
Return _objCanExecuteMethod(DirectCast(parameter, T))
End If
End Function
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
_objExecuteMethod(DirectCast(parameter, T))
End Sub
#End Region
End Class
End Namespace
MainWindow.xaml.vb (the code-behind of MainWindow):
Class MainWindow
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
ic1.Items.Refresh()
End Sub
Private Sub RefreshCanvas(sender As Object, e As DataTransferEventArgs)
sender.Items.Refresh()
End Sub
End Class
Thank you for any help that might be offered to point me in the right direction, and hopefully this can help someone else out as well.
***** UPDATE, ISSUE SOLVED *****
E-Bat has so kindly pointed out that the properties of the LineData structure themselves need to implement INotifyPropertyChanged. I have implemented this change and added the updated and working 'MainWindowViewModel.xaml' code below:
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Public Class MainWindowViewModel
Inherits ViewModelBase
' Sample line data variable
Private _LineData As ObservableCollection(Of LineData) = GetLineData()
Public Property LineData As ObservableCollection(Of LineData)
Get
Return _LineData
End Get
Set(value As ObservableCollection(Of LineData))
_LineData = value
OnPropertyChanged("LineData")
End Set
End Property
' ICommands
Private _Command1 As ICommand
Public ReadOnly Property Command1 As ICommand
Get
If _Command1 Is Nothing Then
_Command1 = New MVVM.RelayCommand(AddressOf ExecuteCommand1)
End If
Return _Command1
End Get
End Property
Private _Command2 As ICommand
Public ReadOnly Property Command2 As ICommand
Get
If _Command2 Is Nothing Then
_Command2 = New MVVM.RelayCommand(AddressOf ExecuteCommand2)
End If
Return _Command2
End Get
End Property
' ICommand Methods
Private Sub ExecuteCommand1()
' Re-arrange LineData(0) to make a plus sign on the canvas
' This works - Assigning a new value to an item of the collection updates the canvas
LineData(0) = New LineData With {.X1 = "175", .Y1 = "50", .X2 = "175", .Y2 = "150"}
End Sub
Private Sub ExecuteCommand2()
' Put LineData(0) back into its original position
' Now it works, it's voodoo!
LineData(0).X1 = "50"
LineData(0).Y1 = "50"
LineData(0).X2 = "300"
LineData(0).Y2 = "50"
End Sub
' Misc methods
Private Function GetLineData() As ObservableCollection(Of LineData)
Dim tmpList As New ObservableCollection(Of LineData)
' Create two horizontal parallel lines
tmpList.Add(New LineData With {.X1 = "50", .Y1 = "50", .X2 = "300", .Y2 = "50"})
tmpList.Add(New LineData With {.X1 = "50", .Y1 = "100", .X2 = "300", .Y2 = "100"})
OnPropertyChanged("LineData")
Return tmpList
End Function
End Class
Public Class LineData
Implements INotifyPropertyChanged
Private _X1 As String
Public Property X1 As String
Get
Return _X1
End Get
Set(value As String)
_X1 = value
OnPropertyChanged("X1")
End Set
End Property
Private _Y1 As String
Public Property Y1 As String
Get
Return _Y1
End Get
Set(value As String)
_Y1 = value
OnPropertyChanged("Y1")
End Set
End Property
Private _X2 As String
Public Property X2 As String
Get
Return _X2
End Get
Set(value As String)
_X2 = value
OnPropertyChanged("X2")
End Set
End Property
Private _Y2 As String
Public Property Y2 As String
Get
Return _Y2
End Get
Set(value As String)
_Y2 = value
OnPropertyChanged("Y2")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
When you replace an item from an ObservableCollection, the old reference will be removed first and then it adds the new one, so ObservableCollection will be rising its events and that is why the first commands works as magic.
Now for the second command to refresh the UI you have to make the items itself, LineStructure, implementers of INotifyPropertyChanged so any changes to its properties will be refreshed by the binding. So say goodbye to automated properties for this class.
Public Class LineStructure
Implements INotifyPropertyChanged
Private _x1 As String
Public Property X1 As String
Get
Return _x1
End Get
Set(value As String)
If _x1 = value Then Return
_x1 = value
OnPropertyChanged("X1")
End Set
End Property
End Class

Reverse Sort ObservableCollection?

Experts,
Short Version:
The way ObservableCollection sorts is the most recent at the end, and I need it to be exactly the opposite, for display in a WPF DataGrid. Right now, all is well, but new entries are added to the end, so the user can't see the new rows added.
Less Short Version:
I have a DateTime field on the Entry class if needed to sort by/on, but to be honest, if I could just add them to the top as I get them, I don't even need to sort! I just need:
*Each item added to the collection to be added to the top, and not the default bottom.*
A Little Longer Version:
I simply cannot find a way to introduce new elements to the 'top' of the collection... "Why?" Well, I am displaying rows of data in a WPF form, and I want the most current on the top, sorted by a date field in the object.
If it is the same as IList, then why is this so hard?
Too complicated? Let me simplify:
Really Long Version:
At the very start, there is a class that will make up a row in a WPF DataGrid. The class is called "Entry", and the only 2 properties that matter below are:
Class Entry
[...]
Public Property TsCreated As Nullable(Of DateTime)
Public Property EntryRaw As String
Set(value As String)
If value <> _entryRaw Then
_entryRaw = value
OnPropertychanged("EntryRaw")
End If
End Set
Get
Return _entryRaw
End Get
End Property
Private _entryRaw As String
[...]
End Class
Next is the ObservableCollection of these Entrys...
Public Class SysEvents
Inherits ObservableCollection(**Of Entry**)
Private Shared _list As New SysEvents
Public Sub New()
End Sub
End Class
ObservableCollection has a non-overridable Sub Add(), so I can't write my own Sub Add() and sort right after adding...
So in use, this WPF Window class is like this (again, making it really simple):
Class MainWindow
Private RawEvents As New toag.syslog.SysEvents
Sub New()
grdEntryRaw.ItemsSource = RawEvents ' grid that holds rows
End Sub
Sub AddRow()
Me.RawEvents.Add(sysEvent)
End Sub
End Class
As if it matters, the XAML (can I sort in XAML?):
<DataGrid x:Name="grdEntryRaw" [...]>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding EntryRaw}" Header="Entry Raw" />
</DataGrid.Columns>
</DataGrid>
OK. Nobody Made It This Far Version:
Since I can't intercept the .Add() that the Binding is doing, it seems like I can't ever get in there with some sorting algorithm...
I thought the fight was over when I figured this much out... But now it seems success has been snatched from me on the 1 yard line! Oh, Visual Studio.. .you are a cruel mistress...
TIA!!!
There's nothing special about the ObservableCollection other than it implements INotifyCollectionChanged and INotifyPropertyChanged.
I suggests that you create your own ObservableCollection with the required behavior.
Public Class ObservableStack(Of T)
Implements IEnumerable, ICollection, IList
Implements IEnumerable(Of T), ICollection(Of T), IList(Of T)
Implements INotifyCollectionChanged, INotifyPropertyChanged
Public Sub New()
Me.list = New List(Of T)
End Sub
'...
Public Sub Add(item As T) Implements ICollection(Of T).Add
'TODO: Validate.
Me.list.Insert(0, item) 'Insert at top of the list.
Me.RaisePropertyChanged("Count")
Me.RaisePropertyChanged("Item")
Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Add, item, 0)
End Sub
Private Function _Add(obj As Object) As Integer Implements IList.Add
Me.Add(TryCast(obj, T))
Return 0
End Function
'...
Private ReadOnly list As List(Of T)
End Class
Example
Public Class ObservableStack(Of T)
Implements IEnumerable, ICollection, IList
Implements IEnumerable(Of T), ICollection(Of T), IList(Of T)
Implements INotifyCollectionChanged, INotifyPropertyChanged
Public Sub New()
Me.list = New List(Of T)
End Sub
Public Event CollectionChanged As NotifyCollectionChangedEventHandler Implements INotifyCollectionChanged.CollectionChanged
Protected Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public ReadOnly Property Count() As Integer Implements ICollection.Count, ICollection(Of T).Count
Get
Return Me.list.Count
End Get
End Property
Default Public Property Item(index As Integer) As T Implements IList(Of T).Item
Get
Return Me.list.Item(index)
End Get
Set(value As T)
Me.Replace(index, value)
End Set
End Property
Private ReadOnly Property _IsFixedSize() As Boolean Implements IList.IsFixedSize
Get
Return CType(Me.list, IList).IsFixedSize
End Get
End Property
Private ReadOnly Property _IsReadOnly() As Boolean Implements IList.IsReadOnly, ICollection(Of T).IsReadOnly
Get
Return CType(Me.list, IList).IsReadOnly
End Get
End Property
Private ReadOnly Property _IsSynchronized() As Boolean Implements ICollection.IsSynchronized
Get
Return CType(Me.list, ICollection).IsSynchronized
End Get
End Property
Private Property _Item(index As Integer) As Object Implements IList.Item
Get
Return Me.Item(index)
End Get
Set(value As Object)
Me.Item(index) = DirectCast(value, T)
End Set
End Property
Private ReadOnly Property _SyncRoot() As Object Implements ICollection.SyncRoot
Get
Return CType(Me.list, ICollection).SyncRoot
End Get
End Property
Public Sub Add(item As T) Implements ICollection(Of T).Add
Me.Insert(0, item)
End Sub
Public Sub Clear() Implements IList.Clear, ICollection(Of T).Clear
If (Me.Count > 0) Then
Me.list.Clear()
Me.RaisePropertyChanged("Count")
Me.RaisePropertyChanged("Item")
Me.RaiseCollectionReset()
End If
End Sub
Public Function Contains(item As T) As Boolean Implements ICollection(Of T).Contains
Return Me.list.Contains(item)
End Function
Public Sub CopyTo(array() As T, index As Integer) Implements ICollection(Of T).CopyTo
Me.list.CopyTo(array, index)
End Sub
Public Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator
Return Me.list.GetEnumerator()
End Function
Public Function IndexOf(item As T) As Integer Implements IList(Of T).IndexOf
Return Me.list.IndexOf(item)
End Function
Public Sub Insert(index As Integer, item As T) Implements IList(Of T).Insert
'TODO: Validate item.
Me.list.Insert(index, item)
Me.RaisePropertyChanged("Count")
Me.RaisePropertyChanged("Item")
Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Add, item, index)
End Sub
Public Sub Move(ByVal oldIndex As Integer, ByVal newIndex As Integer)
Me.MoveItem(oldIndex, newIndex)
End Sub
Protected Overridable Sub MoveItem(ByVal oldIndex As Integer, ByVal newIndex As Integer)
Dim item As T = Me.Item(oldIndex)
Me.list.RemoveAt(oldIndex)
Me.list.Insert(newIndex, item)
Me.RaisePropertyChanged("Item")
Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex)
End Sub
Protected Overridable Sub OnCollectionChanged(e As NotifyCollectionChangedEventArgs)
RaiseEvent CollectionChanged(Me, e)
End Sub
Protected Overridable Sub OnPropertyChanged(e As PropertyChangedEventArgs)
RaiseEvent PropertyChanged(Me, e)
End Sub
Private Sub RaiseCollectionChanged(action As NotifyCollectionChangedAction, item As T, index As Integer)
Me.OnCollectionChanged(New NotifyCollectionChangedEventArgs(action, item, index))
End Sub
Private Sub RaiseCollectionChanged(ByVal action As NotifyCollectionChangedAction, ByVal item As Object, ByVal index As Integer, ByVal oldIndex As Integer)
Me.OnCollectionChanged(New NotifyCollectionChangedEventArgs(action, item, index, oldIndex))
End Sub
Private Sub RaiseCollectionChanged(action As NotifyCollectionChangedAction, oldItem As T, newItem As T, index As Integer)
Me.OnCollectionChanged(New NotifyCollectionChangedEventArgs(action, newItem, oldItem, index))
End Sub
Private Sub RaiseCollectionReset()
Me.OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
End Sub
Private Sub RaisePropertyChanged(propertyName As String)
Me.OnPropertyChanged(New PropertyChangedEventArgs(propertyName))
End Sub
Public Function Remove(item As T) As Boolean Implements ICollection(Of T).Remove
Dim index As Integer = Me.IndexOf(item)
If (index <> -1) Then
Me.RemoveAt(index)
Return True
End If
Return False
End Function
Public Sub RemoveAt(index As Integer) Implements IList.RemoveAt, IList(Of T).RemoveAt
Dim item As T = Me.Item(index)
Me.list.RemoveAt(index)
Me.RaisePropertyChanged("Count")
Me.RaisePropertyChanged("Item")
Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Remove, item, index)
End Sub
Public Sub Replace(index As Integer, newItem As T)
'TODO: Validate item.
Dim oldItem As T = Me.Item(index)
Me.list.Item(index) = newItem
Me.RaisePropertyChanged("Item")
Me.RaiseCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem, newItem, index)
End Sub
Private Function _Add(obj As Object) As Integer Implements IList.Add
Me.Add(DirectCast(obj, T))
Return 0
End Function
Private Function _Contains(obj As Object) As Boolean Implements IList.Contains
Return Me.Contains(DirectCast(obj, T))
End Function
Private Sub _CopyTo(array As Array, index As Integer) Implements ICollection.CopyTo
CType(Me.list, ICollection).CopyTo(array, index)
End Sub
Private Function _GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
Return Me.GetEnumerator()
End Function
Private Function _IndexOf(obj As Object) As Integer Implements IList.IndexOf
Return Me.IndexOf(DirectCast(obj, T))
End Function
Private Sub _Insert(index As Integer, obj As Object) Implements IList.Insert
Me.Insert(index, DirectCast(obj, T))
End Sub
Private Sub _Remove(obj As Object) Implements IList.Remove
Me.Remove(DirectCast(obj, T))
End Sub
Private ReadOnly list As List(Of T)
End Class
#Bjørn-Roger Kringsjå certainly pointed me in the right direction.
I stripped things down to the bare minimum.
Instead of making my own collection class, I created the collection inside the WPF Window class:
declaration at the top of the WPF MainWindow class:
Private RawEvents As ObservableCollection(Of Entry)
Then, I had to instantiate it in the mechanics of the class' instantiation, and se tthe ItemsSource for the DataGrid:
RawEvents = New ObservableCollection(Of Entry)
grdEntryRaw.ItemsSource = RawEvents ' source for main window (events)
The only thing left was to put new events into the collection (I get new events from a message queue, but it matters not:
Public Sub PeekQ(ByVal sender As System.Object, ByVal e As System.Messaging.PeekCompletedEventArgs) Handles Q.PeekCompleted
[..]
' send to main display (newest entries on top)
Me.Dispatcher.Invoke(CType(Sub() **Me.RawEvents.Insert(0, someEvent)**, Action))
'
Me.CountryLookupQ.BeginPeek()
End Sub
...and that is it! I didn't even need an additional class to hold the events... I just used the ObservableCollection created inside the WPF window. The XAML is dead simple, and the best part is that there is no sorting algorithm:
[...]
<DockPanel x:Name="EntryRawDockPanel" HorizontalAlignment="Left" LastChildFill="False" Width="517" Margin="0,26,0,41">
<DataGrid x:Name="grdEntryRaw" Grid.Column="1" Margin="0,0,10,43" AutoGenerateColumns="False" HorizontalContentAlignment="Stretch" CanUserAddRows="False" CanUserDeleteRows="True" AlternatingRowBackground="#FFDEFFE4">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding EntryRaw}" Header="Entry Raw" IsReadOnly="False"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
[...]
Honestly, that is the entire solution. The Entry() class is not special in any way.
Hope this helps someone else, and yes, I ave seen more than one way to do this, like sorting in XAML, and even instantiating a class in XAML, but this is the easiest for my way of writing.

WPF AttachedCommandsBehavior in VB.NET

Can anybody give me an example how to implement AttachedCommands?
There are some examples in C# and I think it is very similar to C# but I have problems to translate the code to VB.NET.
At the moment my try to translate a C# AttachedCommand-Class to VB.net looks like that:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Input
Imports System.Reflection
Public Class AttachedCommand
Inherits DependencyObject
#Region "CommandParameter-Class"
Private NotInheritable Class CommandParameter
Public Shared ReadOnly Property DefaultCP() As CommandParameter
Get
Return New CommandParameter()
End Get
End Property
Private _command As ICommand
Public Property Command() As ICommand
Get
Return _command
End Get
Set(ByVal value As ICommand)
_command = value
End Set
End Property
Private _eventName As String
Public Property EventName() As String
Get
Return _eventName
End Get
Set(ByVal value As String)
_eventName = value
End Set
End Property
Private _callBack As [Delegate]
Public Property Callback() As [Delegate]
Get
Return _callBack
End Get
Set(ByVal value As [Delegate])
_callBack = value
End Set
End Property
End Class
#End Region
#Region "Properties"
Private Shared _parameter As IDictionary(Of DependencyObject, CommandParameter)
Private Shared Property Parameter() As IDictionary(Of DependencyObject, CommandParameter)
Get
Return _parameter
End Get
Set(ByVal value As IDictionary(Of DependencyObject, CommandParameter))
_parameter = value
End Set
End Property
Public Property Command() As ICommand
Get
Return GetValue(CommandProperty)
End Get
Set(ByVal value As ICommand)
SetValue(CommandProperty, value)
End Set
End Property
Public Shared ReadOnly CommandProperty As DependencyProperty = _
DependencyProperty.Register("Command", _
GetType(ICommand), GetType(AttachedCommand), _
New UIPropertyMetadata(AddressOf CommandChanged))
Public Property EventName() As String
Get
Return GetValue(EventNameProperty)
End Get
Set(ByVal value As String)
SetValue(EventNameProperty, value)
End Set
End Property
Public Shared ReadOnly EventNameProperty As DependencyProperty = _
DependencyProperty.Register("EventName", _
GetType(String), GetType(AttachedCommand), _
New UIPropertyMetadata(AddressOf EventNameChanged))
#End Region
#Region "Event-Handler"
Public Shared Sub CommandChanged(ByVal dependencyObject As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
If Not Parameter.ContainsKey(dependencyObject) Then
Parameter.Add(dependencyObject, CommandParameter.DefaultCP)
End If
If (Not Parameter(dependencyObject).Callback = Nothing) AndAlso (Not args.OldValue = Nothing) Then
_RemoveEventHandler(dependencyObject)
End If
Parameter(dependencyObject).Command = CType(args.NewValue, ICommand)
_AttachEventHandler(dependencyObject)
End Sub
Public Shared Sub EventNameChanged(ByVal dependencyObject As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
If Not Parameter.ContainsKey(dependencyObject) Then
Parameter.Add(dependencyObject, CommandParameter.DefaultCP)
End If
End Sub
#End Region
#Region "Helper"
Private Shared Sub _RemoveEventHandler(ByVal dependencyObject As DependencyObject)
If dependencyObject Is Nothing Then
Throw New ArgumentNullException("DependencyObject is null.")
End If
If Not Parameter.ContainsKey(dependencyObject) Then
Exit Sub
End If
Dim param As CommandParameter = Parameter(dependencyObject)
If param.Callback Is Nothing Then
Throw New InvalidProgramException("Cannot remove Callback. Callback is null.")
End If
Dim eventInfo As EventInfo = dependencyObject.GetType().GetEvent(param.EventName)
If eventInfo Is Nothing Then
Throw New InvalidProgramException(String.Format("Cannot find an event with the name <{0}>", param.EventName))
End If
eventInfo.RemoveEventHandler(dependencyObject, param.Callback)
End Sub
Private Shared Sub _AttachEventHandler(ByVal dependencyObject)
If dependencyObject Is Nothing Then
Throw New ArgumentNullException("DependencyObject is null")
End If
If Not Parameter.ContainsKey(dependencyObject) Then
Exit Sub
End If
Dim param As CommandParameter = Parameter(dependencyObject)
If param.Command Is Nothing Or String.IsNullOrEmpty(param.EventName) Then
Exit Sub
End If
Dim eventInfo As EventInfo = dependencyObject.GetType.GetEvent(param.EventName)
If eventInfo Is Nothing Then
Throw New InvalidProgramException(String.Format("Cannot find an event with the name <{0}>", param.EventName))
End If
eventInfo.AddEventHandler(dependencyObject, param.Callback)
End Sub
Private Shared Sub _CommandExecutAction(ByVal parameter As CommandParameter)
parameter.Command.Execute(Nothing)
End Sub
Private Shared Function _CreateHandler(ByVal eventInfo As EventInfo, ByVal method As Action) As [Delegate]
If eventInfo Is Nothing Then
Throw New ArgumentNullException("EventInfo is null")
End If
If method Is Nothing Then
Throw New ArgumentNullException("Action-method is null")
End If
Dim handlerType = eventInfo.EventHandlerType
Dim eventParams = handlerType.GetMethod("Invoke").GetParameters()
Dim parameters = eventParams.[Select](Function(p) System.Linq.Expressions.Expression.Parameter(p.ParameterType, "x"))
Dim body = System.Linq.Expressions.Expression.[Call](System.Linq.Expressions.Expression.Constant(method), method.[GetType]().GetMethod("Invoke"))
Dim lambda = System.Linq.Expressions.Expression.Lambda(body, parameters.ToArray())
Return [Delegate].CreateDelegate(handlerType, lambda.Compile(), "Invoke", False)
End Function
#End Region
#Region "INIT"
Public Sub New()
Parameter = New Dictionary(Of DependencyObject, CommandParameter)()
End Sub
#End Region
End Class
But now I have the problem, that the GetValue() and SetValue()-methods are not available. Any ideas?
Thank you for your help..
An AttachedCommandBehaviour ain't much more than an dependency property of type ICommand, which hookes some events.
Check out Dr. WPFs VB-snippets that includes snippets for dependency properties.
If you use the one that includes a property changed handler, you can use this handler to hook the events you want. In the event handler, you call the attached command.
If you still can't make it work, please post some code.

Binding TextBox to a value that is updated from a different Thread

I tried to increment a value as long as I press a button. The incremented value is connected via binding to a TextBox. The problem occures when I update the underlaying integer. I get an exception that another Thread owns it.
<Button Name="Up"
Content="Up"
PreviewMouseLeftButtonDown="Up_PreviewMouseLeftButtonDown"
PreviewMouseLeftButtonUp="Up_PreviewMouseLeftButtonUp">
</Button>
<TextBox Text="{Binding NumericField}">
During Initialisation:
Timer = New Timers.Timer
Timer.Interval = 100
AddHandler Timer.Elapsed, AddressOf Timer_Elapsed
Code behind:
Private _numericField As Integer
Public Property NumericField As Integer
Get
Return _numericField
End Get
Set(ByVal value As Integer)
_numericField = value
RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("NumericField"))
End Set
End Property
Private Sub Timer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs)
NumericField += 1
End Sub
Private Sub Up_PreviewMouseLeftButtonDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
Timer.Start()
End Sub
Private Sub Up_PreviewMouseLeftButtonUp(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
Timer.Stop()
End Sub
This looks a bit too complicated. Can't you use a RepeatButton?
Simple solution:
<RepeatButton Click="RepeatButton_Click" Content="Up" />
Code behind:
Private Sub RepeatButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
NumericField += 1
End Sub
Using commands
Try to avoid unnecessary code in your code behind file. Here's a sketch using Commands
XAML:
<RepeatButton Command="{Binding IncrementField}" Content="Up"></RepeatButton>
Helping class. Re-usable for all commands inside your project:
Public Class ActionCommand
Implements ICommand
Private ReadOnly _executeHandler As Action(Of Object)
Private ReadOnly _canExecuteHandler As Func(Of Object, Boolean)
Public Sub New(ByVal execute As Action(Of Object),
ByVal canExecute As Func(Of Object, Boolean))
If execute Is Nothing Then
Throw New ArgumentNullException("Execute cannot be null")
End If
_executeHandler = execute
_canExecuteHandler = canExecute
End Sub
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 Object, ByVal e As System.EventArgs)
CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
_executeHandler(parameter)
End Sub
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
If (_canExecuteHandler Is Nothing) Then
Return True
End If
Return _canExecuteHandler(parameter)
End Function
End Class
And in your model:
_incrementField = New ActionCommand(AddressOf IncrementExecuted, AddressOf IncrementCanExecute)
...
Private Function IncrementCanExecute(ByVal parameter As Object) As Boolean
Return True
End Function
Private Sub IncrementExecuted(ByVal parameter As Object)
NumericField += 1
End Sub
Private _incrementField As ActionCommand
Public ReadOnly Property IncrementField As ActionCommand
Get
Return _incrementField
End Get
End Property

WPF: What do I need to implement to make an extended IList( of T) bindable?

I'm extending IList so I can track the changes made to the list (updates, inserts and deletes).
Everything is ok, but I can not bind any ItemsControl to it.
Public Class TrackedList(Of T)
Implements IList(Of T), INotifyCollectionChanged
Public Sub New(Lista As IList(Of T))
m_inner = Lista
RaiseEvent CollectionChanged(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
End Sub
Private m_inner As IList(Of T)
Public ReadOnly Property Inner() As IList(Of T)
Get
If m_inner Is Nothing Then
m_inner = New List(Of T)()
End If
Return m_inner
End Get
End Property
Private m_Updates As IList(Of T)
Public ReadOnly Property Updates() As IList(Of T)
Get
If m_Updates Is Nothing Then
m_Updates = New List(Of T)()
End If
Return m_Updates
End Get
End Property
Private m_Inserts As IList(Of T)
Public ReadOnly Property Inserts() As IList(Of T)
Get
If m_Inserts Is Nothing Then
m_Inserts = New List(Of T)()
End If
Return m_Inserts
End Get
End Property
Private m_Deletes As IList(Of T)
Public ReadOnly Property Deletes() As IList(Of T)
Get
If m_Deletes Is Nothing Then
m_Deletes = New List(Of T)()
End If
Return m_Deletes
End Get
End Property
'Propietary methods'
Public Sub ItemUpdated(ByVal item As T)
If Not Updates.Contains(item) Then Updates.Add(item)
RaiseEvent CollectionChanged(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace))
End Sub
Public Sub FlushChanges()
m_Updates = Nothing
m_Inserts = Nothing
m_Updates = Nothing
End Sub
Public Sub Add(ByVal item As T) Implements System.Collections.Generic.ICollection(Of T).Add
Inner.Add(item)
Inserts.Add(item)
RaiseEvent CollectionChanged(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add))
End Sub
Public Sub Clear() Implements System.Collections.Generic.ICollection(Of T).Clear
If m_inner IsNot Nothing Then
Inner.Clear()
End If
End Sub
Public Function Contains(ByVal item As T) As Boolean Implements System.Collections.Generic.ICollection(Of T).Contains
Return Inner.Contains(item)
End Function
Public Sub CopyTo(ByVal array() As T, ByVal arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of T).CopyTo
Inner.CopyTo(array, arrayIndex)
End Sub
Public ReadOnly Property Count() As Integer Implements System.Collections.Generic.ICollection(Of T).Count
Get
Return Inner.Count
End Get
End Property
Public ReadOnly Property IsReadOnly() As Boolean Implements System.Collections.Generic.ICollection(Of T).IsReadOnly
Get
Return Inner.IsReadOnly
End Get
End Property
Public Function Remove(ByVal item As T) As Boolean Implements System.Collections.Generic.ICollection(Of T).Remove
RaiseEvent CollectionChanged(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove))
Deletes.Add(item)
Return Inner.Remove(item)
End Function
Public Function IndexOf(ByVal item As T) As Integer Implements System.Collections.Generic.IList(Of T).IndexOf
Return Inner.IndexOf(item)
End Function
Public Sub Insert(ByVal index As Integer, ByVal item As T) Implements System.Collections.Generic.IList(Of T).Insert
Inserts.Add(item)
Inner.Insert(index, item)
RaiseEvent CollectionChanged(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add))
End Sub
Default Public Property Item(ByVal index As Integer) As T Implements System.Collections.Generic.IList(Of T).Item
Get
Return Inner(index)
End Get
Set(ByVal value As T)
Inner(index) = value
End Set
End Property
Public Sub RemoveAt(ByVal index As Integer) Implements System.Collections.Generic.IList(Of T).RemoveAt
Deletes.Add(Inner(index))
Inner.RemoveAt(index)
RaiseEvent CollectionChanged(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove))
End Sub
Public Function GetEnumerator1() As System.Collections.Generic.IEnumerator(Of T) Implements System.Collections.Generic.IEnumerable(Of T).GetEnumerator
Return Inner.GetEnumerator()
End Function
Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return DirectCast(Inner, IEnumerable).GetEnumerator()
End Function
Public Event CollectionChanged(sender As Object, e As System.Collections.Specialized.NotifyCollectionChangedEventArgs) Implements System.Collections.Specialized.INotifyCollectionChanged.CollectionChanged
End Class
What do I need to do the make the list Bindable?
If you can't just inherit from ObservableCollection, you probably really want to implement INotifyCollectionChanged.
The trouble wasn't in the class, it does not even need to implement INotifyCollectionChanged
The problem was in a intermediate control who has a DependencyProperty for bridging the binding
Public Shared ReadOnly ItemsProperty As DependencyProperty = _
DependencyProperty.Register("Items", GetType(IList), GetType(MainWindowViewModel))
Public Property Items As IList
Set(ByVal value As IList)
SetValue(ItemsProperty, value)
End Set
Get
Return DirectCast(GetValue(ItemsProperty), IList)
End Get
End Property
Turns out that changing the dependency property to IEnumerable solved the problem.

Resources