any help with this would be great.
I have a model
public class Master
{
...
public Detail[] Details {get; set;}
}
I am populating my view model from a WCF service which returns my collection of Master objects. I have configured the service reference to return observablecollection so I can use it easily in my view model.
My view model then has
public ObservableCollection<Master> Masters {get; set;}
public Master SelectedMaster {get; set;}
In my view I have 2 listboxes - one bound to my Masters property and the other bound to SelectedMaster.Details.
This all works fine apart from when I add try to add a new detail to the SelectedMaster.
The collection of Details in the SelectedMaster is just a list of Details (not an ObservableCollection) which is clearly why.
What options do I have here? I have tried implementing INotifyPropertyChanged but that doesn't seem to work. I could have another ObservableCollection for the Details but that means I have to keep this collection in sync when the SelectedMaster is changed (the SelectedMaster property is bound to the SelectedItem on my first listbox.
Hope this comes across OK. Would really love some feedback. Would be ideal if WCF could just return the collection of details as an observablecollection as it does with the collection of Masters but it doesn't seem to work like that.
Thanks.
I don't know enough WCF to say whether it can be configured to return nested collections as other types than simple arrays, but I'll try to give the WPF perspective.
There's no magic here, a simple array doesn't implement any kind of change notification; you simply have to wrap it in some kind of view-aware collection such as ObservableCollection.
Wrap everything up and only interact with the wrapped collections, example:
public class MasterWrapper
{
...
public MasterWrapper(Master master)
{
this.Details = new ObservableCollection<Detail>();
this.Master = master;
foreach (var detail in Master.Details)
this.Details.Add(detail);
}
...
public static ObservableCollection<MasterWrapper> GetMasters()
{
ObservableCollection<MasterWrapper> results =
new ObservableCollection<MasterWrapper>();
List<Master> modelMasters = null; // Populate this from the service.
foreach (var m in modelMasters)
results.Add(new MasterWrapper(m));
return results;
}
You're right that the issue is that the Details property does not have changes notified back to the View, because it is just an array of Detail objects...
I would make it an ObservableCollection<Detail> and load up the collection in Master's constructor, like...
public Master(Detail[] details)
{
Details = new ObservableCollection<Detail>(details);
}
remember that ObservableCollection<T> takes IEnumerable<T> or List<T> as a parameter in its constructor.
Related
I want to bind a list of persons to a DataGrid control.
The objects are loaded on demand, this means that initally I only have a List of IDs (int). In the DataGrid itself I want the objects (of type Person) with all their properties.
For that I used an IValueConverter that converts my List<int> to List<Person>.
I want to bind the SelectedItem to a property int SelectedId. I cannot simply bind to Person, since my Person class has no ID property. But with the value convertion the SelectedItem obviously is of type Person.
Should I initially load the objects in a Dictionary instead and bind that to the ItemsSource. SelectedItem would then be a KeyValuePair.
What other approaches are possible?
IMHO the best way to provide data in a list, is an ObservableCollecion<T>. I'm convinced that this is also the common way, but I've no source to prove it. So it's an IMHO statement.
It implements the INotifyCollectionChanged interface, so that the view is noticed about any changes (adding and removal of items). For more information pls refer here. If the item in the collection, in your case Person, is implementing INotifyPropertyChanged, the view will also be notified regarding changes of the items itself. Please have a look here for more information.
So if I where you, I would put an id property into Person and load the whole collection into an ObservableCollection. If you cannot modify Person you could create a wrapper like
public class PersonVm
{
public int Id { get; set; }
public Person Person { get; set; }
public PersonVm(int id, Person person)
{
Id = id;
Person = person;
}
}
You will have to add the implementation of INotifyPropertyChanged by yourself, if you want to have the notification feature of the items.
Btw, what I described here is a typical implementation of the MVVM pattern. It has definitely many advantages to implement a WPF application with it, but there are some disadvantages too. E.g. there is an overhead which may not be worth the effort in smaller projects. You can get an overview here.
I have a WPF app that uses a business object called Visit, it has a lot of child objects like patient, exam, and others. There are views and view models for editing these various child objects, so there is a view and view model for editing patient info and one set for exam info, etc. There is also a main window view model.
When I need to open a new Visit, I have a search screen that also has it's own view model. It needs to open new visit from the database and notify all the other views that the visit has changed.
I've looked into WeakEventManager, and also having one view model that is the parent of all the others, but I'm not sure what is the best way to proceed. I'd like to know what the relationship between the view models should be and how the open/search view model should tell all the other views to update. I have been calling OnPropertyChanged("propname") in my view models when a property is updated, but since the other views don't know about the open/search view model, they don't care if I say OnPropertyChanged("Visit")
Have a look at this post on SO that talks about the Messenger. In your case you case post the Visit object and have the ViewModel capture that for display.
If you have a very data centric views where the Model data is pretty much presented directly on the View without a lot of modification you can easily expose the Model as a property on the ViewModel and have the View bind to its properties.
This way when one View updates the Model the other View's will update automatically without having to listen for property change events on the Model
Edit:
To elaborate on my second point: You may or may not need this but if your Model also implements INotifyPropertyChanged then any changes to that model will be propagated to the View automatically.
If you need to have 2 Views with the Visit object then you can have the Visit property directly bound in XAML
public class ViewModel1 : ViewModelBase
{
public ViewModel1(IMessenger messenger)
{
messenger.Register<Visit>(this, (v) => CurrentVisit = v);
}
public Visit CurrentVisit
{
get { _visit; }
set { _visit = value; RaiseNotifyPropertyChange("CurrentVisit"); }
}
}
public class ViewModel2: ViewModelBase
{
public ViewModel2(IMessenger messenger)
{
messenger.Register<Visit>(this, (v) => CurrentVisit = v);
}
public Visit CurrentVisit
{
get { _visit; }
set { _visit = value; RaiseNotifyPropertyChange("CurrentVisit"); }
}
}
public class CurrentVisit : INotifyPropertyChanged
{ ... }
Like I said this is only applicable if you need the same Visit object shared amounst 2 or more ViewModels and if the View's mostly are data centric or in other words the data from the Model is being presented directly on the screen. This is to avoid duplicating the properties in the ViewModels and having to raise the property change event all the time.
use global variable. simply use a Property in your base ViewModel class whose set part set the global variable and get part return that variable value... for setting that global variable create a class in other common project and create a static public variable or property.
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.
I have a problem understanding how to build view models based on the following models
(I simplified the models to be clearer)
public class Hit
{
public bool On { get; set;}
public Track Track { get; set; }
}
public class Track
{
public ObservableCollection<Hit> Hits { get; set; }
public LinearGradientBrush Color { get; set; }
public Pattern Pattern { get; set; }
}
public class Pattern
{
public string Name { get; set; }
public ObservableCollection<Tracks> Tracks { get; set; }
}
Now, my problem is, how to build the ViewModels..
I need to keep the original relationships through the models, beacaus i have a Serialize() method on the Pattern that serializes it to an XML file.. (with the related Tracks and Hits)
To be able to bind the pattern to the user controls and it's nested templates I should also have a PatternViewModel with an ObservableCollection<TrackViewModel> in it, same thing for the TrackViewModel and the HitViewModel.. and i neet to have custom presentation properties on the view models that aren't part of the business object (colors and more..)
It just seem not a good thing to me to duplicate all of the relationships of the models on the view models...
and keeping track of all this relations while coding the viewmodels is also much more error prone..
anyone has a better approach/solution?
One thing I've done, with some success, is to move the ObservableCollection out of the model. Here's my general pattern:
In the model objects, expose a property of type IEnumerable<TModel> that gives read-only access to the collection. Use a plain old List<TModel>, not an ObservableCollection, as the backing collection.
For code that needs to mutate the models' collections (add, delete, etc.), add methods to the model object. Don't have outside code directly manipulating the collection; encapsulate that inside methods on the model.
Add events to the model for each type of change you allow. For example, if your model only supports adding items to the end of the collection, and deleting items, then you would need an ItemAdded event and an ItemDeleted event. Create an EventArgs descendant that gives information about the item that was added. Fire these events from the mutation methods.
In your ViewModel, have an ObservableCollection<TNestedViewModel>.
Have the ViewModel hook the events on the model. Whenever the model says an item was added, instantiate a ViewModel and add it to the ViewModel's ObservableCollection. Whenever the model says an item was deleted, iterate the ObservableCollection, find the corresponding ViewModel, and remove it.
Apart from the event handlers, make sure all of the collection-mutation code is done via the model -- treat the ViewModel's ObservableCollection as strictly something for the view's consumption, not something you use in code.
This makes for a lot of duplicate code for each different ViewModel, but it's the best I've been able to come up with. It does at least scale based on the complexity you need -- if you have a collection that's add-only, you don't have to write much code; if you have a collection that supports arbitrary reordering, inserts, sorting, etc., it's much more work.
I ended up using part of the solution that Joe White suggested, in a slighty differ manner
The solution was to just leave the models as they were at the beginning, and attaching to the collections an eventhandler for CollectionChanged of the inner collections, for example, the PatternViewModel would be:
public class PatternViewModel : ISerializable
{
public Pattern Pattern { get; set; }
public ObservableCollection<TrackViewModel> Tracks { get; set; }
public PatternViewModel(string name)
{
Pattern = new Pattern(name);
Tracks = new ObservableCollection<TrackViewModel>();
Pattern.Tracks.CollectionChanged += new NotifyCollectionChangedEventHandler(Tracks_CollectionChanged);
}
void Tracks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (Track track in e.NewItems)
{
var position = Pattern.Tracks.IndexOf((Track) e.NewItems[0]);
Tracks.Insert(position,new TrackViewModel(track, this));
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (Track track in e.OldItems)
Tracks.Remove(Tracks.First(t => t.Track == track));
break;
case NotifyCollectionChangedAction.Move:
for (int k = 0; k < e.NewItems.Count; k++)
{
var oldPosition = Tracks.IndexOf(Tracks.First(t => t.Track == e.OldItems[k]));
var newPosition = Pattern.Tracks.IndexOf((Track) e.NewItems[k]);
Tracks.Move(oldPosition, newPosition);
}
break;
}
}
}
So i can attach the new Color/Style/Command on the view models to keep my base models clean
And whenever I add/remove/move items in the base models collection, the view models collections remain in sync with each other
Luckily I don't have to manage lots of object in my application, so duplicated data and performance won't be a problem
I don't like it too much, but it works well, and it's not a huge amount of work, just an event handler for the view model that contains others view model collections (in my case, one for PatternViewModel to sync TrackViewModels and another on TrackViewModel to manage HitViewModels)
Still interested in your thoughs or better ideas =)
I think I had the same problem and if you do it like "PatternViewModel with an ObservableCollection<TrackViewModel>" you also get a massive impact on your performance because you start duplicating data.
My approach was to build - for your expample - a PatternViewModel with a ObservableCollection<Track>. It's no contradiction to MVVM because the view is bound to the collection.
This way you may avoid the duplication of the relationships.
One solution I've been considering, although I'm not sure if it would work perfectly in practice, is to use converters to create a viewmodel around your model.
So in your case, you could bind Tracks directly to (as an example) a listbox, with a converter that creates a new TrackViewModel from the Track. All your control would ever see would be a TrackViewModel object, and all your models will ever see is other models.
I'm not sure about the dynamic updating of this idea though, I've not tried it out yet.
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.