WPF ComboBox DataBinding vb.net - wpf

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="."/>

Related

WPF Combobox Item Update

I am rather new to the WPF setup and I am running into an issue where as far as I can see I have set it up correctly to have my combobox bound to a observable collection of object.
The Combobox will update when I add or delete items. If I make a change the items in the drop down will not show any differently but if I select one that was edited it will now show the new information but only when selected.
I have set up the object class to use INotifyPropertyChanged correctly I think but it does not seem to be functioning. Going to attach the code below so that you can easily see exactly what I am trying to describe.
What I am trying to do it allow a user to push a button and have the text inside a combobox update to show the new text.
Imports System.ComponentModel
Public Class Window2
Public _names As New System.Collections.ObjectModel.ObservableCollection(Of TestClass)
Public Sub BaseLoading() Handles MyBase.Loaded
Dim AddNewItem As New TestClass
AddNewItem.groupName = "Item " + (_names.Count + 1).ToString
_names.Add(AddNewItem)
cbo_Names.SetBinding(ItemsControl.ItemsSourceProperty, New Binding With {.Source = _names})
End Sub
Private Sub button_PreviewMouseDown(sender As Object, e As MouseButtonEventArgs)
Dim AddNewItem As New TestClass
AddNewItem.groupName = "Item " + (_names.Count + 1).ToString
_names.Add(AddNewItem)
_names(0).groupName = ("Value Changed")
End Sub
End Class
Public Class TestClasss
Implements INotifyPropertyChanged
Public _groupName As String = ""
Public Property groupName As String
Get
Return _groupName.ToString
End Get
Set(value As String)
_groupName = value
onPropertyChanged(New PropertyChangedEventArgs(_groupName))
End Set
End Property
Public Event PropertyChagned(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Public Sub onPropertyChanged(ByVal e As PropertyChangedEventArgs)
RaiseEvent PropertyChagned(Me, e)
End Sub
End Class
XAML
<Window x:Class="Window2"
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="button" Content="Button" PreviewMouseDown="button_PreviewMouseDown"/>
<ComboBox x:Name="cbo_Names" Margin="30,5,30,5" IsEditable="False" ItemsSource="{Binding _names, NotifyOnSourceUpdated=True,Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="groupName" SelectedItem="{Binding _names, NotifyOnSourceUpdated=True,Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>
I would appreciate any help locating what I am missing.
You should pass the name of the data-bound property (instead of the value of the property) to the constructor of the PropertyChangedEventArgs:
onPropertyChanged(New PropertyChangedEventArgs("groupName"))
If you are using at least Visual Studio 2015, you could consider making the following change to your onPropertyChanged routine:
Public Sub onPropertyChanged(<System.Runtime.CompilerServices.CallerMemberName> Optional ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Then, in the setter for groupName you can call onPropertyChanged without specifying the property name, and it will be taken from the name of the caller (that is, it will end up being "groupName").
Effectively, this is doing the same thing as the previous answer, but in a way that is easier for you to code and maintain. (Along with the <CallerMemberName> attribute, this works well with NameOf, both making your code more robust against any changes in names of properties.)

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>

Why My MVVM does not work when I change data on the fly?

My xaml:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestMetroChartsVB"
xmlns:chart="clr-namespace:GravityApps.Mandelkow.MetroCharts;assembly=GravityApps.Mandelkow.MetroCharts"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:TestPageViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<chart:ClusteredBarChart ChartTitle="Example1 " ChartSubTitle="Test1">
<chart:ClusteredBarChart.Series>
<chart:ChartSeries SeriesTitle="Errors" ItemsSource="{Binding Errors}"
DisplayMember="Category" ValueMember="Number"/>
</chart:ClusteredBarChart.Series>
</chart:ClusteredBarChart>
<Button x:Name="btnTest" Height="31" Margin="0,0,405,0" Content="Change Data"/>
</StackPanel>
</Grid>
</Window>
My view:
Imports GravityApps.Mandelkow.MetroCharts Class MainWindow
Private Sub btnTest_Click(sender As Object, e As RoutedEventArgs) Handles btnTest.Click
Dim test As New TestPageViewModel
test.changeData()
End Sub End Class
My viewModel:
Imports System.Collections.ObjectModel
Public Class TestPageViewModel
Public Property Errors() As ObservableCollection(Of TestClass)
Get
Return m_Errors
End Get
Private Set
m_Errors = Value
End Set
End Property
Private m_Errors As ObservableCollection(Of TestClass)
Public Sub New()
Errors = New ObservableCollection(Of TestClass)
Errors.Add(New TestClass("Data1", 5))
Errors.Add(New TestClass("Data2", 10))
Errors.Add(New TestClass("Data5", 15))
End Sub
Public Sub changeData()
Errors.Clear()
Errors.Add(New TestClass("DAta9", 10))
End Sub
End Class
My Model:
Public Class TestClass
Public Property Category As String
Get
Return m_Category
End Get
Set(value As String)
m_Category = value
End Set
End Property
Private m_Category As String
Public Property Number As Integer
Get
Return m_Number
End Get
Set(value As Integer)
m_Number = value
End Set
End Property
Private m_Number As Integer
Public Sub New(category As String, number As Integer)
Me.Category = category
Me.Number = number
End Sub End Class
It displays data initially like this:
But when I click my "change data" button data does not change?
What's the problem?
You are changing the wrong data:
Private Sub btnTest_Click(sender As Object, e As RoutedEventArgs) Handles btnTest.Click
Dim test As New TestPageViewModel
test.changeData()
End Sub
In your button's Click event handler, you create a new view model object and call the changeData() on it. But that's not the view model object the view is using, nor do you do anything to make it the view model object the view is using.
The best thing would be to just get the actual view model and change that one:
Private Sub btnTest_Click(sender As Object, e As RoutedEventArgs) Handles btnTest.Click
Dim view As Button = CType(sender, Button)
Dim test As TestPageViewModel = CType(view.DataContext, TestPageViewModel)
test.changeData()
End Sub
The above assumes that the Button sending the Click event has inherited the MainWindow's data context.

Accessing ListView within an ItemTemplate

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

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