Binding to a BindableCollection inside of a custom object WPF + Caliburn.Micro - wpf

I have a class Item that has a property of type BindableCollection named Children. Each Child has different information that I need to display, here's an example.
Public Property CurrentItem() As ObservableCollection(Of ItemModel)
'Used for displaying all item information
'Will only hold one item
Get
Return _currentItem
End Get
Set(ByVal value As ObservableCollection(Of ItemModel))
_currentItem = value
NotifyOfPropertyChange(Function() CurrentItem)
NotifyOfPropertyChange(Function() ItemsChildren)
End Set
End Property
Public Property ItemsChildren() As BindableCollection(Of ChildModel)
Get
If CurrentItem.Count > 0 Then
Return CurrentItem(0).Children
Else
Return New BindableCollection(Of ChildModel)
End If
End Get
Set(ByVal value As BindableCollection(Of ChildModel))
_itemChildren = value
End Set
End Property
XAML
<!-- Child Info DataGrid-->
<DataGrid ItemsSource="{Binding Path=ItemsChildren}" Grid.Row="3" Grid.ColumnSpan="2" Margin="10" AutoGenerateColumns="False" AlternatingRowBackground="Gray" CanUserAddRows="False" HorizontalAlignment="Stretch">
<DataGrid.Columns>
<DataGridTextColumn Header="Child" Binding="{Binding Path=ChildItem}" />
<DataGridTextColumn Header="Name" Binding="{Binding Path=ItemName}" />
<DataGridTextColumn Header="Assy" Binding="{Binding Path=Assembly}"/>
<DataGridTextColumn Header="Balloon Number" Binding="{Binding Path=BallNo}"/>
<DataGridTextColumn Header="Link Text" Binding="{Binding Path=LinkText}"/>
<DataGridTextColumn Header="Qty" Binding="{Binding Path=Quantity}"/>
</DataGrid.Columns>
</DataGrid>
ChildModel is just a class that has properties for the values in the datagrid
Both CurrentItem's NotifyOfPropertyChange do not get run when add items to CurrentItem, so ItemsChildren is not being updated. How can I force NotifyOfPropertyChange?

Not sure if this will work, but in your code CurrentItem is an ObservableCollection(of ItemModel). Because CurrentItem will only ever hold a single value, then it should be:
Public Property CurrentItem() As ItemModel
'Used for displaying all item information
'Will only hold one item
Get
Return _currentItem
End Get
Set(ByVal value As ItemModel
_currentItem = value
NotifyOfPropertyChange(Function() CurrentItem)
NotifyOfPropertyChange(Function() ItemsChildren)
End Set
End Property
This should fire the NotifyOfPropertyChange() when the selection from the datagrid changes. In your XAML, you will also need to add a SelectedItem property:
<DataGrid ItemsSource="{Binding Path=ItemsChildren}" SelectedItem="{Binding Path=ItemModel, Mode=TwoWay}" Grid.Row="3" Grid.ColumnSpan="2" Margin="10" AutoGenerateColumns="False" AlternatingRowBackground="Gray" CanUserAddRows="False" HorizontalAlignment="Stretch">
Hope this helps.

Related

Property names are not available for my datagrid column bindings

Just beginning to learn DataGrid in WPF XAML. This is the code:
Model
Public Class Idea
Public Property IdeaID() As Integer
Public Property Name() As String
End Class
ViewModel
Public Class IdeaViewModel
Implements INotifyPropertyChanged
Public Shared Property allIdeas() As New ObservableCollection(Of Idea)
Public Sub New()
For index = 1 To 10
Dim anitem As New Idea
anitem.Name = "Value " & index
allIdeas.Add(anitem)
Next
End Sub
Public ReadOnly Property AddAnItemToList() As ICommand
Get
Return New RelayCommand(AddressOf InsertAnItem)
End Get
End Property
Public Sub InsertAnItem()
Dim anItem As New Idea
anItem.Name = "Item " & allIdeas.Count()
allIdeas.Add(anItem)
End Sub
Public ReadOnly Property ClearTheList() As ICommand
Get
Return New RelayCommand(AddressOf ClearStoredList)
End Get
End Property
Public Sub ClearStoredList()
allIdeas.Clear()
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
End Class
View (XAML only, no code behind)
<Window x:Class="IdeaView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:MVVM7"
Title="IdeaView" Height="500" Width="200" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:IdeaViewModel/>
</Window.DataContext>
<StackPanel>
<Button Margin="25" Height="50" Content="Insert an item" Command="{Binding AddAnItemToList}"/>
<Button Margin="25" Height="50" Content="Clear stored list" Command="{Binding ClearTheList}"/>
<DataGrid Height="100"
ItemsSource="{Binding allIdeas}"
AutoGenerateColumns="True"
>
</DataGrid>
<DataGrid Height="100"
AutoGenerateColumns="False"
>
<DataGridTextColumn Binding="{Binding allIdeas}"/>
<DataGridTextColumn Binding="{Binding allIdeas}"/>
</DataGrid>
</StackPanel>
</Window>
The first DataGrid comes up fine. The second DataGrid of course does not because you can't bind a column to the entire allIdeas object. I just left that code in to point out where I know I want something like "{Binding Name}", but the way I'm binding the DataGrid isn't correct & I haven't been able to locate a post that addresses this topic at such a basic level.
I'm trying to work my way up to 2-way binding in a DataGrid but I wanted to be sure I understand how the data is connecting first. That's why I'm trying to manually bind the properties of the ViewModel's ObservableCollection to the columns in a DataGrid.
I'm modifying AQuirky's answer, but he seems to have wandered off at the moment. And his answer's got an error in it.
<DataGrid Height="100"
ItemsSource="{Binding allIdeas}"
AutoGenerateColumns="False"
>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding IdeaID}"/>
<DataGridTextColumn Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
Try this...
<DataGrid Height="100"
ItemsSource="{Binding allIdeas}"
AutoGenerateColumns="False"
>
<DataGridTextColumn Binding="{Binding IdeaID}"/>
<DataGridTextColumn Binding="{Binding Name}"/>
</DataGrid>

WPF VB.net Listview How can I display the Value of the Selected Cell

first of all, I'm new in programming with WPF and I have some difficults with the listview in WPF.
I already have a ObservableCollection and want to display the value of the selected Cell in a MsgBox.
My Question is, how should I do that ?
This doesn't work for me:
mylistview.SelectedItem()
My Code:
XAML
<ListView x:Name="mylistView" Margin="10,31,10,149.714" SelectionMode="Multiple" ItemsSource="{Binding}" Grid.ColumnSpan="3" Background="#FFA4A4A4" BorderThickness="2" BorderBrush="#FF6A6F77">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=Track}" Width="100" Header="Track"/>
<GridViewColumn Width="auto" Header="" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Width="50" Height="50" VerticalAlignment="Center" HorizontalAlignment="Center" Source="{Binding Path=Image}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=Endung}" Width="100" Header=" Container"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Album}" Width="100" Header="Album"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Bitrate}" Width="100" Header="Bitrate"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Pfad}" Width="100" Header="Pfad"/>
</GridView>
</ListView.View>
</ListView>
Code-Behind
Dim files As New ObservableCollection(Of Austauscher)
Public Structure Austauscher
Private _track As String
Private _album As String
Private _pfad As String
Private _bitrate As String
Private _endung As String
Private _image As BitmapImage
Property Track() As String
Get
Return _track
End Get
Set(ByVal Value As String)
_track = Value
End Set
End Property
Property Album() As String
Get
Return _album
End Get
Set(ByVal Value As String)
_album = Value
End Set
End Property
Public Property Pfad As String
Get
Return _pfad
End Get
Set(ByVal Value As String)
_pfad = Value
End Set
End Property
Property Bitrate As String
Get
Return _bitrate
End Get
Set(ByVal Value As String)
_bitrate = Value
End Set
End Property
Property Endung As String
Get
Return _endung
End Get
Set(ByVal Value As String)
_endung = Value
End Set
End Property
Property Image As BitmapImage
Get
Return _image
End Get
Set(ByVal Value As BitmapImage)
_image = Value
End Set
End Property
End Structure
And I add files with:
files.Add(New Austauscher With {.Track = "Track", .Pfad = path, .Album = "Album", .Bitrate = "kbs", .Endung = "extension"})
The problem is with the listView, as you have discovered it lacks a "selectedCell" property.
You may find you can get the functionality you require by using the datagrid control, I have made a version that should work in the same way as your listview example;
<DataGrid Name="myGridView" SelectionMode="Extended" SelectionUnit="Cell" ItemsSource="{Binding}" Grid.ColumnSpan="3" Background="#FFA4A4A4" BorderThickness="2" BorderBrush="#FF6A6F77" AutoGenerateColumns="False">
<DataGridTextColumn Header="Track" Binding="{Binding Path=Track}" Width="100"/>
<DataGridTemplateColumn Header="">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Width="50" Height="50" VerticalAlignment="Center" HorizontalAlignment="Center" Source="{Binding Path=Image}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Path=Endung}" Width="100" Header=" Container"/>
<DataGridTextColumn Binding="{Binding Path=Album}" Width="100" Header="Album"/>
<DataGridTextColumn Binding="{Binding Path=Bitrate}" Width="100" Header="Bitrate"/>
<DataGridTextColumn Binding="{Binding Path=Pfad}" Width="100" Header="Pfad"/>
</DataGrid>
The datagrid offers more control over your selections and contains a property called selectedCells, it is a collection because (as in your example) you can select many cells. If you want to have only one selected cell then you should set the SelectionMode property to single (allowing you to select only a single item and set the SelectionUnit (as in my example) to Cell. Alternitively you can use the CurrentCell property that returns the the cell that has focus;
myGridView.CurrentCell
I hope this helps.

Sorted Enumeration in DataGrid Combo Column

I am try to have an enumeration in my datagrid combo box column, but have it sorted by name and still be able to bind the selected option to object that is the source of the grid.
I have tried Two different ways of accomplishing this.
Enum Declaration
public enum Animals
Zebra
Antelope
Ox
Mouse
End Enum
XAML Enum Reference
xmlns:obj="clr-namespace:SMS_Obj.Enumerations;assembly=SMS_Obj"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
<CollectionViewSource x:Key="AnimalEnum">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription />
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.Source>
<ObjectDataProvider MethodName="GetNames" ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="obj:Animals" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</CollectionViewSource.Source>
</CollectionViewSource>
Object Set as ItemsSource for the Grid
List OF
Public Class clsAnimals
Private _AnimalID As SMS_Obj.Enumerations.Animals
Public Property AnimalID() As SMS_Obj.Enumerations.Animals
Get
Return _AnimalID
End Get
Set(ByVal value As SMS_Obj.Enumerations.Animals)
_AnimalID = value
End Set
End Property
End Class
Attempt 1
<DataGridComboBoxColumn x:Name="dgcAnimalName" Header="Animal Name" Width="*" SelectedValueBinding="{Binding Path=AnimalID, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding Source={StaticResource AnimalEnum},Mode=OneWay}" />
Attempt 2
<DataGridTemplateColumn x:Name="dgcAnimalName" Header="Animal Name" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox DataContext="{StaticResource AnimalEnum}" SelectedValuePath="{Binding Path=AnimalsID, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DataContext="{StaticResource AnimalEnum}" SelectedValuePath="{Binding Path=AnimalID, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
Both attempts Bind the enumeration correctly but i lose the values that are on the object already and any new values. I think there is something that I am missing as I have never used a sorted Enum in XAML before.
Thank you in Advance
The Reason that its not Binding is because the GetNames call, makes the SelectedValue a string and not an Integer. I was able to get it to work by changing the object to
Private _AnimalID As SMS_Obj.Enumerations.Animals
Public Property AnimalID() As SMS_Obj.Enumerations.Animals
Get
Return _AnimalID
End Get
Set(ByVal value As SMS_Obj.Enumerations.Animals)
_AnimalID = value
If _TableName <> [Enum].GetName(GetType(SMS_Obj.Enumerations.Animals), _AnimalID ) Then
_TableName = [Enum].GetName(GetType(SMS_Obj.Enumerations.Animals), _AnimalID )
End If
End Set
End Property
Private _AnimalName As String
Public Property AnimalName() As String
Get
Return _AnimalName
End Get
Set(ByVal value As String)
_AnimalName = value
If _AnimalID <> [Enum].Parse(GetType(SMS_Obj.Enumerations.Animals), _AnimalName) Then
_AnimalID = [Enum].Parse(GetType(SMS_Obj.Enumerations.Animals), _AnimalName)
End If
End Set
End Property
And the Binding to this:
<DataGridComboBoxColumn x:Name="dgcAnimalName" Header="Animal Name" Width="*" SelectedValueBinding="{Binding Path=AnimalName, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding Source={StaticResource AnimalEnum},Mode=OneWay}" />

WPF- TabControl with Datagrid in each TabItem

I have been searching around without finding a fix so here I go.
I have a Windows with a center that contain a UserControl that will fill the general area of my application.
I have a TabControl with multiple TabItem. In each TabItem I have to show different controls including Datagrids.
Here is the sample code of my second TabItem.
<TabItem Header="Suivi" IsSelected="True">
<Grid Background="#FFE5E5E5" >
<DataGrid x:Name="dgSuivi" ItemsSource="{Binding Source=Suivi}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Suivi" Binding="{Binding COD_NOM }" />
<DataGridTextColumn Header="Date planifiée" Binding="{Binding DAT_PLAN}" />
<DataGridTextColumn Header="Date révisée" Binding="{Binding DAT_REVIS}" />
<DataGridTextColumn Header="Date réelle" Binding="{Binding DAT_REEL}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</TabItem>
My code behind has a filled property called Suivi
Public Property Suivi As ObservableCollection(Of MyType)
and MyType is the following class:
Public Class MyType
Property COD_NOM as String
Property DAT_PLAN as DateTime
Property DAT_REVIS as DateTime
Property DAT_REEL as DateTime
Public Sub New()
COD_NOM_DAT = Nothing
DAT_PLAN = New System.DateTime(9999, 1, 1)
DAT_REVIS = New System.DateTime(9999, 1, 1)
DAT_REEL = New System.DateTime(9999, 1, 1)
End Sub
End Class
When I change to the second TabItem (Suivi) the datagrid is filled with empty lines.
I've been searching to fix this but I think I am missing a notion here.
Is my binding done right?
Thanks for the comments! That made me think, what made the first one work? I was sure it was my binding to my observable collection but i've been wrong.
What I was missing is the assignation of the ItemsSource...
Suivi = New ObservableCollection(Of MyType)(MyList)
dgSuivi.ItemsSource=Suivi
it does work now...
Thank you and I hope this can be usefull for other out there.

WPF datagrid new row not getting bound to collection

This is my datagrid
<DataGrid Name="dgItems" AutoGenerateColumns="False" CanUserAddRows="True" ItemsSource="{Binding BillItems}" >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<Label Content="{DynamicResource itemcodehdr}"></Label>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=ItemCode, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<Label Content="{DynamicResource itemnamehdr}"></Label>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=ItemName, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="PreviewKeyDown" Handler="dgItems_PreviewKeyDown"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
Private _billItems As ObservableCollection(Of StockAccountPE)
Public Property BillItems() As ObservableCollection(Of StockAccountPE)
Get
Return _billItems
End Get
Set(ByVal value As ObservableCollection(Of StockAccountPE))
_billItems = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("BillItems"))
End Set
End Property
When the window first loads my collection is empty.
After I did the below, I am getting a row in the grid
_billItems = New ObservableCollection(Of StockAccountPE)
_billItems.Add(New StockAccountPE)
_billItems.RemoveAt(0)
BillItems = _billItems
After entering some value into the grid, the collection is still empty. Basically the new row in the datagrid is not getting added to the collection. This is my first issue.
The second issue is how can I raise the PropertyChanged event when an item is added to the collection. Right now it is raised when the collection is assigned some value, not when it is modified.

Resources