Create table dynamically in WPF (VB.Net) - wpf

I want to create an editable table and it size is defined by user (size can be 10*10 or 20*20 or 30*30).
--
I found this topic (here) but it's running in WinForms, and the DataGridView is not supported by WPF.
I tried with a DataGrid, but the following row doesn't working :
Me.DataGridTableau.ItemsSource = dt
--
I tried with a RadGridView (Telerik) but rows are only updatable by ItemsSource property, and like I don't know how many columns will be, I can't create an object which represents the table (x properties for x columns).
Can anybody help me?

You can set the ItemsSource of a DataGrid to any IEnumerable, including a DataView of a DataTable:
Me.DataGridTableau.ItemsSource = dt.DefaultView

If anybody need it, I found a solution using a RadGridView (Telerik) :
Create this class :
Imports System.Dynamic
Imports System.Collections.Generic
Imports System.ComponentModel
Public Class MyDataRow
Inherits DynamicObject
Implements INotifyPropertyChanged
ReadOnly data As IDictionary(Of String, Object)
Public Sub New()
data = New Dictionary(Of String, Object)()
End Sub
Public Sub New(ByVal source As IDictionary(Of String, Object))
data = source
End Sub
Public Overrides Function GetDynamicMemberNames() As IEnumerable(Of String)
Return data.Keys
End Function
Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder, ByRef result As Object) As Boolean
result = Me(binder.Name)
Return True
End Function
Public Overrides Function TrySetMember(ByVal binder As SetMemberBinder, ByVal value As Object) As Boolean
Me(binder.Name) = value
Return True
End Function
Default Public Property Item(ByVal columnName As String) As Object
Get
If data.ContainsKey(columnName) Then
Return data(columnName)
End If
Return Nothing
End Get
Set(ByVal value As Object)
If Not data.ContainsKey(columnName) Then
data.Add(columnName, value)
OnPropertyChanged(columnName)
Else
If data(columnName) <> value Then
data(columnName) = value
OnPropertyChanged(columnName)
End If
End If
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(name As String)
Try
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
Catch
If Debugger.IsAttached Then Debugger.Break()
Throw ' rethrow exception
End Try
End Sub
Protected Sub OnPropertyChanged(event_args As PropertyChangedEventArgs)
RaiseEvent PropertyChanged(Me, event_args)
End Sub
End Class
In your VM, you need a public property :
Private _tableau As ObservableCollection(Of MyDataRow)
Public Property Tableau() As ObservableCollection(Of MyDataRow)
Get
Return _tableau
End Get
Set(ByVal value As ObservableCollection(Of MyDataRow))
_tableau = value
OnPropertyChanged("Tableau")
End Set
End Property
You need to create a method to load your table :
Private Function LoadTableau() As ObservableCollection(Of MyDataRow)
Dim taille As Integer = Me.GetTailleTableau()
If taille = 0 Then Return Nothing
Dim data As New ObservableCollection(Of MyDataRow)()
For i As Integer = 0 To (taille - 1)
Dim row = New MyDataRow()
For j As Integer = 0 To (taille - 1)
'row(String.Format("Column{0}", j)) = String.Format("Cell {0} {1}", i, j)
row(j) = ""
Next
data.Add(row)
Next
Return data
End Function
You need to load your table :
Me.Tableau = Me.LoadTableau()
And you need to bind your table :
<telerik:RadGridView x:Name="RadGridViewTableau" ItemsSource="{Binding Tableau}" >
I hope this help :)

Related

VBNet: List of items inside a class, I cant change the value for a single item

My Problem:
I have a Class and a list of other class inside:
Public Class Signal_Type_Read
Private c_signal_count As Integer = 0 ' counter for read signals
Private _items As List(Of Signal_Item)
Private item As New Signal_Item
Sub add_sig()
c_signal_count += 1
items.Add(item)
End Sub
Public Property items() As List(Of Signal_Item)
Get
Return _items
End Get
Set(value As List(Of Signal_Item))
_items = value
End Set
End Property
Function item_counter() As Integer
item_counter = c_signal_count
End Function
Public Sub New()
_items = New List(Of Signal_Item)
End Sub
End Class
Public Class Signal_Item
Private _original_name As String
Public Property Original_name() As String
Get
Return _original_name
End Get
Set(value As String)
_original_name = value
End Set
End Property
'Many other properties
End Class
My problem is when I use in a loop
Public Shared ReadSignals As New Signal_Type_Read
//Part of a Loop to read cells values and store in the variable
ReadSignals.add_sig()
Dim c_index As Integer = ReadSignals.item_counter - 1
ReadSignals.items.item(c_index).Original_name = c_row.Cells(e_Signame).Value
It always changes the "Original_name" Property in all items of my Variable. Where is my error? I want only that oe item is changed.
I found the cause of the problem... I need to create a new instane of item in my ADD_sig() sub
Sub add_sig()
Dim s_item As New Signal_Item
c_signal_count += 1
items.Add(s_item)
End Sub

Error while checking row, after Filtering observational collection

I'm having trouble with editing an observable collection... I bind columns in codebehind like so...
Dim dgCheckBoxColumn As New DataGridCheckBoxColumn()
Dim column_username As New DataGridTextColumn()
Dim textColumn2 As New DataGridTextColumn()
Dim textColumn3 As New DataGridTextColumn()
dgCheckBoxColumn.Header = "Selected"
dgCheckBoxColumn.Binding = New Binding("Selected")
dgvResults.Columns.Add(dgCheckBoxColumn)
column_username.Header = "User Name"
column_username.Binding = New Binding("accountName")
dgvResults.Columns.Add(column_username)
textColumn2.Header = "First Name"
textColumn2.Binding = New System.Windows.Data.Binding("firstName")
dgvResults.Columns.Add(textColumn2)
textColumn3.Header = "Last Name"
textColumn3.Binding = New System.Windows.Data.Binding("lastName")
dgvResults.Columns.Add(textColumn3)
Then I create my observablecollection...
Dim oc_userlist As New ObservableCollection(Of user)
Imports System.ComponentModel
Public Class user
Implements INotifyPropertyChanged
Private m_accountname As String
Private m_firstname As String
Private m_lastname As String
Private _Selected As Boolean
Public Property Selected() As Boolean
Get
Return _Selected
End Get
Set(value As Boolean)
_Selected = value
NotifyPropertyChanged("IsChecked")
End Set
End Property
Public Property accountName() As String
Get
Return m_accountname
End Get
Set(value As String)
m_accountname = value
End Set
End Property
Public Property firstName() As String
Get
Return m_firstname
End Get
Set(value As String)
m_firstname = value
End Set
End Property
Public Property lastName() As String
Get
Return m_lastname
End Get
Set(value As String)
m_lastname = value
End Set
End Property
#Region "INotifyPropertyChanged Members"
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
#End Region
#Region "Private Helpers"
Private Sub NotifyPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
#End Region
End Class
Then I add new user to collection, like so...
oc_userlist.Add(New user With { _
.Selected = False, _
.accountName = "awiles", _
.firstName = "Anthony", _
.lastName = "Wiles"
})
I'm trying to filter using a textbox, with the textchanged event, and it's working wonderfully...
Private Sub TextBox_TextChanged(sender As Object, e As TextChangedEventArgs)
Dim result = oc_userlist.Where(Function(w) w.accountName.Contains(txtFilter.Text.ToString) _
Or w.firstName.Contains(txtFilter.Text.ToString) _
Or w.lastName.Contains(txtFilter.Text.ToString))
dgvResults.ItemsSource = result
End Sub
The problem I'm having is once this is filtered, using the textbox, then I try to select something, it tells me
'EditItem' is not allowed for this view.
I'm having issues trying to get around this issue, can anybody point me in the correct direction?
The reason could be the result of your Linq query (with the Where) is not a List(Of T) but an IEnumerable.
Try adding a call to ToList()
Dim result = oc_userlist.Where(Function(w) w.accountName.Contains(txtFilter.Text.ToString) _
Or w.firstName.Contains(txtFilter.Text.ToString) _
Or w.lastName.Contains(txtFilter.Text.ToString)) _
.ToList()
Then tell if it works

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 Datagrid DataGridComboBoxColumn Autogenerate at runtime

I have a datagrid in my vb.net 4.0 wpf project. I have seen many examples in XAML on how to bind a DataGridComboBoxColumn however I need to do this in code as I have auto-generating columns. I switch the datagridbinding source to multiple data sets.
Inside some of these custom classes are a couple lists. I can get text and checkboxes to auto-generate correctly. When it comes across my array (I have tried many different kinds) I just see a textboxcolumn with the words (Collection) in it.
For instance - One screen I am grabbing system information on WMI calls. One of the calls returns all IP addresses on the server (We can have up to 8 IP addresses) I do not want a column per IP address. I would like to include a list of those into the datagrid so you can drop down and see them.
Any suggestions on if this is possible or if I am doing something wrong?
Thank you
Sample Code
Imports System.Collections.ObjectModel
Class MainWindow
Dim ServerInfoArray As ObservableCollection(Of ServerInfo) = New ObservableCollection(Of ServerInfo)
Private ReadOnly _ipAddresses As ObservableCollection(Of String) = New ObservableCollection(Of String)
Private Sub GetInfo(ByVal list As List(Of String))
For Each server As String In list
Dim tempip As List(Of String) = New List(Of String)
Dim sinfo As ServerInfo = New ServerInfo
tempip.Add("192.129.123.23")
tempip.Add("23.213.223.21")
sinfo.IPArray = tempip
sinfo.Servername = server
ServerInfoArray.Add(sinfo)
Next
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
Dim serverlist As List(Of String) = New List(Of String)
serverlist.Add("Test")
serverlist.Add("Random")
serverlist.Add("Local")
GetInfo(serverlist)
End Sub
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Dim Col_Serial As DataGridTextColumn = New DataGridTextColumn()
Col_Serial.Binding = New Binding("Servername")
Col_Serial.Header = "Servername"
Col_Serial.Width = 40
Dim Col_IPArray = New DataGridComboBoxColumn()
Col_IPArray.Header = "IP Addresses"
Col_IPArray.IsReadOnly = True
Col_IPArray.ItemsSource = Me._ipAddresses
Col_IPArray.SelectedItemBinding = New Binding("IPArray")
DataGrid1.Columns.Add(Col_Serial)
DataGrid1.Columns.Add(Col_IPArray)
DataGrid1.ItemsSource = ServerInfoArray
End Sub
End Class
Class ServerInfo
Dim _Servername As String
Dim _IPArray As List(Of String)
Public Property Servername() As String
Get
Return _Servername
End Get
Set(ByVal value As String)
_Servername = value
End Set
End Property
Public Property IPArray As List(Of String)
Get
Return _IPArray
End Get
Set(ByVal value As List(Of String))
_IPArray = value
End Set
End Property
Public Sub New()
_Servername = Nothing
_IPArray = New List(Of String)
End Sub
End Class
Not sure if I am understanding perfectly but if you can live with AutoGenerateColumns = False then you can manipulate the columns, their properties and bindings from code like below. So you could use whatever logic you need to in order setup the columns from code. I tried to show how you can source the combobox column items from a different object than the items in the DataGrid as well.
This is meant to be a simple example so I just did everything in the code-behind but from an MVVM standpoint, it depends on your preferred approach but possibility is that you could accomplish the same logic from a controller class that spins up your view models, etc...
Hope it helps!
XAML...
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button x:Name="btnRefreshIPList">Refresh list of IPs</Button>
<DataGrid x:Name="dataGrid1"></DataGrid>
</StackPanel>
</Window>
Code behind...
Imports System.Collections.ObjectModel
Class MainWindow
'The list of IPs for column's ItemSource property
Private ReadOnly _ipAddresses As ObservableCollection(Of String)
'The items for binding to the DataGrid's ItemsSource
Private _items As List(Of MyObjectWithIPAddress)
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_ipAddresses = New ObservableCollection(Of String)
_items = New List(Of MyObjectWithIPAddress)
End Sub
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Me.dataGrid1.AutoGenerateColumns = False
dataGrid1.Columns.Clear()
'Example of text column (Text bound to Name property)
Dim dgTxtCol = New DataGridTextColumn()
dgTxtCol.Header = "Name"
dgTxtCol.Binding = New Binding("Name")
dataGrid1.Columns.Add(dgTxtCol)
'Example of combobox column (SelectedItem bound to IPAddress)
Dim dgCmbCol = New DataGridComboBoxColumn()
dgCmbCol.Header = "IP Address"
dgCmbCol.ItemsSource = Me._ipAddresses
dgCmbCol.SelectedItemBinding = New Binding("IPAddress")
dataGrid1.Columns.Add(dgCmbCol)
'Add items to DataGrid
_items.Add(New MyObjectWithIPAddress("foo1"))
_items.Add(New MyObjectWithIPAddress("foo2"))
Me.dataGrid1.ItemsSource = Me._items
End Sub
''' <summary>
''' To emulate fetching the object that has the IP list
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Private Function GetIpList() As MyObjectWithListOfIPs
Dim inst = New MyObjectWithListOfIPs
inst.IPList = New List(Of String)(New String() {"10.0.0.1", "10.0.0.2", "10.0.0.3"})
Return inst
End Function
''' <summary>
''' Updates the ObservableCollection instance based on business object
''' </summary>
''' <remarks></remarks>
Private Sub RefreshIpAddresses()
_ipAddresses.Clear()
For Each ip As String In GetIpList().IPList
_ipAddresses.Add(ip)
Next
End Sub
Private Sub btnRefreshIPList_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles btnRefreshIPList.Click
RefreshIpAddresses()
End Sub
End Class
''' <summary>
''' Object with response data (e.g., list of IPs)
''' </summary>
''' <remarks></remarks>
Class MyObjectWithListOfIPs
Private _ipList As List(Of String)
Public Property IPList() As List(Of String)
Get
Return _ipList
End Get
Set(ByVal value As List(Of String))
_ipList = value
End Set
End Property
End Class
''' <summary>
''' Disperate object that "has an" address
''' </summary>
''' <remarks></remarks>
Class MyObjectWithIPAddress
Public Sub New(name As String)
Me._name = name
End Sub
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _ipAddress As String
Public Property IPAddress() As String
Get
Return _ipAddress
End Get
Set(ByVal value As String)
_ipAddress = value
End Set
End Property
End Class

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.

Resources