WPF MVVM update collections so that UI updates - wpf

I want to update my UI. Should I use BackgroundWorker? Do I put the BackgroundWorker in the MainWindowViewModel and instantiate the repositories again, or do I put it in the OrdersQueueViewModel and do something with the properties?
The UI just displays the contents of lists created by LINQ. The lists are ObservableCollection and are properties of the OrdersQueueViewModel. I have a ViewModel MainWindowViewModel that creates a collection ViewModels, so that I can bind to that collection from the MainWindow.xaml (view).
MainWindowViewModel.cs:
public MainWindowViewModel()
{
_printQueueRepos = new OrdersPrintQueueRepository();
_holdQueueRepos = new OrdersHoldQueueRepository();
_linesToPickRepos = new LinesToPickRepository();
_linesPerHourRepos = new LinesPerHourRepository();
//create an instance of viewmodel and add it to the collection
OrdersQueueViewModel viewModel = new OrdersQueueViewModel(_printQueueRepos, _holdQueueRepos, _linesToPickRepos, _linesPerHourRepos);
this.ViewModels.Add(viewModel);
}
MainWindow.xaml:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:OrdersQueueViewModel}">
<vw:OrdersQueueView></vw:OrdersQueueView>
</DataTemplate>
</Window.Resources>
Example of a property in the OrderQueueViewModel that uses a repository:
public ObservableCollection<LinesToPick> LinesToPick
{
get
{
return new ObservableCollection<LinesToPick>(_linesToPickRepos.GetLinesToPick());
}
}
So I haveLinesToPick bound in the OrdersQueueView, and as the database updates the lists should change in the UI. I'v spent some time reading about BackgroundWorker, but I'm not quite sure what to do to update the lists. I'm hoping because they are ObservableCollections I can just "refresh" them and they will use INotifyPropertyChanged and update the UI automatically. Very new to all this, trying to get my head around it, thanks in advance for any help.
EDIT: Using James's suggestion I have ended up with this In my OrdersQueueViewModel. However I am getting the error "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread", when the code gets to .Clear() on the 2 lists, which is what I thought the dispatcher was used for. Any suggestions?
Action workAction = delegate
{
_worker = new BackgroundWorker();
_worker.DoWork += delegate
{
LinesThroughput.Clear();
LinesToPick.Clear();
//refresh LinesToPick
foreach (var item in _linesToPickRepos.GetLinesToPick())
{
LinesToPick.Add(item);
}
//refresh LinesThroughput
List<LinesThroughput> Lines = new List<LinesThroughput> (_linesPerHourRepos.GetLinesThroughput());
foreach (var item in GetLinesThroughput(Lines))
{
LinesThroughput.Add(item);
}
};
_worker.RunWorkerAsync();
};
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, workAction);

You can do it either way - in the MainWindowViewModel or one of the child view models. I would choose based on which way produces lower coupling and higher cohesion between components. (Lower coupling - fewer dependencies. Higher cohesion - things go together that belong logically together.)
And BackgroundWorker is a reasonable technique. Just remember to dispatch to the UI thread to update the collection. As for your ObservableCollection code... That needs some work. Don't reinstantiate the ObservableCollection. Do something like this:
public ObservableCollection<LinesToPick> LinesToPick { get; private set; } // Don't forget to nstantiate in ctor
public void Refresh()
{
LinesToPick.Clear();
foreach(var item in _linesToPickRepos.GetLinesToPick())
{
LinesToPick.Add(item);
}
}
By keeping the same ObservableCollection that was databound, your UI will automatically pick up changes to the collection. If you replace the collection, you lose the binding to it and your UI won't update until you notify it that the property containing the collection changed. Much easier to just keep the same collection.

Related

Implementing INotifyPropertyChanged with ObservableCollection

I want to pull data from a database to display into a ComboBox, and then allow users to select values from that ComboBox and add them into a ListBox (via add/remove buttons). Would I be able to get away with using an ObservableCollections to hold the database values to bind to the ComboBox, since it implements INotifyPropertyChanged (and CollectionChanged)? Sorry if this is a basic question, I starting learning WPF about a month ago.
I've read over the article (very well done) by Sacha Barber.
And I've looked over the MSDN page on ObservableCollection.
What would be the advantages/disadvantages of using an ObservableCollection vs a List (which I know does not implement INotifyPropertyChanged)?
Something you may want to note.
Don't confuse the ObservableCollection's implementation of INotifyPropertyChanged with the objects it contain's implementation.
If one of the properties of one of the objects within the ObservableCollection changes, the UI will not reflect it unless that object implements INotifyPropertyChanged as well. Do not expect the ObservableCollection to take care of this for you.
If the items in your combobox don't change (i.e. you don't add/remove/update items), then List will probably be OK for your needs (ObservableCollection will be too) if you manually notify that your List property changed when you affect it.
public List<X> MyList
{
get
{
...
}
set
{
if (... != value)
{
... = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}
}
}
....
this.MyList = new List<X> { new X(...), new X(...) };
If you plan to add/remove or update items in your combobox (without creating a new MyList object, i.e. using this.MyList.Add(...)), then use ObservableCollection that is able to notify when the collection is updated (so it can update bindings).

WPF DataGrid bound to an ObservableCollection that is updated on separate thread fails to maintain sort

Download Sample Project
I have a wpf 4 datagrid that is bound to an ObservableCollection. This ObservableCollection is
updated on another thread. I can see the updates coming through to my simple gui just fine. I can even sort the data. But the sort does not "stick". It will sort once when you click the column header but when a value in the collection changes the sort does not change accordingly.
The MainWindow backing code is where most of the action goes down (just for simplicity of the example). I create an ObservableCollection and pass it to a thread that does the actual writes to the ObservableCollection. I then bind that same ObservableCollection to the datagrid via a CollectionView (I've tried binding it directly as well). My hunch is that the sorting depends upon the collectionChanged event which I'm pretty sure won't fire back to the Dispatcher ( see: http://bea.stollnitz.com/blog/?p=34).
What to do?
public partial class MainWindow : Window
{
private Thread _dataThread;
private Thread _marketThread;
private SampleData _sampleData;
private Market _market;
private ObservableCollection<Stock> stocks;
private ConcurrentQueue<Stock> _updates = new ConcurrentQueue<Stock>();
public MainWindow()
{
InitializeComponent();
stocks = new ObservableCollection<Stock>();
for (var i = 0; i < 5; i++)
{
var newStock = new Stock();
newStock.Id = (uint)i;
stocks.Add(newStock);
}
var source = CollectionViewSource.GetDefaultView(stocks);
dataGrid.ItemsSource = source;
_sampleData = new SampleData(_updates);
_dataThread = new Thread(_sampleData.CreateData) { Name = "Data Thread" };
_dataThread.Start();
_market = new Market(_updates, stocks);
_marketThread = new Thread(_market.Start){Name = "Market Thread"};
_marketThread.Start();
}
}
Challenge.
Download Sample Project
Have you looked at ObjectDataProvider IsAsynchonous="True" and bind in XAML? You might be able to not thread the collection creation. I have no experience how DataGrid sorts behave behind IsAsynchonous="True".
<ObjectDataProvider IsAsynchonous="True" ...>

Re-establishing WPF databinding after changing property value

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

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.

Inserting a new object into L2S table and databinding to it prior to SubmitChanges() in WPF

I'm just getting started with Linq-to-SQL and data binding in WPF, most of which works like a dream so far!
I've got (what I though was) a common scenario:
a) Query list of records from a table via datacontext and bind to the current user control
this.DataContext = db.ClientTypes;
b) Have the user see a bound ListView and some bound detail controls to make changes to the existing records, with a db.SubmitChanges(ConflictMode.FailOnFirstConflict); to push the changes back to the DB. No problem.
c) User wants to add a new record, so we:
ClientType ct = new ClientType();
ct.Description = "<new client type>";
db.ClientTypes.InsertOnSubmit(ct);
However at this point I dont want to call db.SubmitChanges as I want the user to be able to update the properties of the object (and even back out of the operation entirely), but I want them to be able to see the new record in the bound ListView control. Thinking I just needed to re-run the query:
ClientType ct = new ClientType();
ct.Description = "<new client type>";
db.ClientTypes.InsertOnSubmit(ct);
// Rebind the WPF list?
this.DataContext = db.ClientTypes;
listView1.SelectedItem = ct;
listView1.ScrollIntoView(ct);
However this doesn't work, the newly created record is not part of the returned list. I'm not sure if this is because of caching within L2S or if I'm just going about this the wrong way. Is there a better way to accomplish this?
Thanks.
Instead of setting your Control.DataContext = db.ClientTypes, store db.ClientTypes somewhere else and bind to an ObservableCollection that wraps it.
var somewhereElse = db.ClientTypes;
var toBind = new ObservableCollection<ClientType>(somewhereElse);
toBind.CollectionChanged += (object sender, NotifyCollectionChangedEventArgs e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
types.InsertAllOnSubmit<AddressType>(e.NewItems.Cast<AddressType>());
};
this.DataContext = toBind;
Then, when the user wants to add a new item:
ObservableCollection<ClientType> toBind = this.DataContext as ObservableCollection<ClientType>;
System.Diagnostics.Debug.Assert(toBind != null);
ClientType ct = new ClientType();
ct.Description = "<new client type>";
toBind.Add((ct);
Calling toBind.Add will cause the CollectionChanged event handler above to call InsertOnSubmit on the original Table instance, so you can call SubmitChanges() when convenient. Obviously, you'd probably want to do the same with Remove ...
Hope that helps :)
It may be worth looking into the MVVM pattern. In MVVM you have a ViewModel which wraps your Model, so you would have a ClientTypeViewModel class.
public class ClientTypeViewModel : INotifyProperyChanged
{
public ClientTypeViewModel(ClientType dataModel)
{
this.dataModel = dataModel;
}
public string Description
{
get { return this.dataModel.Description; }
set
{
this.dataModel.Description = value;
// Raise PropertyChanged event
}
}
private ClientType dataModel;
}
And something like an ApplicationView model, which would contain an ObservableCollection of ClientTypeViewModels.
public ApplicationViewModel
{
public ObservableCollection<ClientTypeViewModel> ClientTypes { get; private set; }
}
You then bind to ApplicationViewModel.ClientTypes instead of the plain data model. This way, your view will be automatically updated whenever a new item is added to ClientTypes, or a property is changed on the ClientType view model. ApplicationViewModel can listen for changes on the ClientTypes collection and automatically add newly added items to the DataContext.
You may think it's overkill for your application, I don't know - but MVVM is definitely somthing worth learning. If it feels like you're struglling or fighting with WPF, MVVM is likely where to look ;)
Look at CreateBindingList.
I think it's just because you're assigning the same reference to the DataContext. Hence, WPF doesn't see the need to refresh the binding. The easiest way around this is to:
// rebind
this.DataContext = null;
this.DataContext = db.ClientTypes;

Resources