Re-establishing WPF databinding after changing property value - wpf

I'm playing with ICollectionView right now, and am encountering a problem where I think I understand the "why", but not the "how do I fix it". :)
I have a ComboBox that's databound to an ICollectionView, and it is initially set with the following code:
NameView = CollectionViewSource.GetDefaultView( names); // names is an IEnumerable<string> that comes from a LINQ query
NameView.CurrentChanged += new EventHandler(NameView_CurrentChanged);
Everything works great until I execute a piece of code that generates a new IEnumerable<string> and sets NameView again with the same code as above. Once I do this, CurrentItem is no longer working properly.
I've run into this problem before with ObservableCollection<string> databound to ComboBoxes, and I get around the "unbinding" problem by using Clear() and Add() instead of setting the ObservableCollection<string> property to a new ObservableCollection<string>.
My questions include:
1. If I wanted to be able to just set the property to a new collection, can I re-establish databinding with the new collection somehow? If so, how? If not, can you explain the WPFisms behind why this is fundamentally not possible?
2. What's the best way to deal with changes in an ObservableCollection<string> or ICollectionView? Is my approach of just Clearing and Adding the only way to do it?

When you bind your WPF Controls to ICollectionViews (Happens when the XAML is parsed withing your InitializeComponent-call - You should really define the bindings in XAML!), the Controls subscribe to the required events published by your collection (e.g. CollectionChanged).
Your collection property is just a reference to a memory address. When you bend this to a new collection (i.e. a new address), the DataBinding won't notice. You can't expect the original Collection to publish something like "IAmOuttaHere", and clearly the controls wouldn't listen to a new collection saying "I'm the new guy". But if I see this correctly, your snippet does nothing but add an eventhandler to the CurrentChanged (meaning your observe when some other item in the Combobox is being selected)
Binding is all about notification, so - as long as you don't tell your controls that the collection has been exchanged, they will stick to the initial collection. Please try to implement INotifyPropertyChanged like so:
public class ViewModel : INotifyPropertyChanged
{
private ICollectionView myCollection;
public ICollectionView MyCollection
{
get
{
return this.myCollection;
}
set
{
this.myCollection = value;
this.OnPropertyChanged("MyCollection");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void ExchangeCollection()
{
this.MyCollection = new CollectionView(....)
}
}
Any bindings should be made to MyCollection. Although, personally, I don't define ICollectionViews myself, since they are not really as nice to work with as for example a nifty IList and they are auto-wrapped around any collection anyway as soon as a binding is defined.
Hope this helps
Sebi

Related

WPF ObservableCollection and Listbox.itemsource exception

Editing this entire post to clarify... I cannot seem to nail this:
BackgroundWorker receives data from a WCF service that is a list of objects. The service reference is configured to be ObservableCollection.
I pass the ObservableCollection via a delegate into my main UI thread and set it equal to the UI threads Local Collection.
A listbox is bound to this local collection and does not update. I've added the following to my collection:
public ObservableCollection<EmployeeData> _empData { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<EmployeeData> EmpData
{
get { return _empData ; }
set
{
_empData = value;
OnPropertyChanged("EmpData");
}
}
private void OnPropertyChanged(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(p));
}
This even fires but the PropertyChanged is always null. My XAML listbox has a binding declared as:
ItemsSource="{Binding Path=EmpData}"
No matter what I do EmpData updates but the ListBox does not, I've tried several other methods but nothing ever changes in the listbox, its always just null.
I've been working on this for over a day now, I cannot seem to get this whole automatic updating thing to 'click'.
I'm not sure that I understand exactly what you are doing, but here are a couple of suggestions.
Have a single ObservableCollection
Bind your itemcollection (or listbox, or whatever) to this
Depending on the user, clear and fill that observablecollection with list data
Have the background worker update the list and refresh the observable collection if anything has changed.
Ideally your EmployeeData class will implement the INotifyPropertyChanged interface, so that property changes will get automatically updated in your view.

How do I make a UserControl that can bind to an ItemsSource or DataContext?

I'm trying to write a simple 2D map editor. Here's my code so far. How do I code the UserControl class that binds to a map? I can't seem to find an example of a UserControl that handles the ItemsSource like the built in ListBox and DataGrid do. I'm thinking I need to find out when ItemsSource gets set and then write code that subscribes to CollectionChanged and PropertyChanged and creates/deletes/positions Images? Should I even be trying to do this when I have 3 ObservableCollections to bind to?
public class Map
{
public ObservableCollection<ObservableCollection<MapSquare>> Squares
= new ObservableCollection<ObservableCollection<MapSquare>>();
}
public class MapSquare
{
public ObservableCollection<MapTile> Items = new ObservableCollection<MapTile>();
}
public class MapTile : INotifyPropertyChanged
{
private CroppedBitmap bmp;
public CroppedBitmap Bitmap {
get{return bmp;}
set{ bmp = value; OnPropertyChanged("Bitmap");}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
When you say "like the built in ListBox and DataGrid", how similar do you mean? Do you just mean the broad ability to deal with a list-like source, or do you mean all the stuff that they can do - item generation, virtualization, item templating, container styling and so on?
The mechanism in ItemsControl that underpins all of the built in list binding in WPF is surprisingly complicated, so it would be a huge amount of effort to reproduce. Worse, there are lots of places where other bits of WPF know about ItemsControl and have special handling for it. (E.g., ScrollViewer and certain panels.) So it's not even possible to produce your own implementation that does exactly the same thing unless you also write your own replacements for all those other parts too.
ItemsControl is one of the most powerful features of WPF, but it's also one of the least well-factored. Basically, if you want its functionality, you pretty much have to use it.
So if you want your UserControl to include ItemsControl functionality, you'll need to put some sort of ItemsControl inside your UserControl and just wire your collection properties through to their ItemsSource properties. That may well be the best approach because you're getting the built-in ItemsControl implementation to do all the work for you.
But if you're only looking to reproduce some specific features, you could handle the collection change events. But I'd only go down that path if you've exhausted the option of getting ItemsControl to do it for you.

Silverlight: how to bind List<T> to data grid

MVVM pattern is implemented in my Silverlight4 application.
Originally, I worked with ObservableCollection of objects in my ViewModel:
public class SquadViewModel : ViewModelBase<ISquadModel>
{
public SquadViewModel(...) : base(...)
{
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
...
_model.DataReceivedEvent += _model_DataReceivedEvent;
_model.RequestData(...);
}
private void _model_DataReceivedEvent(ObservableCollection<TeamPlayerData> allReadyPlayers, ...)
{
foreach (TeamPlayerData tpd in allReadyPlayers)
{
SquadPlayerViewModel sp = new SquadPlayerViewModel(...);
SquadPlayers.Add(sp);
}
}
...
}
Here is a peacie of XAML code for grid displaying:
xmlns:DataControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
...
<DataControls:DataGrid ItemsSource="{Binding SquadPlayers}">
...</DataControls:DataGrid>
and my ViewModel is bound to DataContext property of the view.
This collection (SquadPlayers) is not changed after its creation so I would like to change its type to
List<SquadPlayerViewModel>
. When I did that, I also added
RaisePropertyChanged("SquadPlayers")
in the end of '_model_DataReceivedEvent' method (to notify the grid that list data are changed.
The problem is that on initial displaying grid doesn't show any record... Only when I click on any column header it will do 'sorting' and display all items from the list...
Question1: Why datagrid doesn't contain items initially?
Q2: How to make them displayed automatically?
Thanks.
P.S. Here is a declaration of the new List object in my view-model:
public List<SquadPlayerViewModel> SquadPlayers { get; set; }
You can't use List as a binding source, because List not implement INotifyCollectionChanged it is require for WPF/Silverlight to have knowledge for whether the content of collection is change or not. WPF/Sivlerlight than can take further action.
I don't know why you need List<> on your view model, but If for abstraction reason you can use IList<> instead. but make sure you put instance of ObservableCollection<> on it, not the List<>. No matter what Type you used in your ViewModel Binding Only care about runtime type.
so your code should like this:
//Your declaration
public IList<SquadPlayerViewModel> SquadPlayers { get; set; }
//in your implementation for WPF/Silverlight you should do
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
//but for other reason (for non WPF binding) you can do
SquadPlayers = new List<SquadPlayerViewModel>();
I usually used this approach to abstract my "Proxied" Domain Model that returned by NHibernate.
You'll need to have your SquadPlayers List defined something like this:
private ObservableCollection<SquadPlayerViewModel> _SquadPlayers;
public ObservableCollection<SquadPlayerViewModel> SquadPlayers
{
get
{
return _SquadPlayers;
}
set
{
if (_SquadPlayers== value)
{
return;
}
_SquadPlayers= value;
// Update bindings, no broadcast
RaisePropertyChanged("SquadPlayers");
}
}
The problem is that whilst the PropertyChanged event informs the binding of a "change" the value hasn't actually changed, the collection object is still the same object. Some controls save themselves some percieved unnecessary work if they believe the value hasn't really changed.
Try creating a new instance of the ObservableCollection and assigning to the property. In that case the currently assigned object will differ from the new one you create when data is available.

Databinding with INotifyPropertyChanged instead of DependencyProperties

I have been wrestling with getting databinding to work in WPF for a little over a week. I did get valuable help here regarding the DataContext, and I did get databinding to work via DependencyProperties. While I was learning about databinding, I came across numerous discussions about INotifyPropertyChanged and how it is better than DPs in many ways. I figured that I would give it a shot and try it out.
I am using Josh Smith's base ViewModel class and my ViewModel is derived from it. However, I'm having a bit of trouble getting databinding to work, and am hoping that someone here can tell me where I'm going wrong.
In my ViewModel class, I have an ObservableCollection<string>. In my GUI, I have a combobox that is bound to this OC, i.e.
<ComboBox ItemsSource="{Binding PluginNames}" />
The GUI's DataContext is set to the ViewModel, i.e.
private ViewModel _vm;
public GUI()
{
InitializeComponent();
_vm = new ViewModel();
this.DataContext = _vm;
}
and the ViewModel has the OC named "PluginNames":
public class ViewModel
{
public ObservableCollection<string> PluginNames; // this gets instantiated and added to elsewhere
}
When the GUI is loaded, a method is called that instantiates the OC and adds the plugin names to it. After the OC is modified, I call RaisePropertyChanged( "PluginNames"). I was expecting that since the WPF databinding model is cognizant of INotifyPropertyChanged, that this is all I needed to do and it would "magically work" and update the combobox items with the plugins that got loaded... but it doesn't.
Can someone please point out what I've done wrong here? Thanks!
UPDATE: I'm not sure why, but now instead of not doing any apparent updating, it's not finding the property at all. I think I'm being really stupid and missing an important step somewhere.
When you're working with INotifyPropertyChanged, there are two things:
You'll need to use properties, not fields
You should always raise the property changed event when you set hte properties.
You'll want to rework this so it looks more like:
private ObservableCollection<string> pluginNames;
public ObservableCollection<string> PluginNames
{
get { return pluginNames; }
set {
this.pluginNames = value;
RaisePropertyChanged("PluginNames"); // This should raise the PropertyChanged event - use whatever your VM class does for this
}
}
That should cause everything to repopulate.
It looks like you've exposed field not property. Bindings works for properties only... Change it to:
public class ViewModel
{
public ObservableCollection<string> PluginNames {get; private set;}
}

WPF datagrid multiple windows question

I have a scenario where i load an ICollectionView in a datagrid.
In some cases I modify the data where the collectionview gets it's data from. If I then reload the grid with configGrid.ItemsSource = configData; for example, the data gets updated.
Now the thing is, I sometimes open a new window using:
var newWindow = new Edit(movie);
newWindow.Show();
The thing is, I also edit the data using this new window. Now I want the datagrid in the first window to be refreshed after I close this second window (actually, it doesn't matter when it gets refreshed, as long as it does).
How do I do this?
I might be missing something here (I have a crippling hangover unfortunately) but can't you handle the window closed event of newWindow and refresh confiGrids itemsource there?
Window newWindow = new Window();
newWindow.Closed += new EventHandler(newWindow_Closed);
newWindow.Show();
void newWindow_Closed(object sender, EventArgs e)
{
configGrid.ItemsSource = configData;
}
If the collection behind the ICollectionView supports INotifyCollectionChanged (like ObservableCollection) and the object itself supports INotifyPropertyChanged then the grid is supposed to update automatically
Otherwise you are on your own and the editing window should raise some sort of notification (maybe an event) that you should receive and update the list.
Ok, here's the long version:
WPF data-binding can update the UI automatically - but it needs to know that something changed in order to trigger the update, the easiest way to do this is to support INotifyPropertyChanged, let's create simple class:
public class Movie
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Now, let's add INotifyPropertyChanged support:
public class Movie : INotifyPropertyChanged
{
public event PropertyChanged;
protected virtual OnPropertyChanged(string property)
{
var ev = PropertyChanged;
if(ev!=null)
{
ev(this, new PropertyChangedEventArgs(property));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
Now when you bind to the movie class and change the Name property the UI will be updated automatically.
The next step is to handle a list of Movie objects, we do that by using a collection class the implements INotifyCollectionChanged, luckily for us there's one already written in the framework called ObservableCollection, you user ObservableCollection<T> the same way you would use a List<T>.
So, just bind to ObservableCollection and WPF will automatically detect when objects change or when they are added or removed.
ICollectionView is very useful, it adds support for current item, sorting, filtering and grouping on top of the real collection, if that collection is an ObservableCollection everything will just work, so the code:
ObservableCollection<Movie> movies = new ObservableCollection<Movie>();
ICollectionView view = CollectionViewSource.GetDefaultView(movies);
will give you a collection view that supports automatic change notifications.

Resources