WPF ItemsSource works in code-behind but not in XAML - c#

I have a simple combobox with a checkbox inside as such:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="158,180,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding collection}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}"></CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The datacontext is simply the code behind, and to test it I use the following code:
public ObservableCollection<Foo> collection { get; set; }
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
}
When I set the ItemsSource as I have in the code, then it works fine, but I want to set the ItemsSource in the Xaml, however it does not work using the Xaml above. I have also tried setting it to Path = "". Anybody know why?
Thanks

You need to assign DataContext to the control. something like:
var window = new Window1();
window.DataContext = new WindowDC();
window.Show();
where Window1 class contains the combobox, and WindowDC is like:
public class WindowDC
{
public ObservableCollection<Foo> collection { get; set; }
}
That's how this will work.
What you actually do is that you place collection into control class, and set your datacontext for combobox only.
But, for testing purposes, you can still set Combox.Datacontext in control constuctor.

Bindings in WPF always have a Source. If you don't specify the source in the binding itself, then it will implicitly use the DataContext of the control or an ancestor of it. So if you want to bind to properties in your codebehind file, you have to set the DataContext to an object of the class which contains the collection property. In your case this is the instance of the Window (this).
DataContext = this;
As the commentor pointed out, it's not considered good style putting business logic or data inside the code behind file. So consider writing a separate class which contains your collection property and which you can use to initalize your DataContext. If you are writting bigger applications you should take a look at patterns like MVVM, which uses databinding to provide a better separation between your view and your model.
Edit: Changed ordering and incorporated feedback

Make sure there exist a public property collection in your code behind.
in the code behind also do this.DataContext = this
Finally implement INotifyPropertyChanged to tell the view that you have changed the collection once you add items in it
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
//this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
OnPropertyChanged("Collection");
}

It is working when you are setting combo's item source in code behind because the source of combo is getting updated like wise to set the item source in XAML you have to make a property with INotifyPropertyChanged that keep update the combo's itemsource every time you update your collection via this property..
private ObservableCollection<Foo> _Collection;
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
Now as you are filling collection on button click you just have to set that collection in the property as..
_Collection = new ObservableCollection<Foo>();
Foo f = new Foo("DSD");
_Collection .Add(f);
Collection = _Collection ; //here property call OnPropertyChange
like wise you can provide data to any control. It is jsut the game of INotifyPropertyChanged property.
Hope this will help you

Related

WPF ComboBox with custom ItemSource binding

In my app I have a lot of ComboBox with item list predefined by users. I do not want add all lists on my ViewModel (maybe I'm wrong). I prefer to add an additional parameter to the MyComboBox control (new control which inherit from the ComboBox) with the id of list which I want load from database. E.g.:
<MyComboBox ItemsSourceId = "SAMPLE_ID" SelectedItem = "{Binding valueCode}" />
In behind code I will execute query and bind results to ItemSource.
SELECT itemCode, itemValue FROM UserDictionaries WHERE itemListCode = 'SAMPLE_ID'
It is good or bad idea? Maybe you have some sample code? ;)
Advantages of the solution: cleaner ViewModel. Disadvantages: database context in control.
Why don't you want to put it in the ViewModel? This is exactly what the ViewModel is for. If you were to have it on the code-behind of your custom ComboBox, this will defeat the purpose of MVVM, as the ComboBox is now dependent on the data-base. What you should strive for is to have 'loosely coupled' components, and let the ViewModel feed the data to the View.
You can do something like:
public class ViewModel
{
private _itemSource;
public List<Entity> ItemSource
{
get { return _itemSource; }
private set
{
_itemSource = value;
RaisePropertyChanged("ItemSource");
}
}
private void UpdateItemSource(int sampleId)
{
var newItems = SELECT itemCode, itemValue FROM UserDictionaries WHERE itemListCode = 'SAMPLE_ID';
ItemSource = null;
ItemSource = newItems;
}
}
and then update your XAML to:
<ComboBox ItemSource="{Binding ItemSource, UpdateSourceTrigger=PropertyChanged}" SelectedItem = "{Binding valueCode}" />

Can/Should I create instances of a UserControl in my viewmodel?

Question regarding UserControls and MVVM. I have my wpf app with a main View/ViewModel. The viewmodel has a ObservableCollection of my usercontrol(s) that a listbox is bound to. The UserControl instances are added to the collection at run time based on events.
My question is if it's within the MVVM pattern to create the usercontrol objects from my main viewmodel? Example below in the onSomeEvent method is the code I'm unsure of, if this is where I should handle it? This doesn't feel right to me, but I'm still wrapping my mind around mvvm. Should I be adding the user control viewmodel here instead? Thanks for any guidance.
private ObservableCollection<string> myList = new ObservableCollection<string>();
public ObservableCollection<string> MyList
{
get { return myList; }
set
{
myList = value;
RaisePropertyChangedEvent("MyList");
}
}
public void onSomeEvent(string someData1, string someData2)
{
this.MyList.Add(new Views.MyUserControl(someData1, someData2));
}
Ok, I mocked up some code based on feedback from BradleyDotNET and dymanoid as I wrap my mind around it. Pasting it here to see if I'm on the right track.
I modified the listbox in my mainview xaml to add a template:
<ListBox Name="lbMain" Margin="10" ItemsSource="{Binding MyList, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Test1}" FontWeight="Bold" />
<TextBlock Text="{Binding Test2}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I then created a simple class like this to populate a few fields.
public class MyData
{
public MyData(string test1, string test2)
{
this.Test1 = test1;
this.Test2 = test2;
}
private string test1;
public string Test1
{
get
{
return test1;
}
set
{
test1 = value;
}
}
private string test2;
public string Test2
{
get
{
return test2;
}
set
{
test2 = value;
}
}
}
Then in my mainviewmodel I did this:
public void onSomeEvent(string someData1, string someData2)
{
this.MyList.Add(new MyData(someData1, someData2));
}
No, your viewmodel should not create any UserControl instances, because they are views. Furthermore, your main viewmodel shouldn't contain any collections of any views. As #BradleyDotNET mentioned, DataTemplate is the right way for it.
You should change your main viewmodel collection. It shouldn't contain any UserControls (views), but rather their viewmodels. Assuming that you have defined DataTemplates for your sub-viewmodels in XAML, you will get your views automagically created by WPF.
This could look like:
<DataTemplate DataType = "{x:Type local:UserControlViewModel}">
<local:UserControl/>
</DataTemplate>
With this approach, WPF sets the DataContext property value to the sub-viewmodel instance automatically, so you can easily define your bindings in that UserControl.

How to bind data with combobox

I need to know in the simplest form, how to bind data (list of string) to a ComboBox in XAML without using ComboBox.ItemSource = object in the code behind.
I mean what is this:
{Binding Path="What Comes here"}
let's say I have:
class Window1 : UserControl {
List<String> list = new List<String>();// And assign Value to this list
...}
I've tried
{Binding Path=list} or {Binding list}
but nothing has been bind. So how should it be done? (I can't access this combobox in the code behind because Microsoft has limited SilverLight DatGrid to an extent that I can't do it)
Few rules for binding to work properly:
You can bind only with public properties (at least for instance objects) and not with fields.
Be default binding engine looks for property path in DataContext of control where binding is applied on.
If you want to bind to property which doesn't exist in DataContext (or DataContext is not set for control), use RelativeSource markup extension to guide binding engine to resolve property path.
Coming back to your problem statement where you need to bind to List created in code behind, you have to do following changes to code:
// Not sure why you termed it as Window1 if it's actually an UserControl.
public partial class Window1 : UserControl
{
public Window1()
{
InitializeComponent();
MyStrings = new List<string>(new[] { "A", "B", "C", "D" });
DataContext = this; // Rule #2
}
public List<string> MyStrings { get; set; } // Rule #1
}
XAML:
<ComboBox ItemsSource="{Binding MyStrings}"/>
In case you don't set DataContext in constructor of UserControl and still want to bind to property, bind using RelativeSource (Rule #3)
<ComboBox ItemsSource="{Binding MyStrings, RelativeSource={RelativeSource FindAncestor
, AncestorType=UserControl}}"/>
Additional Points
Use ObservableCollection<string> in place of List<string> in case you want to add more items to the list after initialization and want UI to update accordingly.
Read more about MVVM here - Understanding the basics of MVVM pattern. Generally all binding stuff stay in separate class ViewModel so that it can be tested w/o any dependency on UI stuff.
Take a look over here: Model-View-ViewModel (MVVM) Explained
I'm not a SilverLight developer but as far as I know it's just a downstiped version of full wpf.
Try using a ViewModel and define your list in there. Then you can bind the ComboBox.ItemsSource property to it:
public class SomeViewModel : INotifyPropertyChanged
{
private ObservableCollection<string> listOfAwesomeStrings;
public ObservableCollection<string> ListOfAwesomeStrings
{
get { return listOfAwesomeStrings; }
set
{
if (value.Equals(listOfAwesomeStrings))
{
return;
}
listOfAwesomeStrings= value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Set the DataContext of your view like this and also fill your list:
var viewModel = new SomeViewModel();
viewModel.ListOfAwesomeStrings = new ObservableCollection<string>();
viewModel.ListOfAwesomeStrings.Add("Option 1");
viewModel.ListOfAwesomeStrings.Add("Option 2");
viewModel.ListOfAwesomeStrings.Add("Option 3");
this.DataContext = viewModel;
Finally bind to your property in xaml:
<ComboBox ItemsSource="{Binding ListOfAwesomeStrings}" />

Binding a ListBox to an ObservableCollection in XAML

I have a ListBox and a sample ObservableCollection, when I set listBox1.ItemsSource = _collection; in code-behind file, it works fine, but when I do like this in XAML:
ItemsSource="{Binding Collection}"
It doesn't work. In what could be the problem?
In code-behind I have
public ObservableCollection<FeedItem> Collection
{
get { return _collection; }
}
Mostly likely the DataContext of the ListBox (or any of its parents) is not set.
Bind the DataContext to the object holding the collection, and you should be good to go.

WPF ComboBox Binding To Object On Initial Load

I have a combo box that is bound to a list of model objects. I've bound the combo box SelectedItem to a property that is the model type. All of my data binding works beautifully after the window has been loaded. The SelectedItem is set properly and I'm able to save the object directly with the repository.
The problem is when the window first loads I initialize the SelectedItem property and my combobox displays nothing. Before I moved to binding to objects I was binding to a list of strings and that worked just fine on initialization. I know I'm missing something but I can't figure it out.
Thanks in advance for any guidance you can provide.
(One note about the layout of this page. The combo boxes are actually part of another ItemTemplate that is used in a ListView. The ListView is bound to an observable collection in the main MV. Each item of this observable collection is itself a ModelView. It is that second ModelView that has the SelectedItem property.)
Here is my Model:
public class DistributionListModel : Notifier, IComparable
{
private string m_code;
private string m_description;
public string Code
{
get { return m_code; }
set { m_code = value; OnPropertyChanged("Code"); }
}
public string Name
{
get { return m_description; }
set { m_description = value; OnPropertyChanged("Name"); }
}
#region IComparable Members
public int CompareTo(object obj)
{
DistributionListModel compareObj = obj as DistributionListModel;
if (compareObj == null)
return 1;
return Code.CompareTo(compareObj.Code);
}
#endregion
}
Here the pertinent code in my ModelView:
public MailRoutingConfigurationViewModel(int agencyID)
: base()
{
m_agencyID = agencyID;
m_agencyName = DataManager.QueryEngine.GetAgencyName(agencyID);
IntializeValuesFromConfiguration(DataManager.MailQueryEngine.GetMailRoutingConfiguration(agencyID));
// reset modified flag
m_modified = false;
}
private void IntializeValuesFromConfiguration(RecordCheckMailRoutingConfiguration configuration)
{
SelectedDistributionList = ConfigurationRepository.Instance.GetDistributionListByCode(configuration.DistributionCode);
}
public DistributionListModel SelectedDistributionList
{
get { return m_selectedDistributionList; }
set
{
m_selectedDistributionList = value;
m_modified = true;
OnPropertyChanged("SelectedDistributionList");
}
}
And finally the pertinent XAML:
<UserControl.Resources>
<DataTemplate x:Key="DistributionListTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource}, Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
/>
#SRM, if I understand correctly your problem is binding your comboBox to a collection of objects rather than a collection of values types ( like string or int- although string is not value type).
I would suggest add a two more properties on your combobox
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource},
Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
SelectedValuePath="Code"
SelectedValue="{Binding SelectedDistributionList.Code }"/>
I am assuming here that DistributionListModel objects are identified by their Code.
The two properties I added SelectedValuePath and SelectedValue help the combobox identify what properties to use to mark select the ComboBoxItem by the popup control inside the combobox.
SelectedValuePath is used by the ItemSource and SelectedValue by for the TextBox.
don't call your IntializeValuesFromConfiguration from the constructor, but after the load of the view.
A way to achieve that is to create a command in your viewmodel that run this method, and then call the command in the loaded event.
With MVVM light toolkit, you can use the EventToCommand behavior... don't know mvvm framework you are using but there would probably be something like this.

Categories

Resources