I'm trying to use WPF binding to an object that is contained by the DataContext object and it is not working.
If I place all binding elements in the DataContext ViewModel object, everything works, but if I separate out the data list and data elements into a separate class that is contained by the ViewModel class, the ListBox data will work, but the binding to the individual data elements is not working.
With my experimentation, I assume that the bound object needs to be directly bound to the DataContext ViewModel class. I can do that and it works, but it's not as object oriented or reusable as I would like it to be. I've separated out the data list for the ListBox into it's own class and I'm assuming that since it is an ObservableCollection it works regardless of it being attached to the contained object. Since the individual data elements of the objects are only notified through OnPropertyChanged, no matter what I've tried, I can't get the WPF form to recognize the data, even though the DataContext.CurrentRecord shows the correct data. I can only assume that the OnPropertyChanged notification are not going where I need them to go.
Partial Code for DataContext ViewModel is as follows:
public ObservableCollection<ItemModel> Items
{
get { return ItemListMgr.Items; }
set { ItemListMgr.Items = value; }
}
public ItemModel CurrentItem
{
get { return ItemListMgr.CurrentItem; }
set { ItemListMgr.CurrentItem = value; }
}
Corresponding code in Contained ItemListMgr object is as follows:
public readonly ItemListModel _ItemList;
public ObservableCollection<ItemModel> Items
{
get { return _ItemList.Items; }
set
{
_ItemList.Items = value;
OnPropertyChanged("Items");
}
}
private ItemModel _currentItem;
public ItemModel CurrentItem
{
get { return _currentItem; }
set
{
_currentItem = value;
OnPropertyChanged("CurrentItem");
}
}
The "Items" object is the list that gets displayed and even using the contained object "ItemListMgr", this part still works great. As the user scrolls through the list CurrentItem is set to the active item in "Items", but even though the WPF data entry elements are bound to the CurrentItem elements, they will not change when I scroll through the list.
The Xaml code to bind a text box is as follows:
Text="{Binding CurrentItem.ItemName, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}"
The Xaml code binds to the DataContext object, which is using a simple pass through to the contained ItemListMgr class. I've tried to add the OnPropertyChange to the pass through elements, but that didn't help and I would guess only added to the confusion.
As this is a large amount of code and fairly complex, I hope that I've given enough key elements for you to understand what I'm doing. Hopefully there is a way to allow this type of binding as it will greatly enhance what I'm doing.
Whatever help you can give will be greatly appreciated,
Paul
Related
I have a ListBox:
<ListBox
ItemsSource="{Binding SupplyGroups}"
SelectedItem="{Binding ModelSupplyGroup, Mode=TwoWay}"/>
SupplyGroups is an ObservableCollection<SupplyGroup>
SupplyGroup is an object with an int SupplyGroupId and a string SupplyGroupTitle. There is a public override string ToString() so the ListBox gets the Title for display.
ModelSupplyGroup is a Property that accesses the Model's SupplyGroup.
When the window loads up, the value saved as ModelSupplyGroup does not cause the matching ListBox row to be selected.
If I modify the code so SupplyGroup object is replaced with a string, it works correctly.
The binding appears to work in the opposite direction, so if a ListBox row is selected, it will update the bound ModelSupplyGroup correctly.
Inspecting the Objects in the matching SupplyGroup in the ObervableCollection and ModelSupplyGroup, they have identical values for ID and Title.
public SupplyGroup ModelSupplyGroup
{
get
{
return this.purchasingRepository.GetSupplyGroupById(this.Model.SupplyGroupId);
}
set
{
this.Model.SupplyGroup = value;
}
}
The GetSupplyGroupById() returns the full object from the Id.
The view model property to which the SelectedItem property of a ListBox is bound must return an object that compares equal to an element of the collection to which the ItemsSource property is bound.
If the item class does not override the Equals() method, equality means referential equality, which means that the view model property must return an object reference that is contained in the source collection.
The method
purchasingRepository.GetSupplyGroupById(this.Model.SupplyGroupId);
created an object with matching properties, but a different instance, i.e. one that was not contained in the SupplyGroups collection.
Modifying the property fixed the problem:
get
{
foreach (var item in SupplyGroups)
{
if (item.SupplyGroupId == this.Model.SupplyGroupId)
{
return item;
}
}
return null;
}
This could also be written using LINQ like
get
{
return SupplyGroups.FirstOrDefault(
sg => sg.SupplyGroupId == Model.SupplyGroupId);
}
I'm having problem with with my WPF application, where the search filter is applied to the observablecollection, when I add a filter to the ICollectionView.
I got two views which two separate viewmodels. In this case, one view allows you to search on a collection and manipulate it, and the second view has a combobox which allows the user to choose an item from the collection.
At first, I'm retrieving the items to my observablecollection as you can see in the code under. Then I'm setting the CollectionViewSource. As now, I'm adding filter to the CollectionView, which is a search I've implemented. My problem is that I thought that the filter would only apply to the ICollectionView collection, which I'm using in the listbox, but it shows out that it also applies to the ObservableCollection. The listbox is using the CollectionView and the combobox is using the ObservableCollection of the categories. But I don't want the filter to be applied to the combobox collection, which uses the observablecolelction, as I want to show all the available items all the time.
How can I fix this?
public ViewModel ()
{
CollectionViewSource.GetDefaultView(Categories);
}
public ObservableCollection<Category> Categories
{
get
{
return this._categories;
}
set
{
if (this._categories!= value)
{
this._categories= value;
this.OnPropertyChanged("Categories");
}
}
}
private ICollectionView _categoriesCollection;
public ICollectionView CategoriesCollection
{
get
{
return this._categoriesCollection;
}
set
{
if (this._categoriesCollection!= value)
{
this._categoriesCollection= value;
this.OnPropertyChanged("CategoriesCollection");
}
}
}
You are binding to the same view: Should I bind to ICollectionView or ObservableCollection
Instead of setting your CategoriesCollection property to the return value of CollectionViewSource.GetDefaultView(_categories), you could create a new view to "fix" this:
CategoriesCollection = new ListCollectionView(_categories);
I have a ListView and a GridView that lists users in an application by names. Whenever the user selects an user to edit, I add a new tab to a TabControl, and bind all editable properties to the WPF controls.
However, when the user is editing in the Edit Tab, the information in the List (specifically, the name field) is also being updated.
Currently I'm making a copy of the object to be edited and leaving the original so it doesn't update the ListView, but isn't there a better/easier way to do this?
I've tried setting the Binding Mode=OneWay, didn't work, and also UpdateSourceTrigger=Explicit in the GridView but also didn't work.
Is there any easier way to do this?
Edit: The way I implemented my INotifyPropertyChanged class is part of the issue, since I have this:
public partial class MyTabControl : UserControl
{
public MyTabControl()
{
InitializeComponent();
//Here, DataContext is a List<Users>
//Users being my Model from the Database
//Some of it's properties are bound to a GridView
//User doesn't implement INPC
}
public void OpenTab(object sender, RoutedEventArgs e)
{
User original = (sender as Button).DataContext as User;
// - This will create a new ViewModel below with the User I'm sending
MyTabControl.AddTab(original);
}
}
And my ViewModel of Users is:
public class UserViewModel : INotifyPropertyChanged
{
public User Original { get; private set; }
public string Name { get { return Original.Name; } set { Original.Name = value; OnPropertyChanged("Name"); } }
public UserViewModel(User original)
{
Original = original ?? new User();
}
// - INPC implementation
}
Since my ViewModel is the one reporting the property changes, I didn't expect my original User to report it as well to the GridView.
The Mode=OneWay causes the information flow to go from the bound data entity to the target UI property only, any change to the UI property will not be bound back.
The reason why the UI content is changing is because the underlying property is read/write (i.e. has a getter and a setter) and is notifying any value change (due to the implementation of the INPC interface).
Presuming that it is a list of User objects you've bound to the GridView, you have two simple options to fix this. Which one is best depends on how much scope for change you have:
change the current Name property on the User object, remove the setter for it. Replace the setter with a method to set the property (i.e. SetUserName(string name)) which then sets the private member variable. Or pass the name as an argument to the constructor of the User entity.
create a new property with only a getter which returns the name and set your binding to that; i.e. public string UserName { get { return Name; }}. As there is only a getter there will be no notification of this property, so if the name does change it won't be propagated via this new property.
First of all, what I'm trying to do is a "simple" binding of a ComboBox to my source.
The structure is something like:
public class Data
{
public ObservableList<string> List {get;set;}
public string Selected {get;set;}
}
Also, it implements INotifyPropertyChanged interface.
My problem is, i found several solutions to do this via XAML, unfortunately i can't do it with XAML since my ComboBoxes have to be generated during runtime.
So my question is, how i can bind my ComboBox to Data.List, and also the selected item (value?) to Data.Selected, and this one should be TwoWay so my Data class knows that something was selected. Keep in mind this has to be through c# code (XAML is no option unfortunately).
Thanks in advance. :)
It's pretty easy. Assuming, that Data has properties instead of fields:
public class Data
{
public Data()
{
List = new ObservableCollection<string>
{
"Apple", "Orange", "Lime"
};
}
public ObservableCollection<string> List { get; private set; }
public string Selected { get; set; }
}
you can write this:
var comboBox = new ComboBox
{
DataContext = new Data()
};
comboBox.SetBinding(ComboBox.ItemsSourceProperty, new Binding("List"));
comboBox.SetBinding(ComboBox.SelectedItemProperty, new Binding("Selected")
{
Mode = BindingMode.TwoWay
});
To add ComboBox into visual tree, just call proper method for the container. E.g., this will work with any ContentControl (like Window):
AddChild(comboBox);
how i can bind my combobox to Data.List, and also the selected item (value?)
Create a custom composite user control which contains the combobox. Map the combobox's properties to two dependencies properties created on the custom control, one to load the data and the other to provide an on demand selected item's data. Any plumbing needs are done inside the codebehind which ultimately provides all the magic.
Then you can create/bind this control dynamically in codebehind as needed in the other page you are working on.
Sounds like a sort of "recursive binding". If your combos are in a container control, what you need is bound the container to a collection of your single combo model, so each view in the ItemsControl will be bound to a single combo model.
Here's the deal: I have to take a SelectedItem from a Listbox, that I got from this question and add it to a ListBox in another UserControl. The ViewModels and models are all setup, I just need to know how to refer to the ListBox that is getting the items.
This would be under ViewModel A -- the ViewModel that controls the user control with the ListBox that receives the items.
//This is located in ViewModelA
private void buttonClick_Command()
{
//ListBoxA.Items.Add(ViewModelB -> SelectedListItem);
}
I don't understand how to get ListBoxA.
Would it be an ObservableCollection of strings?
For further clarification: ListBoxA, controlled by ViewModelA will be receiving values from ListBoxB in ViewModelB. I have included a property for ViewModelB in ViewModelA
You need to have a property in ViewModelA that can be any type that implements IEnumerable. I will use a list:
public const string MyListPropertyName = "MyList";
private List<string> _myList;
/// <summary>
/// Sets and gets the MyList property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public List<string> MyList
{
get
{
return _myList;
}
set
{
if (_myList == value)
{
return;
}
RaisePropertyChanging(MyListPropertyName);
_myList = value;
RaisePropertyChanged(MyListPropertyName);
}
}
Then in your Listbox, you need to set the ItemsSource to this list
<ListBox ItemsSource="{Binding MyList}">
.......
</ListBox>
Now in your constructer, fill MyList with the data you want to display, and on the Add Command, you want to put
MyList.Add(ViewModelB.myString);
ViewModelB.myString assuming from your previous question that in ViewModelB you have a property myString bound to the SelectedItem of ListBoxB, and you have a reference to the instance of ViewModelB in ViewModelA.
This should do it, let me know
Update:
You should be using an ObservableCollection in VMA since the collection will be added to.