WPF TwoWay binding in multiple UserControl - c#

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.

Related

C# BindingList<> not updating a WPF Listbox on changed items

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.

Combo Box Binding Phone 8.1 UAP

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 !

Binding Combobox Name using Id value

I have a combobox control. I am getting combobox values from Database. There I have Id and Name. But I am binding only Name in the combobox. what i want is,
1. If I select any name in the Combobox, I need to save the Corresponding Id in my Database. Now I am able to bind the value from database and Display the Name in Combobox. but when I select any value in combobox and try to save means, it shows null value on the ID. Here is my code. Please help me to find some solution.
Xaml:
<ComboBox x:Name="cb_rentaltype" HorizontalAlignment="Left" Margin="150,5,0,0" VerticalAlignment="Top" Height="35" Width="200"
SelectedValue="{Binding MasterRentalType}"
DisplayMemberPath="RentalTypeName"
SelectedValuePath="RentalTypeId" />
My code Behind:
var status = new MasterRentalType();
List<MasterRentalType> listRentalType =
status.Get<MasterRentalType>() as
List<MasterRentalType>;
cb_rentaltype.ItemsSource = listRentalType;
and i want to bind the Id to the Data context. here is the code,
private void FacilityDataBind()
{
cb_rentaltype.DataContext = ??
}
Note: MasterRentalType is the Table where i get the Values. There I have ID and Name values.
public class MasterRentalType : EntityBase
{
public string RentalTypeId {get; set;}
public string RentalTypeName {get; set;}
}
how can I bind and save the id value?
Your problem is caused because you have data bound the ComboBox.SelectedValue property to your MasterRentalType property, which I assume is of type MasterRentalType, but then you set the SelectedValuePath property to RentalTypeId. So you're saying *make the SelectedValue use the string RentalTypeId property, but then data binding a MasterRentalType to it.
There are a number of solutions. Correcting your example, you should try this:
<ComboBox x:Name="cb_rentaltype" HorizontalAlignment="Left" Margin="150,5,0,0"
SelectedValue="{Binding MasterRentalType.RentalTypeId}"
DisplayMemberPath="RentalTypeName"
SelectedValuePath="RentalTypeId" />
Alternatively, you could have done this:
<ComboBox x:Name="cb_rentaltype" HorizontalAlignment="Left" Margin="150,5,0,0"
SelectedItem="{Binding MasterRentalType}"
DisplayMemberPath="RentalTypeName" />
To find out more about the differences, please take a look at the How to: Use SelectedValue, SelectedValuePath, and SelectedItem page on MSDN.
There are several ways to achieve this. One of them is implemented below:
The xaml should look like this:
<ComboBox x:Name="cb_rentaltype" HorizontalAlignment="Left" Margin="150,5,0,0"
ItemsSource="{Binding MasterRentalTypeColl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedItem="{Binding SelectedMasterRentalType, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
DisplayMemberPath="RentalTypeName">
</ComboBox>
The MasterRentalType class:
public class MasterRentalType : EntityBase, INotifyPropertyChanged
{
private string _RentalTypeId;
private string _RentalTypeName;
public string RentalTypeId
{
get { return _RentalTypeId; }
set
{
_RentalTypeId = value;
NotifyPropertyChanged("RentalTypeId");
}
}
public string RentalTypeName
{
get { return _RentalTypeName; }
set
{
_RentalTypeName = value;
NotifyPropertyChanged("RentalTypeName");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
The model class:
public class MasterRentalModel: INotifyPropertyChanged
{
private MasterRentalType _SelectedMasterRentalType;
private List<MasterRentalType> _MasterRentalTypeColl = new List<MasterRentalType>();
public MasterRentalType SelectedMasterRentalType
{
get { return _SelectedMasterRentalType; }
set
{
_SelectedMasterRentalType = value;
NotifyPropertyChanged("SelectedMasterRentalType");
}
}
public List<MasterRentalType> MasterRentalTypeColl
{
get { return _MasterRentalTypeColl; }
set { _MasterRentalTypeColl = value; }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
And in the code-behind, you just need to assign the model to the DataContext of you ComboBox; you can implement this any other function (here I have implemented it in the event handler of Loaded event of my Window):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
MasterRentalModel masterRentalModel = new MasterRentalModel();
// Fill the list of RentalType here
masterRentalModel.MasterRentalTypeColl.Add(new MasterRentalType() { RentalTypeId = "1", RentalTypeName = "Monthly" });
masterRentalModel.MasterRentalTypeColl.Add(new MasterRentalType() { RentalTypeId = "2", RentalTypeName = "Quarterly" });
masterRentalModel.MasterRentalTypeColl.Add(new MasterRentalType() { RentalTypeId = "1", RentalTypeName = "Yearly" });
cb_rentaltype.DataContext = masterRentalModel;
}
Whenever user changes the selection, SelectedMasterRentalType will be updated. And in the SelectedMasterRentalType, you can get both RentalTypeId and RentalTypeName. If you follow proper binding your model will always be updated; that's the essence of WPF.
Hope this will help.

Binding to member of Listbox item

I don't know if I would be informative enough, but I'm having a problem.
I bound an ObservableCollection to a normal Listbox, everything is working fine, but ImageInfo has a member (Source) which contains the place where the image is, and I need the Source member of the current selected item in the Listbox. However, I don't seem to have a clue where to start.
Maybe you need in your xaml something like <Image Source="{Binding ElementName=myListbox, Path=SelectedItem.Source}"> . Other examples and explanations related to binding here https://stackoverflow.com/a/1069389/1606534
Are you binding in normal mode to a property like: EG: < combobox itemssource={Binding Listing}/>? If so you really just need to have a public property exposed for the 'selecteditem' if memory serves. The real power in Observable Collection from my understanding of WPF is how things can change in real time and you can notice those changes when implementing INotifyPropertyChanged or INotifyCollectionChanged.
<combobox x:Name="mycombo" itemssource="{Binding itemsource}"
selecteditem="{Binding SelectedItem}" />
ViewModel property:
public string SelectedItem { get; set; }
However if you want your property to be noticed when it changes you need to implement INotifyPropertyChanged. Typically then in studios I have worked in they set a private variable at the top of the class and then use it in the get set and then use the public property in bindings.
public class example : INotifyPropertyChanged
{
private string _SelectedItem;
public string SelectedItem
{
get { return _SelectedItem; }
set
{
_SelectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
public void DoSomething()
{
Messagebox.Show("I selected: " + SelectedItem);
}
}

WPF - Auto refresh combobox content

I got a sample mvvm app. The UI has a textbox, a button and a combobox. when I enter something in the textbox and hit the button, the text I enter gets added to an observablecollection. The Combobox is bound to that collection. How do I get the combobox to display the newly added string automaticly?
As I understand correctly, you want to add an item and select it.
Here is the example how it can be done using ViewModel and bindings.
Xaml:
<StackPanel>
<TextBox Text="{Binding ItemToAdd}"/>
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<Button Content="Add" Click="Button_Click"/>
</StackPanel>
ViewModel:
public class MainViewModel:INotifyPropertyChanged
{
public ObservableCollection<string> Items { get; set; }
public string ItemToAdd { get; set; }
private string selectedItem;
public string SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public void AddNewItem()
{
this.Items.Add(this.ItemToAdd);
this.SelectedItem = this.ItemToAdd;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The MainViewModel has 3 properties (one for the TextBox and two other for the ComboBox) and the method AddNewItem without parameters.
The method can be triggered from a command, but there is no standard class for commands, so I will call it from the code-behind:
((MainViewModel)this.DataContext).AddNewItem();
So you must explicitly set an added item as selected after you add it to a collection.
Because the method OnItemsChanged of the ComboBox class is protected and can't be used.
If the ComboBox is bound to an ObservableCollection, the ComboBox will be updated as soon as the collection is changed.
That's the advantage of using an ObservableCollection - you don't need to do any extra coding to update the UI.
If this is not the behavior you're seeing, perhaps you can post some code/xaml.

Categories

Resources