Accessing ListView within an ItemTemplate - wpf

I have an ItemsControl that is bound to a list of Objects.
I have an ItemTemplate set for the ItemsControl... and within the ItemTemplate I have a ListView, which I want to bind to another list of Objects.
How do I gain access to the ListView within the ItemsControl.ItemTemplate so that I can set it's DataContext to the second list of objects?
Here is an example of what I have:
<ItemsControl x:Name="DealerShips" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}" Foreground="White">
<ListView x:Name="CarTypes" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<CheckBox Content="{Binding Path=Name}" IsChecked="{Binding Path=InStock}"></CheckBox>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my VB.NET code I can refer to my "DealerShips" ItemsControl to set the DataContext to a List(Of DealerShip) Objects. I can't figure out how to gain access to the "CarTypes" ListView so that I can set it's DataContext.
Public Class CarsWindow
Private _listOfDealerShips As ObservableCollection(Of DealerShip)
Private _listOfInventory As ObservableCollection(Of Car)
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
_listOfDealerShips = New ObservableCollection(Of DealerShip)
listOfDealerShips.Add(New DealerShip("A"))
listOfDealerShips.Add(New DealerShip("B"))
listOfDealerShips.Add(New DealerShip("C"))
_listOfInventory = New ObservableCollection(Of Cars)
listOfDealerShips.Add(New Car("TypeX",True))
listOfDealerShips.Add(New Car("TypeY",False))
listOfDealerShips.Add(New Car("TypeZ",True))
DealerShips.DataContext = _listOfDealerShips
' I cannot access CarTypes....
'CarTypes.DataContext = listOfInventory
End Sub
Private Class DealerShip
Public Property Name As String
Public Sub New
End Sub
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
End Class
Private Class Car
Public Property Name As String
Public Property InStock As Boolean
Public Sub New
End Sub
Public Sub New(ByVal name As String, ByVal isInStock As Boolean)
Me.Name = name
Me.InStock = isInStock
End Sub
End Class
End Class
Thank you for your help!
-Frinny

Can you setup a viewmodel that contains your lists and make them public, set the cars window's DataContext to an instance of the viewmodel (maybe via DI), then change your listview in xaml to look like this:
<ListView x:Name="CarTypes" ItemsSource="{Binding DataContext.ListOfInventory, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">

I ended up creating a class that had both lists within it.
Like this:
Public Class DealershipDetails
Public Property ListOfDealerShips As ObservableCollection(Of DealerShip)
Public Property ListOfInventory As ObservableCollection(Of Car)
Public Sub New()
ListOfDealerShips = New ObservableCollection(Of DealerShip)
ListOfDealerShips.Add(New DealerShip("A"))
ListOfDealerShips.Add(New DealerShip("B"))
ListOfDealerShips.Add(New DealerShip("C"))
ListOfInventory = New ObservableCollection(Of Cars)
ListOfInventory.Add(New Car("TypeX",True))
ListOfInventory.Add(New Car("TypeY",False))
ListOfInventory.Add(New Car("TypeZ",True))
End Sub
End Class
That way I could bind the ItemsControl to an instance of the DealershipDetails class and selectively bind the controls within the ItemsControl to the appropriate lists.
While this approach works, my original question remains unanswered.
I do not know how to access a child control.
-Frinny

Related

Datagrid values not updating in wpf vb.net when using databinding on an observablecollection

I'm using databinding in a datagrid, and I can get the values into the grid, but when I edit them, it's not firing the update of the values in the property for the value. I want to be able to do something with the value when I have changed it.
In my PLPage.xaml.vb I have:
Class PLPage
Implements INotifyPropertyChanged
Public MyCollection As New ObservableCollection(Of Pl)
Dim reg As List(Of String) = New List(Of String)()
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub New()
InitializeComponent()
Me.DataContext = MyCollection
End Sub
Private Sub FillData()
Dim pla As Pl = New Pl()
pla.Reg = reg(count)
MyCollection.Add(pla)
PGTable.ItemsSource = MyCollection
End Sub
Public Class Pl
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property _r As String
Public Property Reg As String
Get
Return _r
End Get
Set(value As String)
If _r <> value Then
_r = value
'RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Reg"))
NotifyPropertyChanged("Reg")
End If
End Set
End Property
Private Sub NotifyPropertyChanged(propertyName As String)
Me.OnPropertyChanged(New PropertyChangedEventArgs(propertyName))
End Sub
Protected Overridable Sub OnPropertyChanged(e As PropertyChangedEventArgs)
RaiseEvent PropertyChanged(Me, e)
End Sub
End Class
Then in my xaml I have:
<DataGrid ItemsSource="{Binding Pl}" x:Name="PlGridTable" AutoGenerateColumns="False">
<DataGrid.Columns>
<materialDesign:MaterialDataGridTextColumn Header="Desig" Width="Auto" Binding="{Binding _reg, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}"/>
</DataGrid.Columns>
</DataGrid>
When I first load it up, it is hitting the reg property stuff, and assigning the value correctly, but when I update the value using the editing element built into the material design package, it's not firing off anywhere. Any help as to why would be appreciated.
{Binding _reg} should be {Binding Reg} since the property is named Reg:
<materialDesign:MaterialDataGridTextColumn Header="Desig" Width="Auto" Binding="{Binding Reg, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}"/>

Why isn't my ItemsControl showing my ViewModels?

I am trying to populate a page with a list of items from my database. I have created models and viewmodels for these items, but they are not showing up in my ItemsControl.
I have a Model, which has a corresponding ViewModel that implements INotifyPropertyChanged.
Model:
Public Class ItemModel
Private _year As String
Public Property Year As String
Get
Return _year
End Get
Set(value As String)
_year = value
End Set
End Property
Public Sub New()
_year = Now.Year & "-" & (Now.Year + 1)
End Sub
Public Function ToString() As String
Return _year & " Item Model"
End Function
End Class
ViewModel:
Imports System.ComponentModel
Public Class ItemViewModel
Implements INotifyPropertyChanged
Private _currentItem As ItemModel
Public Property CurrentItem As ItemModel
Get
Return _currentItem
End Get
Set(value As ItemModel)
If _currentItem IsNot value Then
_currentItem = value
NotifyPropertyChanged("CurrentItem")
End If
End Set
End Property
Public Sub New()
_currentItem = New DciSurveyModel()
End Sub
Public Function ToString() As String
Return _currentItem.Year & " Item ViewModel"
End Function
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
I bind the ItemsControl to an ObservableCollection of ViewModels, but the ViewModels do not appear. I have tried using an ItemsTemplate to create a textbox setting the Text={Binding Path=CurrentItem.Year} to no avail.
XAML:
<Page x:Class="ItemPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="ItemPage"
Title="ItemPage" Loaded="Page_Loaded_1" Margin="10">
<Grid>
<ItemsControl ItemsSource="{Binding Path=ItemCollection}" />
</Grid>
</Page>
Here is the code-behind:
Imports OClient = Oracle.DataAccess.Client
Imports System.Collections.ObjectModel
Imports System.Data
Class ItemPage
Private ds As DataSet
Private itemsTable As DataTable
Public Property ItemsCollection As ObservableCollection(Of ItemViewModel)
Private Sub Page_Loaded_1(sender As Object, e As RoutedEventArgs)
Dim itemsQry = "select item_year from items order by item_year desc"
Dim queryCmd As New OClient.OracleCommand(itemsQry, O.con)
Dim adapter As New OClient.OracleDataAdapter(queryCmd)
ds = New DataSet
adapter.Fill(ds, "items")
itemsTable = ds.Tables("items")
ItemsCollection = New ObservableCollection(Of ItemViewModel)
For Each r As DataRow In surveys.Rows
Dim newItem As New ItemViewModel
newItem.CurrentItem.Year = r.Item("ITEM_YEAR").ToString
Next
Me.DataContext = Me
End Sub
End Class
I am having a very hard time figuring out where my app is falling apart. Is it in my implementation of the ViewModels? Am I not binding the data correctly? Do I need to do something different with my ObservableCollection?
Thanks for helping a newbie.
You iterate over the elements of surveys.Rows and create a new ItemViewModel for each one, but you never add them to ItemsCollection, by ItemsCollection.Add(newItem):
For Each r As DataRow In surveys.Rows
Dim newItem As New ItemViewModel
newItem.CurrentItem.Year = r.Item("ITEM_YEAR").ToString
ItemsCollection.Add(newItem)
Next
You're also using a wrong Path for the ItemsSource Binding. It must be ItemsCollection instead of ItemCollection.
Besides that, instead of overriding ToString() you should declare a DataTemplate:
<ItemsControl ItemsSource="{Binding ItemsCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding CurrentItem.Year, StringFormat={}{0} Item ViewModel}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

WPF ComboBox DataBinding vb.net

I am trying to populate a combobox using databinding.Below are the snippets of my code.
XAML:
<ComboBox Name="cmbClientName" ItemsSource="{Binding Path=blClientList}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectedValue="{Binding Path=blClientList}"/>
Code behind 'MainWindow':
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim blClientList As ObservableCollection(Of String) = New ObservableCollection(Of String)
Dim objClientRepository As PET_ClientRepository = New PET_ClientRepository
For Each objClient As PET_Client In objClientRepository.GetAll()
blClientList.Add(objClient.Name)
Next
Me.cmbClientName.ItemsSource = blClientList
End Sub
Class1.vb:
Public Property Name As String
Get
Return _Name
End Get
Set(value As String)
_Name = value
End Set
End Property
I would like to see the name of the client in my combobox. The code above does not return anything and combobox is blank.I know the problem lies in binding but can not figure out what I am doing wrong.
Here's an example that may help you achieve your desired result:
Re: your comment, binding is good, you just need to set context
WPF
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4VB"
Title="MainWindow" Height="100" Width="293">
<Window.DataContext>
<local:Vm />
</Window.DataContext>
<Grid>
<ComboBox ItemsSource="{Binding Collection}" DisplayMemberPath="Name" />
</Grid>
</Window>
important things being that you add your local namespace:
xmlns:local="clr-namespace:WpfApplication4VB"
and that you set DataContext
Classes
Public Class Vm
Public Property Collection As System.Collections.ObjectModel.ObservableCollection(Of Thing)
Public Sub New()
Collection = New ObjectModel.ObservableCollection(Of Thing)()
Collection.Add(New Thing("Test1"))
Collection.Add(New Thing("Test2"))
Collection.Add(New Thing("Test3"))
End Sub
End Class
Public Class Thing
Public Property Name As String
Public Sub New(Name As String)
Me.Name = Name
End Sub
End Class
Thank you everyone for your help.
I went ahead with populating the combobox in the code behind in stead of binding it to a datasource. I didn't even have to pass "ItemSource" in XAML. I did however pass ItemSource in Mainwindow.vb which did the trick for me.
Below are the snippets of code that are working:
Code behind MainWindow.vb
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Me.Username.Text = System.Security.Principal.WindowsIdentity.GetCurrent().Name
Dim blClientList As ObservableCollection(Of String) = New ObservableCollection(Of String)
Dim objClientRepository As PET_ClientRepository = New PET_ClientRepository
For Each objClient As PET_Client In objClientRepository.GetAll()
blClientList.Add(objClient.Name)
Next
Me.cmbClientName.ItemsSource = blClientList
XAML:
<ComboBox Name="cmbClientName" IsReadOnly="True" PresentationTraceSources.TraceLevel="High"/>
Tracelevel isn't doing anything but is just there to provide debug tracing support.
Class1.vb
Public Property Name As String
Get
Return _Name
End Get
Set(value As String)
_Name = value
End Set
End Property
Replace this:
Me.cmbClientName.ItemsSource = blClientList
with this:
Me.DataContext = blClientList
and correct XAML as:
<ComboBox Name="cmbClientName" ItemsSource="{Binding}"
DisplayMemberPath="." SelectedValuePath="."/>

Directly binding a BitmapImage created inside a class bound to a ListBox in WPF, possible?

I am adding object directly to a ListBox, and inside this class, I've got a BitmapImage object.
I'm using an ItemTemplate :
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=ElementIcon}"></Image>
<TextBlock Text="{Binding Path=ElementName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
And I directly add object of this class :
Public Class ExplorerClass
Implements INotifyPropertyChanged
Public Property ElementType As String = Nothing
Public Property ElementName As String = Nothing
Public Property ElementContainer As String = Nothing
Public Property ElementIcon As New BitmapImage
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Public Sub New(ByVal WantedElementContainer As String, ByVal WantedElementName As String, ByVal WantedElementType As String)
ElementType = WantedElementType
ElementName = WantedElementName
ElementContainer = WantedElementContainer
Dim str As New MemoryStream
Dim IWorking As Icon = showIcon(ElementName.Substring(ElementName.LastIndexOf(".")))
IWorking.ToBitmap.Save(str, System.Drawing.Imaging.ImageFormat.Png)
ElementIcon.BeginInit()
ElementIcon.StreamSource = str
ElementIcon.EndInit()
NotifyPropertyChanged("ElementIcon")
End Sub
End Class
But, there is no pictures showed;
So, my question is : "How can I bind the BitmapImage" ?
It looks like you are not implementing the INotifyPropertyChanged Interface. Take a look at this page on MSDN. I'm not sure if this is also a problem but try setting the height and width of the image.

WPF ComboBox binding not working as expected

I want my WPF ComboBox's ItemsSource property to be bound to MyListObject's MyList property. The problem is that when I update the MyList property in code, the WPF ComboBox is not reflecting the update. I am raising the PropertyChanged event after I perform the update, and I thought WPF was supposed to automatically respond by updating the UI. Am I missing something?
Here's the CLR object:
Imports System.ComponentModel
Public Class MyListObject
Implements INotifyPropertyChanged
Private _mylist As New List(Of String)
Public Sub New()
_mylist.Add("Joe")
_mylist.Add("Steve")
End Sub
Public Property MyList() As List(Of String)
Get
Return _mylist
End Get
Set(ByVal value As List(Of String))
_mylist = value
End Set
End Property
Public Sub AddName(ByVal name As String)
_mylist.Add(name)
NotifyPropertyChanged("MyList")
End Sub
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Public Event PropertyChanged(ByVal sender As Object, _
ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
Here is the XAML:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:WpfApplication1"
>
<Window.Resources>
<ObjectDataProvider x:Key="MyListObject" ObjectType="{x:Type local:MyListObject}"/>
</Window.Resources>
<Grid>
<ComboBox Height="23"
Margin="24,91,53,0"
Name="ComboBox1"
VerticalAlignment="Top"
ItemsSource="{Binding Path=MyList, Source={StaticResource MyListObject}}"
/>
<TextBox Height="23"
Margin="24,43,134,0"
Name="TextBox1"
VerticalAlignment="Top" />
<Button Height="23"
HorizontalAlignment="Right"
Margin="0,43,53,0"
Name="btn_AddName"
VerticalAlignment="Top"
Width="75">Add</Button>
</Grid>
</Window>
And here's the simple code-behind:
Class Window1
Private obj As New MyListObject
Private Sub btn_AddName_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) _
Handles btn_AddName.Click
obj.AddName(TextBox1.Text)
End Sub
End Class
Thanks!
You are binding to a list of strings. That list class does not implement Inotifyproperty. You should use an observablecollection instead.
I also notice in your code behind you declare
Private obj As New MyListObject
This is not the static resource you bound the combo box to. So your add call would not be reflected in your view.
The ObservableCollection is most likely the solution, but if it still gives you grief, you can directly access your static resource by calling the following code after your list gets updated:
DirectCast(Me.FindResource("MyListObject"), ObjectDataProvider).Source = _myList
Try using a BindingList(Of T) instead of a List(Of T).
Edit: I am new to WPF and it does look like BindingList isn't a complete solution to your problem, but it might be a step in the right direction. I was able to test the MyListObject converted to BindingList in WinForm and the ListChanged event was raised to the ComboBox which then updated its list.
I found this (possible) solution to wrap your class in an ObservableCollection that might help you solve your problem
Enabling WPF Magic Using WCF - Part 1
This is the code to update your object to a BindingList. Combine your code with the code from that resource and you should be good to go.
Public Class MyListObject
...
'Private _mylist As New List(Of String)
Private _mylist As New BindingList(Of String)
...
'Public Property MyList() As List(Of String)
' Get
' Return _mylist
' End Get
' Set(ByVal value As List(Of String))
' _mylist = value
' End Set
'End Property
Public Property MyList() As BindingList(Of String)
Get
Return _mylist
End Get
Set(ByVal value As BindingList(Of String))
_mylist = value
End Set
End Property
...
End Class

Resources