I have a ComboBox on a silverlight control, that I want to bind. Sounds simple, except what I'm finding is that because the data for the ItemsSource comes from a web service asynchronously, I need to use the code behind to bind the SelectedValue only after the data has come back.
The collection that the data goes in implements INotifyCollectionChanged and INotifyPropertyChanged, so it should all be working, and indeed the combo box loads properly, but there is no value pre-selected.
What I think is happening is that the SelectedValue is getting bound before the collection has loaded - when the combobox is empty - so nothing is selected, and then later when the data comes in, the combobox is populated, but it is not checking the selected value again.
So whilst I have this working if I use code behind to hook up events and creating bindings in code, I'd like to move this all to XAML with something like:
<ComboBox HorizontalAlignment="Stretch" Margin="5,3,9,127" Name="cboCategoryID" Grid.Row="4" Grid.Column="1"
ItemsSource="{StaticResource Categories}"
SelectedValue="{Binding CategoryID, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}"
SelectedValuePath="CategoryID"
DisplayMemberPath="Caption"
VerticalAlignment="Center">
</ComboBox>
This correctly loads items, but doesn't bind the selected value. If I put the following code in the code-behind, it all works:
public MainControl()
{
InitializeComponent();
CategoryCollection cats = new CategoryCollection();
cats.Dispatcher = this.Dispatcher;
cats.LoadComplete += new EventHandler(cats_LoadComplete);
cboCategoryID.ItemsSource = cats;
cats.LoadAll();
}
private void cats_LoadComplete(object sender, EventArgs e)
{
cboCategoryID.SetBinding(ComboBox.SelectedValueProperty, new System.Windows.Data.Binding("CategoryID"));
}
Is there a way to do this without resorting to code behind?
Are you using mvvm? If so, you can try to set the ItemsSource and SelectedItem in the callback of the web service, or take a look at this post from Kyle.
http://blogs.msdn.com/b/kylemc/archive/2010/06/18/combobox-sample-for-ria-services.aspx
you are already using a collection that notifies of changes, so if the value that you are binding the SelectedValue to is notifying of changes, then all you have to do is set that property after the values are loaded from the webservice. it SHOULD update the combobox automatically, allowing you to do your binding purely in xaml.
public myObject CategoryID { get {....}
set {
this.categoryID = value;
RaisePropertyChanged("CategoryID");}
public void DataLoadedHandler()
{
CategoryID = 34; // this will cause the binding to update
}
take a look at this simple sample:
XAML:
<UserControl
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:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" xmlns:local="clr-namespace:StackoverflowQuestions.Silverlight" xmlns:sdk1="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="StackoverflowQuestions.Silverlight.MainPage"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<DataTemplate x:Key="Item">
<TextBlock Text="{Binding PropertyToBeWatched}" />
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<!--<sdk:DataGrid ItemsSource="{Binding MyList}" RowStyle="{StaticResource Style1}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding PropertyToBeWatched}" Header="Property1"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>-->
<ComboBox ItemsSource="{Binding MyList}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Height="50" VerticalAlignment="Top" ItemTemplate="{StaticResource Item}" />
</Grid>
</UserControl>
Codebehind:
public partial class MainPage : UserControl, INotifyPropertyChanged
{
private ObservableCollection _myList;
private CustomClass _selectedItem;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<CustomClass> MyList
{
get { return _myList ?? (_myList = new ObservableCollection<CustomClass>()); }
set
{
_myList = value;
RaisePropertyChanged("MyList");
}
}
public CustomClass SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
protected void RaisePropertyChanged(string propertyname)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyname));
}
public MainPage()
{
InitializeComponent();
this.DataContext = this;
MyList.Add(new CustomClass() { PropertyToBeWatched = "1"});
MyList.Add(new CustomClass() { PropertyToBeWatched = "2" });
MyList.Add(new CustomClass() { PropertyToBeWatched = "2" });
MyList.Add(new CustomClass() { PropertyToBeWatched = "2" });
SelectedItem = MyList[1]; //Here is where it happens
}
}
By binding the SelectedItem of the ComboBox to an entity, we can achieve what you want. This works TwoWay ofcourse.
Hope this helps. :D
Related
I have a base abstract viewmodel containing 2 static models. In a usercontrol, I bind to both. Only the one binding (language) resolves and updates when the underlying static model changes. The binding to Employee does not update the fields. I have tested that the Employee setter is called and indeed it is. The getter however does not get called after the value changes. Any guidance will be appreciated.
Base Viewmodel:
namespace POC.Windows
{
public abstract class ViewModel : ObservableObject, IDataErrorInfo
{
private static int _languageId;
private static Languages _language;
private static Employee _employee;
public Employee Employee
{
get { return _employee; }
set
{
_employee = value;
NotifyPropertyChanged();
}
}
public int LanguageId
{
get{return _languageId;}
set{
if (_languageId != value)
{
_languageId = value;
LoadLanguage(); //Async populate Language model
NotifyPropertyChanged();
}
}
}
public Languages Language
{
get
{
if (_language == null)
{
_language = new Languages();
_languageId = -1;
LoadLanguage();
}
return _language;
}
set
{
_language = value;
NotifyPropertyChanged();
}
}
//Async Loading functions here - omitted
}
}
UserControl View:
<UserControl x:Class="POC.DesktopClient.UserControls.EmployeeDetails"
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"
xmlns:viewModels="clr-namespace:POC.DesktopClient.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModels:EmployeeDetailsViewModel}">
<UserControl.Resources>
<viewModels:EmployeeDetailsViewModel x:Key="EmployeeDetailsViewModel"/>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0">
<TextBlock VerticalAlignment="Top" Margin="10"
Text="{Binding Path=Language.FirstName}"
Foreground="DarkBlue" FontWeight="Bold" FontSize="20">
</TextBlock>
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="1">
<TextBlock VerticalAlignment="Top" Margin="10"
Text="{Binding Path=Employee.FirstName}"
Foreground="DarkBlue" FontWeight="Bold" FontSize="20">
</TextBlock>
</StackPanel>
</Grid>
haven't touched the usercontrol's xaml.cs and EmployeeDetailsViewModel is empty:
public class EmployeeDetailsViewModel : ViewModel
{
}
---EDIT---
Observable Object:
namespace POC.Windows
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Additional information:
The Usercontrol is hosted in the MainWindow.xaml.
In MainWindow.xaml.cs a datacontext is defined as a MainViewModel (that also is blank and inherits from ViewModel).
The static Employee model is bound to a listbox's selectedItem property.
The listbox is in a separate usercontrol also on the MainWindow.
Ths listbox's viewmodel is also blank and inherits from the base viewmodel. Its xaml.cs is blank, datacontext is set in Xaml, same as the details usercontrol.
LanguageID is bound to a combobox selectedValue on the MainWindow.
So in a nutshell, when I change the language in the combobox, the usercontrol's labels bound to language updates correctly. When I select an employee in the listbox, usercontrol's labels bound to Employee remains blank.
This should be an extremely simple solution, but searching through the internet there seems to be multiple different ways to do binding and NONE seem to actually work.
I've created a simple application with a button, textbox and listbox. The user adds text to the textbox, clicks Add and I want the text to appear in the list box. Note that the Add button will create a Person with the firstname the text in the textbox and the last name "Jones". This is just to figure out how to get binding to actually work. I have the ObservableCollection but can't seem to even figure out how to put in the resource to the object within the class itself. Is this even possible? do I have to create a separate class to have a binding?
Here is the complete XMAL
<UserControl x:Class="simpleBinding.MainPage"
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:z="clr-namespace:simpleBinding"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Canvas x:Name="LayoutRoot" Background="White">
<Button Name="_b" Content="Add" Height="23" HorizontalAlignment="Left" VerticalAlignment="Top" Width="58" Canvas.Left="90" Canvas.Top="5" Click="OnAdd" />
<TextBox Name="_tb" Canvas.Left="12" Canvas.Top="4" Height="24" Width="72"></TextBox>
<ListBox Name="_list" Canvas.Left="18" Canvas.Top="41" Height="98" Width="190" />
</Canvas>
and here is the complete Code behind
namespace simpleBinding
{
public partial class MainPage : UserControl
{
public ObservableCollection<Person> PersonList = new ObservableCollection<Person> ();
public MainPage()
{
InitializeComponent();
}
private void OnAdd(object sender, RoutedEventArgs e)
{
PersonList.Add(new Person(_tb.Text, "Jones"));
}
}
public class Person
{
public string FirstName {private set; get;}
public string LastName {private set; get; }
public Person(string fName, string lName)
{
FirstName = fName;
LastName = lName;
}
}
}
thanks for any help,
chris
To illustrate Ravuthasamy's & aqwert's comments. You have to set a DataContext first. You can set this in DataContext or read how MVVM work (It's a good Silvelight binding pattern) :
c#
public MainPage()
{
InitializeComponent();
DataContext = this;
}
After you can bind the class properties to elements :
Xaml
<ListBox
ItemsSource="{Binding PersonList}"
Canvas.Left="18"
Canvas.Top="41"
Height="98"
Width="190" />
Following the timeline you can see that this has taken me a week to finally get to a solution. I post it here now in hopes that someone else won't waste this much time. There seems to be a lot of posts about how to deal with this issue and the examples are limited. They either show only C# or Xaml. Then CollectionChanged and PropertyChanged aren't dealt with in a single example.
This is a simple example, that implements both collection changed and property changed. As well as binding in Xaml
Here is the Xaml.
<UserControl x:Class="simpleBinding.MainPage"
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:src="clr-namespace:simpleBinding"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Canvas x:Name="LayoutRoot" Background="White" DataContext="{Binding}">
<Canvas.Resources>
<src:PersonList x:Key="myDataSource"></src:PersonList>
</Canvas.Resources>
<Button Name="_b" Content="Add" Height="23" HorizontalAlignment="Left" VerticalAlignment="Top" Width="58" Canvas.Left="90" Canvas.Top="5" Click="OnAdd" />
<Button Canvas.Left="150" Canvas.Top="5" Content="Edit" Height="23" Name="button1" Width="58" Click="OnEdit" />
<TextBox Name="_tb" Canvas.Left="12" Canvas.Top="4" Height="24" Width="72"></TextBox>
<ListBox Name="_list" Canvas.Left="18" Canvas.Top="41" Height="98" Width="190" ItemsSource="{Binding Source={StaticResource myDataSource}}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=FirstName}" Margin="0,0,2,0" />
<TextBlock Text="{Binding Path=LastName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Canvas>
Add a xmlns that will reference your code behind. In this case my namespace is xmlns:src then you can use VS intellisense to go to the correct class.
Add a resource to the layoutRoot item. In my case I'm using a canvas, but it could be Grid or Stackpanel etc.
With the resource declared, you can now set the ItemSource binding in the ListBox.
I've chosen to use a template to display the data which I think is really cool (best part of Xaml!) In this case there are two textBlocks but if my underlying data source had an image, I could have used this was well to graphically display the data. The binding for each textbox can be set because the exposed properties of the object are declared in the C# code. Which will be discussed next
C# Code behind
namespace simpleBinding
{
public partial class MainPage : UserControl
{
public PersonList m_pList = new PersonList();
public MainPage()
{
InitializeComponent();
_list.ItemsSource = m_pList;
m_pList.Add(new Person("John", "Doe"));
}
private void OnAdd(object sender, RoutedEventArgs e)
{
m_pList.Add(new Person("Jones", _tb.Text));
}
private void OnEdit(object sender, RoutedEventArgs e)
{
m_pList[1].FirstName = _tb.Text;
}
}
public class PersonList : ObservableCollection<Person> , INotifyPropertyChanged
{
public PersonList() : base() // need to call base on intialization otherwise the binded resource is not updated.
{
Add(new Person("Willa", "Cather"));
Add(new Person("Isak", "Dinesen"));
Add(new Person("Victor", "Hugo"));
Add(new Person("Jules", "Verne"));
}
}
public class Person : INotifyPropertyChanged
{
private string _fName;
private string _lName;
public event PropertyChangedEventHandler PropertyChanged;
public string FirstName
{
set
{
_fName = value;
NotifyPropertyChanged("FirstName");
}
get
{
return _fName;
}
}
public string LastName
{
set
{
_lName = value;
NotifyPropertyChanged("LastName");
}
get
{
return _lName;
}
}
public Person(string fName, string lName) : base()
{
FirstName = fName;
LastName = lName;
}
public override string ToString()
{
return String.Format("{0} {1}", FirstName, LastName);
}
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I've chosen to use the ObservableCollection because it implements INotifyCollectionChanged. The public variable is exposed which allows you to bind to the resource declared in the Xaml. (Better code, make the var private and have a property that exposes the variable through a get!)
The ListBox _List needs to have its ItemsSource property set in Code Behind!!! without this whenever you change the list (add, delete etc) the UI is not updated. AND in fact you do not need the binding in the ListBox at all because we set the source in Code behind it is nice however in that in the designer with this bound control you can see that the binding is working because there are four names added when instantiating the PersonList.
The ObservableCollection needs to have the INotifyCollectionChanged added. Without this, when a property is changed the UI is NOT changed.
The properties that are to be exposed to the UI need to be implement in the object that is contained within the ObservableCollection (in my case the class Person exposed both FirstName and LastName) and then these properties can be bound in the Xaml (see the textBlocks's)
INotifyPropertyChanged requires that you implement a PropertyChanged event i.e. public event PropertyChangedEventHandler PropertyChanged;
To actually fire that event the "Person" object needs to implement code to do that, which in my case is the NotifyPropertyChanged Method. Each time a property is set, I call this method, which in turn looks to see is the PropertyChanged event is not null, and if not, then it raises that event.
Here is the key to property changes, without adding the , INotifyPropertyChanged to the Observable collection PropertyChanged is null.
Hope this helps someone
I have a combobox that is editable by the user, so I bound the Text property to a property of my class. The ItemsSource of that same combobox is bound to an AsyncObservableCollection (which I did based on other posts and it works nicely).
However, I have a problem when updating the ItemsSource.
Here's the Steps to reproduce:
Select a value in the combobox drop down.
Type some text into the combobox. (say "aaa")
Update the ItemsSource. (via my button click)
Result: The MyText property remains set to the text you typed in ("aaa"), but the combo box shows a blank entry.
However, if you do the same steps above but skip Step 1, the combobox shows the text from the MyText property correctly. This leads me to believe that the selected index/selected value is being used to update the combobox after the update to the ItemsSource is done.
Any ideas on how I can keep the displayed value in sync with the MyText property after an update to the ItemsSource?
In the code provided below I'm updating the ItemsSource on the button click in order to reproduce.
Thank You!
XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="comboBox1" VerticalAlignment="Top" Width="200" IsEditable="True"
DataContext="{Binding Path=MyDataClass}"
ItemsSource="{Binding Path=MyListOptions}"
SelectedIndex="{Binding Path=MySelectedIndex}"
Text="{Binding Path=MyText, UpdateSourceTrigger=LostFocus}"
>
</ComboBox>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="416,276,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
Code behind:
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Diagnostics;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public class DataClass : INotifyPropertyChanged
{
private string mytext = "";
public string MyText
{
get
{
return mytext;
}
set
{
mytext = value;
OnPropertyChanged("MyText");
}
}
private int myselectedindex = -1;
public int MySelectedIndex
{
get
{
return myselectedindex;
}
set
{
if (value != -1)
{
mytext = MyListOptions[value];
OnPropertyChanged("MyText");
}
}
}
private AsyncObservableCollection<string> mylistOptions = new AsyncObservableCollection<string>();
public AsyncObservableCollection<string> MyListOptions
{
get
{
return mylistOptions;
}
set
{
mylistOptions.Clear();
OnPropertyChanged("MyListOptions");
foreach (string opt in value)
{
mylistOptions.Add(opt);
}
OnPropertyChanged("MyListOptions");
}
}
public DataClass()
{
}
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string prop)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
public DataClass MyDataClass { get; set; }
public MainWindow()
{
MyDataClass = new DataClass();
MyDataClass.MyListOptions.Add("Option 1 - Provides helpful stuff.");
MyDataClass.MyListOptions.Add("Option 2 - Provides more helpful stuff.");
MyDataClass.MyListOptions.Add("Option 3 - Provides extra helpful stuff.");
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
private void button1_Click(object sender, RoutedEventArgs e)
{
AsyncObservableCollection<string> newList = new AsyncObservableCollection<string>();
newList.Add("Option A - Provides helpful stuff.");
newList.Add("Option B - Provides more helpful stuff.");
newList.Add("Option C - Provides extra helpful stuff.");
MyDataClass.MyListOptions = newList;
}
}
}
Ok, I solved this problem by binding the SelectedValue to the same property as Text and setting its mode to OneWay.
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="comboBox1" VerticalAlignment="Top" Width="200" IsEditable="True"
DataContext="{Binding Path=MyDataClass}"
ItemsSource="{Binding Path=MyListOptions}"
SelectedIndex="{Binding Path=MySelectedIndex}"
SelectedValue="{Binding Path=MyText, Mode=OneWay}"
Text="{Binding Path=MyText, UpdateSourceTrigger=LostFocus}"
I have an ObservableCollection named SeiveList. I want all the SeiveIdSize from the list (except the last one as it is of no use) and set the DataContext for a Combobox. I added
seiveCmb.DataContext = GlobalUtils.SeiveList;
seiveCmb.DisplayMemberPath = // WHAT SHOULD GO HERE. hOW TO ONLY SHOW SeiveIdSize
// XML
<ComboBox Name="seiveCmb" ItemsSource="{Binding}" Grid.Column="1" Grid.Row="1" Margin="2" SelectedIndex="0" ></ComboBox>
EDITED AS PER Sebastian's suggestion : At present, I just tried out with list for combobox.
My Seive class :
public class Seive : INotifyPropertyChanged
{
// Other Members
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(p));
}
}
In my Window .xaml file :
<Window.Resources>
<CollectionViewSource Source="{Binding Path=comboSeives}"
x:Key="comboSeivesFiltered"
Filter="ComboSeiveFilter">
</CollectionViewSource>
</Window.Resources>
<ComboBox Name="seiveCmb" ItemsSource="{Binding Source={StaticResource comboSeivesFiltered}}" DisplayMemberPath="SeiveIdSize"
Grid.Column="1" Grid.Row="1" Margin="2" SelectedIndex="0"
></ComboBox>
In Window .cs file :
public ObservableCollection<Seive> comboSeives { get; set; }
// Initial original data in Window_Loaded method
comboSeives = GlobalUtils.SeiveList;
public void ComboSeiveFilter(object sender, FilterEventArgs e)
{
Seive sv = e.Item as Seive;
// Add items those is != "TOTAL" and isSelected == false
if (sv.SeiveIdSize != "TOTAL" && sv.IsSelected == false)
e.Accepted = true;
else
e.Accepted = false;
}
If the id is "TOTAL" or isSelected is false (i.e. not added to the grid), then only return true and it will add up in it. With initial all records have isSelected = false.
This is what I have understood from youe explaination and help of this site. and have implemetned this. But in the runtime, I don't see any thing in the combobox. I tried to debug adding break at filter method, but it is never reached there. Can you point out where I am making mistake from the above code.
aNY HELP IS Appreciated.
Thanks
I understand that you want your Collection to be filtered, removing one element. One way to do this is create a CollectionView in your Window.Resources and apply a filter Method - as demonstrated and explained here.
<Window.Resources>
<CollectionViewSource Source="{Binding Path=SeiveList}"
x:Name="seiveListFiltered"
Filter="MyFilter">
</CollectionViewSource>
</Window.Resources>
Your code insinuates that in your case the collection is the DataContext of the Window. This must be changed to match your new Resource:
<ComboBox ItemsSource="{Binding Source={StaticResource seiveListFiltered}}"/>
Note that this will fill your ComboBox with items that resemble the output of your SeiveItem.ToString()-Method (Actually, I don't know the class name of the Item). Use the DisplayMemeberPath-Property to set the name of the Property to display instead.
<ComboBox DisplayMemberPath="SeiveIdSize" ItemsSource="{Binding Source={StaticResource seiveListFiltered}}"/>
I have a very ordinary ViewModel and I am tring to bind a collection of values to a combobox. The problem is nothing is binding. I have checked the ViewModel constructor and the data is being loaded so I suspect its in my XAML but I just cant find out where.
public class OwnerOccupierAccountViewModel : ViewModelBase
{
readonly UserAccountContext _userAccountContext;
readonly LoadOperation<Structure> _loadStructures;
#region Properties
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Structures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
#endregion
public OwnerOccupierAccountViewModel()
{
_userAccountContext = new UserAccountContext();
if (!DesignerProperties.IsInDesignTool)
{
_loadStructures = _userAccountContext.Load(_userAccountContext.GetStructuresQuery());
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
_structures = new ObservableCollection<Structure>();
foreach (var structure in _loadStructures.Entities)
{
Structures.Add(structure);
}
}
}
<UserControl.Resources>
<viewmodel:OwnerOccupierAccountViewModel x:Key='ViewModel'></viewmodel:OwnerOccupierAccountViewModel>
</UserControl.Resources>
<ComboBox x:Name='cboApartments'
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
Width='200' />
Try intializing your ObservableCollection of Structures like that:
void _loadStructures_Completed(object sender, EventArgs e)
{
Structures = new ObservableCollection<Structure>(_loadStructures.Entities);
}
and as it was mentioned earlier i think you should change order here:
if (!DesignerProperties.IsInDesignTool)
{
//other code before
//_loadStructures = ...
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
//and now start loading
}
I did similar, very simple app to check what could gone wrong, but everything works well. I will show you my code, so you can compare and maybe you will find some bugs in your solution.
Structure.cs
public class Structure
{
public Structure(string name)
{
Name = name;
}
public string Name { get; set; }
}
StructureService.cs
public class StructureService
{
public void GetAllStructures(Action<IList<Structure>> CompleteCallback)
{
var temp = new List<Structure>()
{
new Structure("Str1"),
new Structure("Str2"),
new Structure("Str3"),
new Structure("Str4"),
new Structure("Str5"),
new Structure("Str6"),
new Structure("Str7")
};
CompleteCallback(temp);
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
protected void RaisePropertyChanged(string prop)
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
OwnerOccupierAccountViewModel:
public class OwnerOccupierAccountViewModel : ViewModelBase
{
StructureService service;
public OwnerOccupierAccountViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
service = new StructureService();
service.GetAllStructures((result) =>
{
Structures = new ObservableCollection<Structure>(result);
});
}
}
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Stuctures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
}
MainPage.xaml:
<UserControl x:Class="SilverlightApplication1.MainPage"
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:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:OwnerOccupierAccountViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Source={StaticResource ViewModel},Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
If i were in your shoes i wll change xaml to such view:
SuggestedView:
<UserControl x:Class="SilverlightApplication1.MainPage"
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:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<vm:OwnerOccupierAccountViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures, Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
but i understand that it is somehow impossible in your scenario?
Try replacing this line:
_structures = new ObservableCollection<Structure>();
with this:
Structures = new ObservableCollection<Structure>();
And set the binding of ComboBox to OneWay.
Edited to update solution:
Set DisplayMemberPath property of ComboBox as well:
DisplayMemberPath="StructureName"
The binding will only fire when the property is changed. The line setting the backing variable won't call the RaisePropertyChanged event. Even if it did it would be empty at this point anyway and you'd end up with an empty list.
_structures = new ObservableCollection<Structure>();
When you then add to the collection you aren't changing the property value, you're calling the getter so again the RaisePropertyChanged won't fire.
Structures.Add(structure);
You need to build a local collection then use that as the value for the Structures property. This should cause the binding to be triggered.
var structures = new ObservableCollection<Structure>();
foreach ...
Structures = structures;
You are binding directly to the ViewModel key as a source, but is it set as a DataContext anywhere?