Binding List to ComboBox in VB.Net WPF Application - wpf

I have a ComboBox on a VB.Net WPF application.
<ComboBox HorizontalAlignment="Left" Margin="77,49,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Path=Server.RecipeList()}" DisplayMemberPath="RecipeID" SelectedValuePath="RecipeID"/>
In the code-behind, I have a class variable called Server:
Public Server As BatchServer = New BatchServer
In my Server object, I have a List:
Private mRecipeList As New List(Of Recipe)
And my default constructor for the Server is
Public Sub New()
Server = New BatchRemote.RemoteSupport
PopulateRecipeList()
End Sub
I want to bind this List to the ComboBox, so it should display the RecipeID of each Recipe in my List. My data-binding looks as it does in the first code block, but when I run the application the ComboBox is always empty.
What am I doing wrong here?

First, you need to declare Server as a public Property that correctly implements the INotifyPropertyChanged interface. Then you need to do the same for RecipeList. Then you should be able to Bind like this:
<ComboBox HorizontalAlignment="Left" Margin="77,49,0,0" VerticalAlignment="Top"
Width="120" ItemsSource="{Binding Path=Server.RecipeList}" DisplayMemberPath="RecipeID"
SelectedValuePath="RecipeID" />

You code doesn't work because you're binding to a simple list, and so the property isnt updated when you add or remove some list element for example.
Declare the auxiliar list as an ObservableCollection read more here in the window and binding to the combo like:
Private Property auxRecipeList As New ObservableCollection(Of Recipe)
then in the xaml do the binding like this:
<ComboBox HorizontalAlignment="Left" Margin="77,49,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding auxRecipeList, Mode=TwoWay, UpdateSourcetrigger=PropertyChanged}" DisplayMemberPath="RecipeID" SelectedValuePath="RecipeID"/>

Related

Binding TwoWay to SelectedItem: "Wrong way" synchronization on initialization

I am trying to bind a property of my DataContext to the SelectedItem on a ComboBox like this:
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem="{Binding ValueElement, Mode=TwoWay}">
where the Elements resource is a CollectionViewSource (don't know, whether this matters).
When everything is initialized, the property ValueElement of the DataContext is set to the first item in the CollectionViewSource. What I want, is to initialize it the other way around: I would like to set SelectedItem of the ComboBox to the value of the property or null if no matching item is contained.
How can this be done?
EDIT - Additional information:
The ComboBox is part of a DataTemplate:
<DataTemplate x:Key="ReferenceTemplate"
DataType="viewModels:ElementMetaReferenceViewModel">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<ResourceDictionary>
<views:ElementsForReferenceViewSource x:Key="Elements"
Source="{Binding DataContext.CurrentProject.Elements, ElementName=Root}"
ReferenceToFilterFor="{Binding}"/>
</ResourceDictionary>
</StackPanel.Resources>
<TextBlock Text="{Binding PropertyName}"/>
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem=""{Binding ValueElement, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
The ElementsForReferenceViewSource simply derives from CollectionViewSource and implements an additional DependencyProperty which is used for filtering.
The DataContext of the items in the CollectionViewSource look like this:
public class ElementMetaReferenceViewModel : ViewModelBase<ElementMetaReference, ElementMetaReferenceContext>
{
...
private ElementMetaViewModel _valueElement;
public ElementMetaViewModel ValueElement
{
get { return _valueElement; }
set
{
if (value == null) return;
_valueElement = value;
Model.TargetElement = value.Model;
}
}
...
}
For people encountering the same issue
The above code works as expected. The solution was getting the stuff behind the scenes right. Make sure, that the instance of the ViewModel which is the value of the property you want to bind to is definitely contained in the CollectionViewSource.
In my case the issue was deserializing an object tree incorrectly, so objects were instantiated twice. Then for each object a distinct ViewModel was initialized and then obviously the value of the property was not contained in the list.
Remark
To check whether this is an issue in your case, you can try the following:
Override the ToString() methods of the ViewModels displayed in the ComboBox like this:
public override string ToString()
{
return "VM"+ Model.GetHashCode().ToString();
}
Then you can easily compare the items in the source collection with the value on your property. Not the most professional way, but it did the job for me.

How to create a WPF ListCollectionView to sort DataGrid CollectionViewSources

Here are my CollectionViewSources:
<CollectionViewSource x:Key="topLevelAssysViewSource" d:DesignSource="{d:DesignInstance my:TopLevelAssy, CreateList=True}" />
<CollectionViewSource x:Key="topLevelAssysRefPartNumsViewSource" Source="{Binding Path=RefPartNums, Source={StaticResource topLevelAssysViewSource}}" />
<CollectionViewSource x:Key="topLevelAssysRefPartNumsRefPartNumBomsViewSource" Source="{Binding Path=RefPartNumBoms, Source={StaticResource topLevelAssysRefPartNumsViewSource}}" />
I currently have the following controls feeding data to one another:
DataContext for my window is fed through a Grid housing all of my Controls:
<Grid DataContext="{StaticResource topLevelAssysViewSource}">
A ComboBox:
<ComboBox DisplayMemberPath="TopLevelAssyNum" Height="23" HorizontalAlignment="Left" ItemsSource="{Binding}" Margin="12,12,0,0" Name="topLevelAssysComboBox" SelectedValuePath="TopLevelAssyID" VerticalAlignment="Top" Width="120" />
a ListBox:
<ListBox DisplayMemberPath="RefPartNum1" Height="744" HorizontalAlignment="Left" ItemsSource="{Binding Source={StaticResource topLevelAssysRefPartNumsViewSource}}" Margin="12,41,0,0" Name="refPartNumsListBox" SelectedValuePath="RefPartNumID" VerticalAlignment="Top" Width="120" />
Finally, a DataGrid which I am trying to make Sort-able: (Just one Column for now):
<DataGrid CanUserSortColumns="true" AutoGenerateColumns="False" EnableRowVirtualization="True" HorizontalAlignment="Left" ItemsSource="{Binding Source={StaticResource topLevelAssysRefPartNumsRefPartNumBomsViewSource}}" Margin="6,6,0,1" Name="refPartNumBomsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" Width="707">
<DataGrid.Columns >
<DataGridTextColumn x:Name="cageCodeColumn" Binding="{Binding Path=CageCode}" Header="CageCode" Width="45" />
<DataGridTextColumn x:Name="partNumColumn" Binding="{Binding Path=PartNum}" Header="PartNum" Width="165" SortDirection="Ascending" />
</DataGrid.Columns>
</DataGrid>
My Exact code thus far is:
public partial class MainWindow : Window
{
racr_dbEntities racr_dbEntities = new racr_dbEntities();
public MainWindow()
{
InitializeComponent();
}
private System.Data.Objects.ObjectQuery<TopLevelAssy> GetTopLevelAssysQuery(racr_dbEntities racr_dbEntities)
{
// Auto generated code
System.Data.Objects.ObjectQuery<racr_dbInterface.TopLevelAssy> topLevelAssysQuery = racr_dbEntities.TopLevelAssys;
// Update the query to include RefPartNums data in TopLevelAssys. You can modify this code as needed.
topLevelAssysQuery = topLevelAssysQuery.Include("RefPartNums");
// Update the query to include RefPartNumBoms data in TopLevelAssys. You can modify this code as needed.
topLevelAssysQuery = topLevelAssysQuery.Include("RefPartNums.RefPartNumBoms");
// Returns an ObjectQuery.
return topLevelAssysQuery;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Load data into TopLevelAssys. You can modify this code as needed.
CollectionViewSource topLevelAssysViewSource = ((CollectionViewSource)(this.FindResource("topLevelAssysViewSource")));
ObjectQuery<racr_dbInterface.TopLevelAssy> topLevelAssysQuery = this.GetTopLevelAssysQuery(racr_dbEntities);
topLevelAssysViewSource.Source = topLevelAssysQuery.Execute(MergeOption.AppendOnly);
ListCollectionView topLevelAssyView = CollectionViewSource.GetDefaultView(CollectionViewSource.CollectionViewTypeProperty) as ListCollectionView;
topLevelAssyView.SortDescriptions.Add(new SortDescription("PartNum", ListSortDirection.Descending));
}
I have read and understand the importance of creating the ListCollectionViews in order to handle the sort properties included in the CollectionViewSource, which I got from blog Bea Stollnitz's blog.
However, I keep getting the error message Null Reference Exception Unhandled: "Object reference not set to an instance of the object."
How do I take care of this issue? Do I need to further define my ListCollectionView, or perhaps I need to establish an ICollectionView? My PartNum column contains part numbers that begin with numbers and sometimes letters. Will the standard sortdirections apply?
Please provide full stack trace for the exception, or at least number of line in your example which throws this exception.
From what you've provided so far, I think that the source of error is
ListCollectionView topLevelAssyView = CollectionViewSource.GetDefaultView(CollectionViewSource.CollectionViewTypeProperty) as ListCollectionView;
If you are using Entity Framework, the default View for ObjectQuery Results will not be ListCollectionView, hence NullReferenceException.
To use ObjectQuery/EntityCollection as the source for CollectionViewSource and sort with it you have to wrap it in some other container that supports sorting (and if want to perform CRUD, use that container everywhere instead of source EntityCollection).
For example, try something along those lines:
ObservableCollection<TopLevelAssy> observableCollection = new ObservableCollection(topLevelAssysQuery.Execute(MergeOption.AppendOnly));
((ISupportInitialize)topLevelAssysViewSource).BeginInit();
topLevelAssysViewSource.CollectionViewType = typeof(ListCollectionView);
topLevelAssysViewSource.Source = observableCollection;
topLevelAssysViewSource.SortDescriptions.Add(new SortDescription("CageCode", ListSortDirection.Ascending));
((ISupportInitialize)topLevelAssysViewSource).EndInit();
And change your binding to refer to CollectionViewSource.View property:
ItemsSource="{Binding Source={StaticResource topLevelAssysViewSource}, Path=View}"
Additional reading: http://blog.nicktown.info/2008/12/10/using-a-collectionviewsource-to-display-a-sorted-entitycollection.aspx

Populate ComboBox based on another ComboBox using XAML

I have two ComboBoxes
<ComboBox Name="cmbMake" DisplayMemberPath="MakeName" SelectedValuePath="MakeID"/>
<ComboBox Name="cmbModel" DisplayMemberPath="ModelName"/>
I use LINQ-to-Entities to populate the cmbGroup ComboBox
Dim db as myDataEntity
cmbGroup.ItemsSource = db.Makes
How do I populate my second ComboBox (cmbModels) based on the selection of the first ComboBox (cmbMake) using XAML so that whatever I select in the first ComboBox automatically filters the ItemsSource in the second ComboBox?
Is this even possible?
I am posting the full solution here
XAML
<ComboBox Name="cmbMake" DisplayMemberPath="MakeName" SelectedValuePath="MakeID" Width="200"/>
<ComboBox Name="cmbModel" DisplayMemberPath="ModelName" DataContext="{Binding SelectedItem, ElementName=cmbMake}" Width="200"/>
CODE-BEHIND
Private Sub cmbMake_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs) Handles cmbMake.SelectionChanged
Dim myItem = From m In myModel
Where m.MakeID = cmbMake.SelectedValue
cmbModel.ItemsSource = myItem
End Sub
Whenever the value is changed in the cmbModel ComboBox it will use LINQ to reset the ItemsSource of the cmbModel ComboBox.
Many Thanks to #XAMeLi for the helping hand!
If your data is hierarchical, where each item in db.Makes holds a list of the Models (and lets say this list is in a property called MyModelsList), then:
<ComboBox Name="cmbMake" DisplayMemberPath="MakeName" SelectedValuePath="MakeID"/>
<ComboBox Name="cmbModel" DisplayMemberPath="ModelName" DataContext="{Binding SelectedItem, ElementName=cmbMake}" ItemsSource="{Binding MyModelsList}"/>
It should be possible to use a converter to filter the items, for that you can employ a MultiBinding to get the values for the items and the selection in the other box in.
Would look something like this:
<ComboBox Name="cmbModel" DisplayMemberPath="ModelName">
<ComboBox.ItemsSource>
<MutliBinding>
<MultiBinding.Converter>
<vc:MyFilterConverter/>
</MultiBinding.Converter>
<Binding Path="Items"/> <!-- This should bind to your complete items-list -->
<Binding Path="SelectedValue" ElementName="cmbMake"/>
</MutliBinding>
</ComboBox.ItemsSource>
</ComboBox>
The converter needs to implement IMultiValueConverter.

Setting multiple Datacontext

I am trying to find out how to set correctly multiple DataContexts in XAML page. I have a basic collection that I create in code behind and set ItemSource Binding og AutoCompleteBox to it. At the same time, I have another datacontext to set labelsDataSource inside the grid. If I set this datacontext, the AutoCompleteBox’s itemsSource binding is lost. AutoCompleteBox is inside that grid. I do assign DataContext directly to the objetc this way:
MyAutoCompleteBox.DataContext = this;
I am wondering if there is a better way to do it?
Thank you in advance for the help!
Setting AutoComplete Box:
<sdk:AutoCompleteBox x:Name="MyAutoCompleteBox" IsTextCompletionEnabled="True" ItemsSource="{Binding Items}" />
Code Behind:
public IList<string> Items
{
get;
private set;
}
public Basic_ChildWindow()
{
InitializeComponent();
Items = new List<string>();
Items.Add(#"One");
Items.Add(#"Two");
Items.Add(#"Three");
DataContext = this;
}
Another datacontext in the same XAML page, AutoCompleteBox is inside that grid:
<Grid x:Name="grdBasic_ChildWindow_Right" Style="{StaticResource GridStyle}" DataContext="{Binding Source={StaticResource LabelsDataSource}}">
I'm not sure I understand your question--what is "labelsDataSource"?
However, if what you have posted is all the code and there is nothing more to it, simply remove the datacontext/binding from the grid. The grid does not need a datacontext set (it is simply a visual container--not data-related).
So change this:
<Grid x:Name="grdBasic_ChildWindow_Right" Style="{StaticResource GridStyle}" DataContext="{Binding Source={StaticResource LabelsDataSource}}">
To this:
<Grid x:Name="grdBasic_ChildWindow_Right" Style="{StaticResource GridStyle}">

ListBox not populating on data bind in Silverlight 2

So I'm trying to learn Silverlight so I've built a simple demo app that pulls my home feed from FriendFeed and displays the items in a list.
I've got a listbox defined:
<ListBox x:Name="lstItems" Margin="5,61,5,5" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="8,8,43,8">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
which is being populated by a web service call
private void LoginButton_Click(object sender, RoutedEventArgs e)
{
FriendFeedServiceClient client = new FriendFeedServiceClient();
client.GetHomeCompleted += new EventHandler<GetHomeCompletedEventArgs>(client_GetHomeCompleted);
client.GetHomeAsync(FfUsername.Text, FfApiKey.Password);
}
void client_GetHomeCompleted(object sender, GetHomeCompletedEventArgs e)
{
lstItems.DataContext = e.Result;
}
The FriendFeedServiceClient is doing a call to a local webservice that proxies a request to the actual FriendFeed webservice.
The service call works fine, the items are returned, if I debug the call the lstItems.DataContext property is populated with a list of items with data in them, but the list doesn't display anything, it's always blank. Have I missed something?
You need to bind your Listbox, something like this
<ListBox x:Name="lstItems" Margin="5,61,5,5" Grid.Row="1" ItemsSource="{Binding}">
and then the TextBlock's binding to the path Title should work.
EDIT: You are setting the DataContext, which kind of gives a hint that you are probably binding a custom object, have you tried casting the e.GetResult to your custom object,
something to the likes of
YourCustomObject obj = (YourCustomObject) e.GetResult;
lstItems.DataContext = obj;
HTH
Rather than DataContext you should be setting ItemsSource. If you use DataContext then you have to set ItemsSource with a binding, however, this level of indirection is rather unnecessary for what you're trying to do.
See this MSDN article for details on listing data in the ListBox.
You're not binding to DataContext.
Try adding ItemsSource="{Binding}":
<ListBox x:Name="lstItems" Margin="5,61,5,5" Grid.Row="1" ItemsSource="{Binding}">
Then make sure that both class and Title property of your object are not private.
Also check output (int output window in visual studio) if there are any Binding error messages and let us know.

Resources