I have this WPF/MVVM Application that a TabControl with a bunch of tabs. When the app loads, data for all tabs are loaded. There are some calculations that are made on Tab1 that's dependent on values from Tab2. what's happening is, when I enter / change data on tab2, it simply doesn't reflect on Tab1 when I click on Tab1. under the hood, the calculations are made properly but it doesn't reflect on tab1. I have to go to the main tab to re-load all the data to reflect changes. Any ideas how to implement this?
You need to implement INotifyPropertyChanged on your view model data properties. Then, have your view model subscribe to the event (the Initialize() method is called by the view model constructor):
private void Initialize()
{
// Subscribe to events
this.PropertyChanging += OnPropertyChanging;
this.PropertyChanged += OnPropertyChanged;
this.Books.CollectionChanging += OnBooksCollectionChanging;
}
The view model handler for the event can then update any properties that need to be updated:
void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch(e.PropertyName)
{
case "FirstProperty":
this.SomeOtheProperty = whatever;
break;
case "Another property":
this.YetAnotherProperty = somethingElse;
break;
}
}
That should get the job done.
If two different ViewModels need to show the same data/value they should bind to the same ViewModel.
I think that adding a binding between ViewModels is bad because it introduces a lot of dependencies.
If a property of a single ViewModel is dependent on an other property of the same ViewModel you could use property changed notification as mentioned in David's answer.
Related
I'm very new to WPF and MVVM, and it's been causing me a lot of headaches. Due to issues with navigation, I decided to just have all my content visible at once. I thought I would create a new ViewModel (MainViewModel) to contain my two other ViewModels (StudentViewModel and AddStudentsViewModel).
MainViewModel contains something like this:
private StudentViewModel _studentVM;
private AddStudentsViewModel _addStudentsVM;
public StudentViewModel StudentVM
{
get { return _studentVM; }
set
{
if (_studentVM != value)
{
_studentVM = value;
NotifyPropertyChanged("StudentVM");
}
}
}
(public AddStudentsViewModel AddStudentsVM exists as well, I'm just trying to keep this short)
I have successfully bound StudentVM and AddStudentsVM to my main View, as I can programmatically set values during the initialization phase and when debugging, I can see my button clicks are being redirected to the correct methods. It even seems like I am successfully adding students to objects, however my main View isn't reflecting these changes.
Am I missing something in MainViewModel? Or is it not possible for a ViewModel to see the changes in any other ViewModels inside it?
If your view is bound to some property inside StudentViewModel through nested navigation into the property StudentVM like StudentVM.Property, then to reflect changes on StudentVM.Property unto your view would require that you notify the view that StudentVM (not StudentVM.Property) has changed.
So it depends on how you defined your binding and on which property you're raising the PropertyChanged event.
A viewmodel which contains two other viewmodels. Just think about it for a second. It's not a good idea.
Anyway, you have to implement INotifyPropertyChanged in your containing viewmodels as well. Not only in the containing MainViewModel.
Maybe that is the fault?
My viewmodel has two Collections, one is MainCollection and other is DerivedCollection. They are displayed using a control, so that when user interacts with the mouse, items can be added or removed from MainCollection, and DerivedCollection should be refreshed accordingly.
The first part (updating MainCollection) happens automatically via data-binding, but I don' know how can I hook RefreshDerivedCollection method to MainCollection.PropertyChanged event.
Both collections and the method live in the same viewmodel.
You can subscribe to MainCollection.CollectionChanged and refresh derived collection there:
MainCollection.CollectionChanged += this.OnMainCollectionChanged;
and
void OnMainCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// TODO: Handle main collection change here.
}
I am just getting familiar with WPF databinding. I've figured out most of the basics but I'm having trouble figuring out a couple of things.
First, let's say I have an object called Synth that has a collection of Banks. In turn, a Bank has a collection of Patches. I have a synth window to which I set the DataContext to a single Synth object. I have one listbox (lstBanks) that shows all the banks ({Binding Banks}) and another (lstPatches) that shows all the patches ({Binding ElementName=lstBanks, Path=SelectedItem.Patches}). This all works great. I see the applicable patches when I select a bank.
Question 1: How can I load a selected Patch into a dialog window with two-way binding, yet cancel those changes if DialogResult = false?
Right now, I have a patch dialog that receives a patch in the constructor which it sets as its DataContext, but I am only using OneWay binding. This happens on the doubleclick of lstPatches.
private void Patch_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
Models.Patch patch = (Models.Patch)((ListBoxItem)sender).DataContext;
PatchEdit p = new PatchEdit(patch);
p.Owner = this;
if (p.ShowDialog().GetValueOrDefault())
{
// Do stuff if applicable
}
}
Here is my PatchEdit constructor and OK button event:
public PatchEdit(Models.Patch Patch) : this()
{
this.DataContext = Patch;
}
private void btnOK_Click(object sender, RoutedEventArgs e)
{
Models.Patch p = (Models.Patch)DataContext;
p.Name = txtName.Text;
p.MidiProgramChangeValue = int.Parse(txtPCN.Text);
this.DialogResult = true;
this.Close();
}
If the user clicks OK on the patch dialog, that's when I set the properties from the form back to the DataContext. I wasn't sure if this was the best way to do it. I don't want to really save the changes until the user clicks OK on the main synth window. So all bank and patch edits should only remain local, and only be "locally" committed if the user clicks OK and not Cancel on the dialog.
Question 2: Once a patch is updated via the dialog, how can I get that change reflected in lstPatches?
I understand that directly navigating my models which are essential of type DBSet aren't Observable. I've seen posts regarding using an Observable collection, but doesn't this just complicate something that is supposed to be easy with WPF databinding? If it's the only way, how do I accomplish this easily using my code first models?
Question 1: Bind to a second/temporary object. If the user cancels, throw it away. If they don't, use it to update your original object. Data-binding doesn't really offer an "undo" or "reset" method.
Question 2: No, using ObservableCollection's doesn't complicate things. It is the recommended way of doing things. It is actually much harder to work without them.
In my Silverlight app I have a view containing a tab control and a view model of this view.
When the selected tab is changed, I need to refresh its data. In order to do that in the view model I'm using a command triggered by EventTrigger in the view and passing the appropriate event args to it (as described here http://weblogs.asp.net/alexeyzakharov/archive/2010/03/24/silverlight-commands-hacks-passing-eventargs-as-commandparameter-to-delegatecommand-triggered-by-eventtrigger.aspx).
Each tab item has its own view model and, therefore, to distinguish which view model I have to use to refresh the data, I'm watching the header in the tab item which I can get from the event args, e.g:
_tabSelectionChangedCommand = new DelegateCommand<SelectionChangedEventArgs>(TabSelectionChanged);
public ICommand TabSelectionChangedCommand
{
get { return _tabSelectionChangedCommand; }
}
private void TabSelectionChanged(SelectionChangedEventArgs e)
{
var tabItem = (TabItem)e.e.AddedItems[0];
if (tabItem.Header == "Header1" )
{
TabItem1ViewModel.Refresh();
}
.....
}
So, my question is :
Is good that I'm using in the view model the types related to the UI(TabItem, SelectionChangedEventArgs) and are there better ways to do what I've described above?
Maybe you can bind the SelectedIndex of the TabControl to a property defined in your viewmodel and attach an InvokeActionCommand to the TabControl and subscrible to its SelectionChanged event.
Then when the command gets called, check which index it is then load the data accordingly?
In short my question is: How do you prefer to expose filtered/sorted/grouped ObservableCollections to Views in WAF?
I was fairly happy with my first attempt which involved filtering on the VM and exposing an ICollectionView of Model objects for the View to bind to:
public StartDetailViewModel(IStartDetailView view, StartPoint start, Scenario scenario)
: base(view)
{
this.scenario = scenario;
this.start = start;
this.startsViewSource = new CollectionViewSource();
this.startsViewSource.Filter += new FilterEventHandler(Starts_Filter);
this.startsViewSource.Source = scenario.Starts;
}
public ICollectionView FilteredStarts
{
get
{
return startsViewSource.View;
}
}
void Starts_Filter(object sender, FilterEventArgs e)
{
if (e.Item != null)
{
e.Accepted = (((StartPoint)e.Item).Date == this.start);
}
}
}
However, exposing the Model objects directly is insufficient since each item now needs its own ViewModel.
So, CollectionViewSource.Source is now attached to a collection of Views. The main problem with this is when applying filters:
void Starts_Filter(object sender, FilterEventArgs e)
{
//Since e.Item is now a view we are forced to ask the View for the ViewModel:
StartItemViewModel vm = ((IStartItemView)e.Item).GetViewModel<StartItemViewModel>();
[...]
}
This feels wrong to me. Are there better approaches?
UPDATE
So I reverted to a CollectionViewSource.Source of Model objects and maintained a seperate collection of child View objects to which the View was bound.
The question then of course is why am I using CollectionViewSource in a ViewModel at all?
I think the following prinicple applies: If the filtering/sorting functionality is a property of the View only (i.e. an alternate view might legitimately not provide such functionality) then CollectionViews should be used in the View (with code-behind as necessary). If the filtering/sorting functionality is a dimension of the Model then this can be dealt with in the ViewModel or Model by other means.
This makes sense once you realise that code-behind in MVVM views is perfectly acceptable.
Any comments?
I think the real benefit of CollectionView lies in when you are in need of reporting information as you step through collectionview items one by one. In this way you are able to utilize the CurrentPosition property and MoveCurrentToNext (/etc.) methods which may be desireable. I particularly like the idea of being able to report PropertyChanged notifications in MVVM when item properties in the collection changed/items are added/removed/changed.
I think it just makes a bit more sense to use in controls that require more complex notifications (such as datagrid, where you may want to raise PropertyChanged events and save to your datastore each time the selectionchanges or a new item is addd to the control).
I hope that makes sense. That is just what I am putting together as a beginner.
Also, I really don't think anything should go in the code-behind of a view except a datacontext and the shared data you may be feeding it from a viewmodel.