I am on a MVVM C# project.
I want to display a list of objects.
I want to add and remove items in this list and ALSO change items in this list.
So I choosed the BindingList<> over the ObservableCollection<>, which would not get noticed if an item has changed.
(I also tested the ObservableCollectionEx which is out there in the web, but this has the same behavior like the BindingList for me).
But the Listbox is not changing when items are changed.
(Adding and removing items is updated in the Listbox)
In my XAML
<ListBox DisplayMemberPath="NameIndex" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}">
or alternative with the ItemTemplate
<ListBox DockPanel.Dock="Right" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}" Margin="0,10,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding NameIndex}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my ViewModel (ViewModelBase is implementing INotifyPropertyChanged etc)
public class ProfileListViewModel : ViewModelBase
{
private BindingList<Profile> profiles;
public BindingList<Profile> Profiles
{
get
{
return profiles;
}
set
{
profiles = value;
RaisePropertyChanged();
}
}
My items are also implementing INotifyPropertyChanged and I am calling OnPropertyChanged("Name") in my Setters.
My model
public class Profile : INotifyPropertyChanged
{
public Profile(){}
public int ProfileID { get; set; }
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Wiring the View with the ViewModel (BindingList is initialized before View)
ProfileListViewModel plvw= new ProfileListViewModel(message.Content);
var profileView = new ProfileListView(plvw);
profileView.ShowDialog();
In the View.xaml.cs
public ProfileListView(ProfileListViewModel plvw)
{
InitializeComponent();
DataContext = plvw;
}
When I am changing the name of an object then I get the ListChanged event to which I have subscribted in my ViewModel (Profiles.ListChanged += Profiles_ListChanged;) for testing BUT the items in the ListBox are NOT changing.
What am I doing wrong?
How can I get a updated Listbox?
Since your DisplayIndex is the computed property NameIndex, you need to call OnPropertyChanged("NameIndex") when its value changes due to a change in other properties, e.g.:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
OnPropertyChanged("NameIndex");
}
}
Use
Profiles.ResetBindings() to bind it again.
Related
I have multiple UserControl which contain a shared ViewModel.
It's a DataGrid where the user click on a row to see the detail of the row (the actual structure is more complex).
The problem is when I handle the SelectionChanged in the grid, I update the shared ViewModel to update the ContactDetail but it doesn't update the value in the TextBoxes (the object is updated in ContactDetail but values are not displayed).
ListContact.xaml.cs
public void contactsTable_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
contacts.current_identity = //Get the associated `IdentityViewModel`
}
ContactDetail.xaml.cs
public partial class ContactDetail : UserControl
{
public ContactsViewModel contacts;
public DetailContact(ContactsViewModel contacts)
{
InitializeComponent();
this.contacts = contacts;
this.DataContext = contacts;
}
}
ContactDetail.xaml
<UserControl x:Class="ContactDetail">
<TextBox Name='address' Text="{Binding Path=contacts.current_identity.address, Mode=TwoWay}"/>
<TextBox Name='phone' Text="{Binding Path=contacts.current_identity.phone, Mode=TwoWay}"/>
<TextBox Name='email' Text="{Binding Path=contacts.current_identity.email, Mode=TwoWay}"/>
</UserControl>
ContactsViewModel.cs (IdentityViewModel uses the same structure)
public class ContactsViewModel : INotifyPropertyChanged
{
private List<Contact> _contacts;
public List<Contact> contacts;
{
get { return _contacts; }
set { _contacts = value; OnPropertyChanged("contacts"); }
}
private IdentityViewModel _current_identity;
public IdentityViewModel current_identity
{
get { return _current_identity; }
set { _current_identity = value; OnPropertyChanged("current_identity"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The question is, why doesn't this work and how to notify ContactDetail so that it displays the new value ?
Your data for contacts changes but the original reference location Binding Path=contacts.current_identity.address is still being referred to in the binding. I.E. address is still valid and has not changed. What changed was contacts.current but you are not binding to that.
Remember that binding is simply reflection to a location reference. If the original address changes you would see a change because that is what is being looked for to have a change. But instead the parent instance is what changed.
You need to refactor your bindings to allow for proper update when the current_identity changes.
I have the following:
<ListBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding items}" DisplayMemberPath="s"/>
<TextBlock Text="{Binding SelectedItem.s}"/>
This is definition of SelectedItem
public MemEntity SelectedItem {get; set;}
MemEntity is a class containing
public String s {get; get;}.
Basically, I want s of the selected item to be shown in the TextBlock (same property as shown in ListBox). This doesn't work, so what am I doing wrong?
Try this,
<TextBlock ... Text="{Binding ElementName=items, Path=SelectedItem.s}" />
then add a name to your ListBox as,
<ListBox x:Name="items" SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding items}" DisplayMemberPath="s"/>
There are multiple way to do this. One option has already been provided in another answer that focusing on achieving the desired functionality by binding to a view element. Here is another option.
The view is unaware that selected item has changed. look into using INotifyPropertyChanged
You can create a base ViewModel to encapsulate the repeated functionality
public abstract class ViewModelBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Have the view models inherit from this base class in order for the view to be aware of changes when binding.
public class ItemsViewModel : ViewModelBase {
public ItemsViewModel() {
items = new ObservableCollection<MemEntity>();
}
private MemEntity selectedItem;
public MemEntity SelectedItem {
get { return selectedItem; }
set {
if (selectedItem != value) {
selectedItem = value;
OnPropertyChanged(); //this will raise the property changed event.
}
}
}
public ObservableCollection<MemEntity> items { get; set; }
}
The view will now be aware when ever the SelectedItem property changes and will update the view accordingly.
I have a TextBlock in a ComboBox in a C# WPF project bound to a list of 'Envelope' items, which have a string 'Name' and a double 'Weight' property, the former of which I would like to see displayed in the TextBlock.
When I run my program, the ComboBox appears without any text in it. It properly has three unlabeled items in it, and if I view the ItemsSource or SelectedItem of the ComboBox they show the appropriate values, and other code which interacts with the SelectedItem of the ComboBox behaves properly. The only thing that does not work is that the TextBlock contains no text. If I replace the "{Binding Name}" with "au ghdfjlnvgmumar" then the appropriate garbled characters appear in the ComboBox, so it is definitely a problem with the binding. What is the problem, and how can I get around it?
Relevant code:
xaml:
<ComboBox Name="EnvelopeList" HorizontalAlignment="Center" Width="200" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
C#:
//main window code
public MainWindow()
{
InitializeComponent();
envelopes = new List<Envelope>();
envelopes.Add(new Envelope("TEST", 0));
envelopes.Add(new Envelope("HI", 10));
EnvelopeList.ItemsSource = envelopes;
}
//Envelope class
class Envelope
{
public string Name;
public double Weight;
public Envelope()
{
Name = "[None]";
Weight = 0;
}
public Envelope(string n, double w)
{
Name = n;
Weight = w;
}
public override string ToString()
{
return Name;
}
}
When DataBinding, you can only bind to Properties. Also, you need to update your properties with a PropertyChangedEvent. Otherwise, if you change your property after the initial binding it won't update the UI.
You need to use on property changed and a property
public class Envelope: ModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name= value; OnPropertyChanged("Name"); }
}
}
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
Finally, I notice that you're setting ItemsSource directly. Instead, you want to set your View's DataContext property and then bind to your ItemsSource
Here is a MSDN article on DataBinding that will teach you how to do it properly.
Name is a field, you can only bind to properties.
How do i bind properly to a combox on windows phone 8.1 I tried what i would normally do in winforms but it didnt work. Also this is for a settings page is their any standard practise yet for a 8.1 Phone Store app to create a settings page same way silverlight did.
And before you ask yes the data is their fine have dubged that.
public class City
{
public string id { get; set; }
public string timing_title { get; set; }
}
public class CitysList
{
public List<City> cityList { get; set; }
}
I thought that DisplayMmember path would work when its set from item source
<ComboBox x:Name="cboCitys" ItemsSource="{Binding}" DisplayMemberPath="{Binding timing_title}" HorizontalAlignment="Left" Margin="18,73,0,0" VerticalAlignment="Top" Width="343" Height="51">
</ComboBox>
How i Fetech the data
popcornpk_Dal _dal = new popcornpk_Dal();
CitysList _mycities = await _dal.GetCityListAsync();
cboCitys.ItemsSource = _mycities.cityList;
DisplayMemberPath is used to specify the path to the displayed property, you don't need to bind it
DisplayMemberPath="timing_title"
beside that it would be much more elegant if you bind your combobox's itemSource to a Collection property, and implement the INotifyPropertyChanged in your CitysList class, like so :
public class CitysList:INotifyPropertyChanged
{
private ObservableCollection<City> _citylist ;
public ObservableCollection<City> CityList
{
get
{
return _citylist;
}
set
{
if (_citylist == value)
{
return;
}
_citylist = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Xaml
<ComboBox ItemsSource="{Binding CitysList}" DisplayMemberPath="timing_title" />
and don't forget to set the DataContext to an instance of the class that hold the collection, and to update the List just reinstantiate it
CityList = new ObservableCollection<City>(await _dal.GetCityListAsync());
Update
To set the dataContext,
First Create a CityList property in the codebehind,
private CitysList _cityList ;
public CitysList CityList
{
get
{
return _cityList;
}
set
{
if (_cityList == value)
{
return;
}
_cityList = value;
OnPropertyChanged();
}
}
Second, set the page DataContext to the codebehind using
this.DataContext=this; //in the main constructor
or from Xaml using
DataContext="{Binding RelativeSource={RelativeSource Self}}"
the Combobox will automatically inherit the page DataContext
Third Bind to your collection
<ComboBox x:Name="cboCitys" ItemsSource="{Binding CityList.CityList}" DisplayMemberPath="timing_title" HorizontalAlignment="Left" Margin="18,73,0,0" VerticalAlignment="Top" Width="343" Height="51">
PS: you may as well consider adding the CityList collection directly in your codebehind there are no need to add a class just to hold that collection !
I have this combobox
<ComboBox Height="30" SelectedIndex="0" Margin="5 3 5 3" Width="170" ItemsSource="{Binding WonderList}" SelectedValuePath="selectedWonder">
<ComboBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Image Source="{Binding Path}" Height="20"></Image>
<Label Content="{Binding Name}" Style="{StaticResource LabelComboItem}"></Label>
</WrapPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
where I want to show items as an image plus a text.
This is the business class for the objects in the item list
public class Wonder: INotifyPropertyChanged
{
private string name;
private string path;
public event PropertyChangedEventHandler PropertyChanged;
#region properties, getters and setters
public String Name { get; set; }
public String Path { get; set; }
#endregion
public Wonder(string name, string path)
{
this.name = name;
this.path = path;
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
and the code behind of the window
public class Window1 {
public List<Wonder> WonderList;
public Window1()
{
InitializeComponent();
WonderList = new List<Wonder>();
WonderList.Add(new Wonder("Alexandria", "Resources/Images/Scans/Wonders/Alexandria.jpg"));
WonderList.Add(new Wonder("Babylon", "Resources/Images/Scans/Wonders/Babylon.jpg"));
}
}
I´m pretty new to this xaml "magic" and guess I dont understand correctly how the data binding works, I think that with ItemsSource="{Binding WonderList}" it should take the collection with that name (from the code behind) and show their Name and Path, but it shows an empty list.
If I do Combo1.ItemsSource = WonderList; in the code behind (I prefer to use the xaml and avoid the code behind), it shows two blank slots but still don´t know how to show the items.
Can you point me in the right direction?
Thanks
If you want to bind like this ItemsSource="{Binding WonderList}" you have to set the DataContext first.
public Window1()
{
...
this.DataContext = this;
}
Then Binding will find the WonderList in Window1 but only if it is a property too.
public List<Wonder> WonderList { get; private set; }
Next: It is useless to bind to property Name if you assign your value to private field name. Replace your constructor with
public Wonder(string name, string path)
{
this.Name = name;
this.Path = path;
}
Next: Your auto properties ({ get; set; }) will not notify for changes. For this you have to call OnPropertyChanged in setter. e.g.
public String Name
{
get { return name; }
set
{
if (name == value) return;
name = value;
OnPropertyChanged("Name");
}
}
Same thing for WonderList. If you create the List to late in constructor it could be all bindings are already resolved and you see nothing.
And finally use ObservableCollection if you want to notify not for a new list but a new added item in your list.
You are not doing the correct way. Simply saying, you should have a Wonders class holding an ObservableCollection property, which is bound to ComboBox's ItemsSource. You should read MSDN:
http://msdn.microsoft.com/en-us/library/ms752347.aspx