ObservableCollection or List data bind with comboBox using MVVM? - c#

I have ObservableCollection items i want to bind this data to my comboBox.
How to bind data with comboBox?
I am using MVVM pattern so suggest me how to bind data using MVVM pattern
I am trying to do this code but not working properly..
In my XAML PAGE:
<ComboBox x:Name="comobo1"
DisplayMemberPath="CardTypeName"
SelectedValuePath="CardTypeID"
ItemsSource="{Binding Path=combodata}">
</ComboBox>
In my ViewModel
(Card is my model)
public ObservableCollection<Card> combodata = new ObservableCollection<Card>();
foreach (var item in App.db.States)
{
Card c = new Card(item.StateName, item.StateID);
combodata.Add(c);
}
How to bind this combodata to my comboBox - what am I doing wrong ?

At first: your combodata has private access modifier instead of public. At second: combodata must be property but not the field. And you'd better add INotifyPropertyChanged implementation to you class.

You have to bind to a public property of the view model that should implement INotifyPropertyChanged.
Here's what you should do:
View:
<ComboBox x:Name="comobo1" DisplayMemberPath="CardTypeName" SelectedValuePath="CardTypeID" ItemsSource="{Binding Path=ComboData}" />
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<Card> comboData;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Card> ComboData
{
get
{
return this.comboData;
}
set
{
if (this.comboData != value)
{
this.comboData = value;
this.NotifyPropertyChanged("ComboData");
}
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
EDIT: you'll also need to set the DataContext property of your view. The simple way is to affect the instance of your ViewModel to the DataContext property in the code-behind of your view.

private ObservableCollection<Card> _combodata;
Public ObservableCollection<Card> comboData
{
get
{
if (_combodata == null)
_combodata = new ObservableCollection<Card>();
return _combodata;
}
set
{
if (value != _combodata)
_combodata = value;
}
}
<ComboBox x:Name="comobo1"
DisplayMemberPath="CardTypeName"
SelectedValuePath="CardTypeID"
ItemsSource="{Binding Path=comboData}">
</ComboBox>
And don't forget to set a DataContext property.

There are following problems with the code.
1.You can not bind to private field or property. It should be public property.
2.You only provided ItemsSource="{Binding Path=combodata}" but did not provide a source. Where does combodata come from ?
ObservableCollection has nothing to do unless your combo box items doe not change once filled. In this case List can work well.
To make it work, Change your combodata to public property as
public ObservableCollection<Card> combodata {get;set;}
then,
<ComboBox x:Name="comobo1"
DisplayMemberPath="CardTypeName"
SelectedValuePath="CardTypeID"
ItemsSource="{Binding Path=combodata}" ElementName=mainWindow>
</ComboBox>
by specifying ElementName you are telling WPF binding engine to look for the combodata property of the mainWindow class.
I hope this helps.

My best guess is your ComboBox's DataContext is not set to an instance of your ViewModel
I often use Snoop to debug DataContext problems with the application. It allows you to view your Visual Tree, and see what the DataContext is for all controls.
The DataContext is the data your UI is bound to. Usually the DataContext is set higher up in the Visual Tree, such as on the Window object, although as an example the following line of code would set the ComboBox's DataContext to a new instance of your ViewModel, and then your ComboBox should be able to find the combodata collection to bind to it.
comobo1.DataContext = new MyViewModel();
Also, change your combodata from a Field (no get/set accessor methods) to a Property (see Dmitriy's Answer for an example)

You need to bind to a public property. In your example, combodata is private.

Correct Answer is :
In XAML PAGE":
<CollectionViewSource x:Key="comboBoxCollection" Source="{Binding comboData}"></CollectionViewSource>
<DataTemplate x:Key="ComboBoxDataTemplate">
<Grid MinHeight="25">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="comobo1" DisplayMemberPath="CardTypeName" SelectedValuePath="CardTypeID" ItemsSource="{Binding Source={StaticResource comboBoxCollection }}">
</ComboBox>
</Grid>
</DataTemplate>
In ViewModel:
private ObservableCollection<Card> _combodata;
public ObservableCollection<Card> comboData
{
get
{
if (_combodata == null)
_combodata = new ObservableCollection<Card>();
return _combodata;
}
set
{
if (value != _combodata)
_combodata = value;
}
}
if (_objCardField.FieldTag == "State")
{
cards = new Cards();
foreach (var item in App.db.States)
{
Card c = new Card(item.StateName, item.StateID);
comboData.Add(c);
}
}

Related

Binding model with multiple properties in UserControl using one DependencyProperty [duplicate]

This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
Closed 4 years ago.
I would like to be able to bind complex model (many properties) to UserControl through DependencyProperty, and if model would be edited in UserControl I would like to see this edited information inside my binded model.
Example application: Model, UserControl (xaml + cs), MainWindow (xaml + cs). I have no ViewModel to simplify idea.
Model:
public class MyModel : INotifyPropertyChanged
{
private string _surname;
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string Surname
{
get => _surname;
set
{
_surname = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MyModelEditor.xaml (inside Grid):
<DockPanel>
<TextBox Text="{Binding MyModel.Name}"/>
<TextBox Text="{Binding MyModel.Surname}"/>
</DockPanel>
Also contains this line in UserControl root element:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
MyModelEditor.xaml.cs:
public partial class MyModelEditor : UserControl
{
public MyModel MyModel
{
get => (MyModel)GetValue(MyModelProperty);
set => SetValue(MyModelProperty, value);
}
public static readonly DependencyProperty MyModelProperty =
DependencyProperty.Register("MyModel", typeof(MyModel), typeof(MyModelEditor), new FrameworkPropertyMetadata(null));
public MyModelEditor()
{
InitializeComponent();
}
}
MainWindow.xaml (inside Grid):
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Press Me!" Click="ButtonBase_OnClick"/>
<controls:MyModelEditor MyModel="{Binding MyModel}"/>
</DockPanel>
MainWindow.xaml.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private MyModel _myModel;
public MyModel MyModel
{
get => _myModel;
set
{
_myModel = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show(MyModel?.Name);
}
}
My test scenario: type text in textbox, press button.
Current behavior: Message after pressing button is empty.
Expected behavior: Message after pressing button is same like in textbox.
I wold not like to bind to all properties separately, because in future I will have much more then two properties.
Why current approach does not work?
How can I achieve my goal?
You are apparently not using the UserControl instance as Binding source in your UserControl's XAML. One way to do this would be to set the Binding's RelativeSource:
<TextBox Text="{Binding MyModel.Name,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
However, you don't need a new dependency property at all for this purpose. Just bind the UserControl's DataContext to a MyModel instance, like
<controls:MyModelEditor DataContext="{Binding MyModel}"/>
The Bindings in the UserControl's XAML would automatically work with the MyModel object, like this:
<DockPanel>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Surname}"/>
</DockPanel>
For both of your TextBox controls, you should define their Binding with a TwoWay mode (ms docs on binding modes). Which, basically, would assure that the data flow is working in both direction (i.e. from the view model into the view and the other way around):
<DockPanel>
<TextBox Text="{Binding MyModel.Name, Mode=TwoWay}"/>
<TextBox Text="{Binding MyModel.Surname, Mode=TwoWay}"/>
</DockPanel>
As a good practice, you should always explicitly define what is the mode of the the Binding (NOTE: by default it's OneWay TwoWay - how to know which is the default?).
Another tip would be to go ahead and use MvvmHelpers nuget (github project), which could spare you the time of implementing INotifyPropertyChanged. Besides, you shouldn't re-invent the wheel
EDIT: Fixes are in your GitHub repo
Two things to note here
You have not instantiated your ViewModel (i.e. MyModel), so it was always null
You don't need to create DependencyPropery every time you want to pass some information to your UserControl. You could simply bind the DataContext itself

binding instanciated property to UI

I have this class :
public class property : DependencyObject, INotifyPropertyChanged
{
private string _myproperty;
public string MyProperty
{
get
{
return this._myproperty;
}
set
{
this._myproperty = value;
NotifyPropertyChanged("MyProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string sproperty)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sproperty));
}
}
}
In the main window I have created an instance of this class myclass xx = new myclass();, where I populate my property with string data and bind it to XAML like so:
<Window.Resources>
<local:property x:Key="prop"></local:property>
</Window.Resources>
In my TextBox i have set the binding :
Text="{Binding Path=MyProperty, Source={StaticResource prop}}" BorderBrush="#FFC7CACC" />
This will not work unless if i use the existing resources:
var property = (local:property)Resources["prop"];
Is there another way to update the TextBox rather than using the resources? I want to use the normal class instantiation.
if you say Text="{Binding Path=MyProperty, Source={StaticResource prop}}" BorderBrush="#FFC7CACC" />
means that your VM is an instance of property class.
Try to surround your textbox with a Grid and set the grid dataContext with an instance of your poperty clas.
I mean
<Grid DataContext="from view or from behind assign your vm= new property()">
<TextBox Text="{Binding Path=MyProperty" ....../>
</Grid>
Try this:
<Window.DataContext>
<local:property/>
<Window.DataContext>
<TextBox Text="{Binding MyProperty}"/>
After setting the data context, just try to build the application, the build will succeed if it can find the property class in the local namespace.
After building your app, if succeeded, you can try to set the binding and also the Intellisense will automatically show MyProperty in Binding Options.
If this doesn't work, try to set the data context and binding using the Properties panel. Maybe visually you can get things right.
Try it, and if it fails, tell me where it went wrong

How to bind to one element of an ObservableCollection

I have an ObservableCollection and want to bind a Textbox to a specific element of that collection. The Items in the ObservableCollection are of a Type that implements INotifyPropertyChanged.
I have thought about creating a Property that selects the right element from the ObservableCollection, but then I would have to make this Property realise when the corresponding element in the Collection changes and I am not sure if this is the right way to do this.
Usually, especially if you use MVVM, you'll have a viewModel with your ObservableCollection and a property for the SelectedItem that you update with data binding.
For example, your viewModel could look like this:
class ProductsViewModel : INotifyPropertyChanged
{
public ObservableCollection<Product> Products { get; set; }
private Product _selectedProduct;
public Product SelectedProduct
{
get { return _selectedProduct; }
set
{
_selectedProduct = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct"));
}
}
public ProductsViewModel()
{
Products = new ObservableCollection<Product>();
Products.Add(new Product() { Name = "ProductA" });
Products.Add(new Product() { Name = "ProductB" });
}
public event PropertyChangedEventHandler PropertyChanged;
}
Your window object xaml:
<Window x:Class="ProductsExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox HorizontalAlignment="Left" Height="171" Margin="32,29,0,0" VerticalAlignment="Top" Width="176"
ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct, Mode=TwoWay}"
DisplayMemberPath="Name"
/>
<TextBox HorizontalAlignment="Left" Height="33" Margin="36,226,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="172"
Text="{Binding SelectedProduct.Name, Mode=TwoWay}"/>
</Grid>
</Window>
and the code-behind where you just set the datacontext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ProductsViewModel();
}
}
Whenever you select a product in the listbox, the textbox is updated with the selected product, and if you change the product in the textbox (if product correctly implements INotifyPropertyChanged) the item in the listbox will also be updated.
Obviously you can achieve all this only using the code-behind, but for several reasons explained here:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx, it is better to have a ViewModel
If the item you need is specific by index you can access using the index
<TextBlock Text="{Binding MyItemsSource[2]}" />
To solve my problem I created a Property that selects the right element from the ObservableCollection and created an event Handler that is added to the CollectionChanged event of the ObservableCollection and raises the PropertyChanged Event for my SelectionProperty.
In code that looks something like this in the constructor of the class containing the ObservableCollection and the SelectionProperty:
myObservableColleciton.CollectionChanged +=
new NotifyCollectionChangedEventHandler(
myObservableCollection_CollectionChanged);
somewhere else in the class define this event handler:
void myObservableCollection_CollectionChanged(
Object sender, NotifyCollectionChangedEventArgs e){
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectionProperty"));
}
}
my selectionProperty looks something like this:
public User SelectionProperty
{
get { return myObservableCollection.First( user => user.id == 0); }
}
if the SelectionProperty depends on more than the ObservableCollection (maybe we want to find a user closest to a certain age, that is set elsewhere) then it needs to be made sure that the PropertyChanged event for SelectionProperty is raised as well, when those other properties change.

Bind a WPF View to couple of DataContexts

Objective: Set the visibility of a control based on the selected value of a ComboBox
Issue: The property that is being used to check the visibility is in the VM, however I don't know how to use it as DataContext is already defined to another object, i.e would I need to bind 2 datacontexts?!
Details:
I have a CustomControl that I load in my view associating to it a DataContext (a List of objects that is displayed as a grid:
<GUI:Counterparties_UserInputs x:Name="UserInputs" DockPanel.Dock="Right" DataContext="{Binding Source={StaticResource counterpartiesDataView}}"/>
In that user control I have some StackPanel which visibility should be triggered based on the selection of a ComboBox:
<ComboBox ItemsSource="{Binding Source={StaticResource CounterpartyTypes}}" SelectedValue="{Binding SelectedCounterpartyType}"/>
<StackPanel Visibility="{Binding Path=SelectedCounterpartyType,Converter={StaticResource SelectedValueToVisible}}"/>
The issue I have is that the code behind is never hit as I don't find how to associate an "extra" DataContext to the view.
Here is my code behind:
public partial class Counterparties_UserInputs : UserControl
{
...
public Counterparties_UserInputs()
{
// this.DataContext = _cptyUserInputsVM;
_cptyUserInputsVM = new Counterparties_UserInputs_VM();
InitializeComponent();
}
}
And the ViewModel where the Property "SelectedCounterpartyType" is never hit:
public class Counterparties_UserInputs_VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _selectedCounterpartyType;
public string SelectedCounterpartyType
{
get
{
return _selectedCounterpartyType;
}
set
{
_selectedCounterpartyType = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedCounterpartyType"));
}
}
}
}
I've seen that answer already but it's not exactly what I am doing... So would really appreciate your help! Thank you!

Implementing a ListView with a CollectionViewSource - Not Refreshing?

I'm working on setting up a ListView whose Source property is set to an ivar of a class of mine, called Cat.
Each Cat has an ObservableCollection of Trait objects:
private ObservableCollection<Trait> _traits = new ObservableCollection<Trait>();
public ObservableCollection<Trait> Traits
{
get
{
return _traits;
}
}
public void AddTrait(Trait t)
{
_traits.Add(t);
// Is this redundant? Is one better than the other?
this.OnPropertyChanged("_traits");
this.OnPropertyChanged("Traits");
}
public IEnumerator<Object> GetEnumerator()
{
return _traits.GetEnumerator();
}
And then I'm assigning the Source property to this Traits collection:
this.CollectionViewSource.Source = CurrentCat.Traits;
This works properly, and the Trait objects are properly displayed in my ListView.
The issue is that changes to this underlying _traits collection do not cause the UI to update properly. For example, this:
void AddTraitButton_Click(object sender, RoutedEventArgs e)
{
if (this.CurrentCat != null)
{
this.CurrentCat.AddTrait(new Trait());
}
}
Doesn't seem to have any effect immediately in the UI, but if I reset the Source property like so:
var oldSource = this.CollectionViewSource.Source;
this.CollectionViewSource.Source = null;
this.CollectionViewSource.Source = oldSource;
Then the ListView updates properly. But, I'm sure there must be something that I'm missing, as I'd like for the UI to update upon the addition/removal of an item.
Edit: The CollectionViewSource is being applied to the ListView in my XAML file:
<CollectionViewSource x:Name="CollectionViewSource" x:Key="CollectionViewSource" />
...
<ListView x:Name="ItemListView" ItemsSource="{Binding Source={StaticResource CollectionViewSource}}" ...
I can't seem to find it now, but I seem to remember some problem with binding to CollectionViewSource. Have you tried binding directly to CurrentCat.Traits and setting this.DataContext = this in the code-behind (I am assuming you aren't using MVVM here)?
<ListView x:Name="ItemListView" ItemsSource="{Binding CurrentCat.Traits}" />
Rather than binding to the CollectionViewSource directly and replacing its Source to force a refresh, I believe you want to bind to the CVS's View property...
<ListView x:Name="ItemListView"
ItemsSource="{Binding Source={StaticResource CollectionViewSource}, Path=View}" ...
...and call CollectionViewSource.Refresh() after updating the source collection.
void AddTraitButton_Click(object sender, RoutedEventArgs e)
{
if (this.CurrentCat != null)
{
this.CurrentCat.AddTrait(new Trait());
this.CollectionViewSource.Refresh();
}
}
Also, a couple notes, since you seem relatively new to .NET/WPF conventions:
The private members of .NET classes are typically referred to as "fields" rather than "ivars" (Objective-C background? :))
Prefixing class members with the this keyword is usually redundant, unless there is another identifier in scope with the same name
It's worth exploring the MVVM and related patterns if you'll be doing anything non-trivial in WPF; they help you keep your views (XAML objects) as light and easy-to-change as possible.
In your case, for example, I assume the code you've shown is from the code-behind of whatever Window or UserControl contains your ListView. Following the MVVM pattern would involve creating a separate "ViewModel" class that would contain the Traits collection and expose it via a CollectionViewSource (using the View property, as I've mentioned). Your UserControl would then have an instance of the ViewModel assigned as its DataContext, and the ListView could be bound to the exposed CollectionView.
You may work exclusively with the ObservableCollection still. Although there is one problem - it would not show the data in IsInDesignMode. Maybe in the future it will improve.
public class MainViewModel : ViewModelBase
{
...
private ObservableCollection<PartViewModel> _parts;
public ObservableCollection<PartViewModel> Parts
{
get
{
if (_parts == null)
{
_parts = new ObservableCollection<PartViewModel>();
_parts.CollectionChanged += _parts_CollectionChanged;
}
return _parts;
}
}
object m_ReorderItem;
int m_ReorderIndexFrom;
void _parts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
m_ReorderItem = e.OldItems[0];
m_ReorderIndexFrom = e.OldStartingIndex;
break;
case NotifyCollectionChangedAction.Add:
if (m_ReorderItem == null)
return;
var _ReorderIndexTo = e.NewStartingIndex;
m_ReorderItem = null;
break;
}
}
private PartViewModel _selectedItem;
public PartViewModel SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
_selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
}
...
#region ViewModelBase
public override void Cleanup()
{
if (_parts != null)
{
_parts.CollectionChanged -= _parts_CollectionChanged;
}
base.Cleanup();
}
#endregion
}
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<CollectionViewSource x:Name="PartsCollection" Source="{Binding Parts}"/>
</Grid.Resources>
<ListView Margin="20" CanReorderItems="True" CanDragItems="True" AllowDrop="True"
ItemsSource="{Binding Source={StaticResource PartsCollection}}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
...
</ListView.ItemTemplate>
</ListView>
</Grid>

Categories