MVVM and window lists - wpf

I'm writing an app that lets users browse through data, and I want to use the FireFox UI style: allow the user to open as many windows as they want, each with as many tabs as they want. I also want to try to do this using the Model-View-ViewModel pattern as much as possible.
Opening a new tab should be easy enough to handle in MVVM. Make an ObservableCollection of TabViewModel, bind that collection to the ItemsSource of a TabControl, and then opening a new tab is theoretically as easy as adding a new TabViewModel to the collection.
Here's the question that interests me: Is there a way to do the same thing for opening a new window? I.e., databind an ObservableCollection of WindowViewModel to the ItemsSource of... the Application's Windows collection?... so that when I add a new WindowViewModel to the observable collection, a new window automatically opens? And then tie that into app startup, so that instead of setting StartupUri, I just add the first WindowViewModel to the collection?
Since I can't actually databind Application.Windows, what would be the best way for the ViewModel layer to:
Add a new WindowViewModel and have a new Window appear automatically;
Remove the WindowViewModel and have its Window automatically close.
Remove the WindowViewModel from the collection if the user closes the window.
I could write my own object that watches an INotifyCollectionChanged and opens/closes windows in response to collection events, but I'm not sure whether that's the best way -- and if it is, I'm not sure of the best way to hook it into the application. Anyone have thoughts on the best way to go about this?

The point of MVVM is, that the ViewModel doesn't have to concern itself (in detail) with how the View will react to changes in the ViewModel.
One possibility would be a simple tracking algorithm in the View listening to the CollectionChanged event in the ViewModel, creating and destroying Windows on the go:
Dictionary<WindowModel, WindowView> _cache = new;
void WindowModelListChangedHandler(sender, args) {
switch(args.Action) {
case Add:
_cache[args.NewItem] = new WindowView(args.NewItem);
_cache[args.NewItem].Show();
break;
case Remove:
// ...
}
}

Related

Update a DataGrid in MVVM

I have a DataGrid displaying rows from an Entity Framework Code First context. I am not sure about the best method to update automatically my DataGrid.
I am opening a new window where the user can add a new Costumer, once the user fills the form and clicks on the Save button, the window's ViewModel will add the Costumer to the Business Context, but the DataGrid displaying the Costumers is in the Main Window, controlled by the Main ViewModel.
I am not so sure about the best way to make the DataGrid show the recently added Costumer. I can only think about these ways:
I will create an ObservableCollection<Costumer> (as the DataGrid Data Binding Source) in the Main ViewModel and pass it to the Add New Costumer ViewModel's Constructor when the Main ViewModel creates this new window, so the new window's ViewModel can add the new Costumer in a new instance of the Business Context and add the Costumer to the ObservableCollection as well.
I can create a Business Context with an ObservableCollection<Costumer> inside it and update this collection every time it runs an Add Costumer method. The Business Context will have to be a Singleton throughout the entire app, being passed around of all the ViewModels and new windows the Main ViewModel creates.
I can make an Update Action on the Main ViewModel and invoke this Action from every New Window ViewModel. This Action will run a method to update the ObservableCollection<Costumer> in the Main ViewModel from the Business Context, thus updating the DataGrid.
I can make an Action or Func for every CRUD operation the app has to do on the Main ViewModel so every new ViewModel just invoke's it and the Main ViewModel will save it and update all references needed to the UI.
Are there any other better ways to accomplish this? I have seen some good examples of MVVM with Entity Framework Code First, but none of them have a logic that spans several ViewModels at the same time as mine does.
I am inclined to go with invoking Func in the Main ViewModel for every operation, I think this way is more clean of dependencies between ViewModels and all the data operations are limited to one class.
This should be pretty easy to achieve using the MVVM Light framework. MVVM Light uses a class called 'ViewModelLocator' to instantiate and track all of your ViewModels. For your scenario I'd have:
ViewModels:
MainViewModel
AddCostumerViewModel
Views:
MainView
AddCostumerView
The MainViewModel holds an ObservableCollection<Costumer>.
The AddCostumerViewModel holds a Costumer.
You bind controls on AddCostumerView to the properties of the Costumer in AddCostumerViewModel. You have a Button with a CommandBinding to a method in AddCostumerViewModel which includes something like:
_viewModelLocator.MainViewModel.Costumers.Add(newCostumer);
Your Costumer is added to the ObservableCollection<Costumer> in MainViewModel.
When the new Costumer is added to your ObservableCollection, NotifyPropertyChanged fires and your DataGrid updates.

wpf how to add different commands to keybindings in a control based on datacontext of item

Originally I added to the input bindings of the actual treeviewitem on the loaded event like this:
void MultiSelectTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
this.InputBindings.Add(new KeyBinding(ApplicationCommands.SelectAll, new KeyGesture(Key.A, ModifierKeys.Control)));
SignalViewModel svm = this.DataContext as SignalViewModel;
if (svm != null)
{
this.InputBindings.Add(new KeyBinding(DaedalusGraphViewer.GraphViewer.AddSignalsCommand, new KeyGesture(Key.Enter)));
}
BitViewModel bvm = this.DataContext as BitViewModel;
if (bvm != null)
{
this.InputBindings.Add(new KeyBinding(DaedalusGraphViewer.GraphViewer.OpenNewBusDialogCommand, new KeyGesture(Key.Enter)));
}
}
the problem with this is that if I want to use the treeviewitem elsewhere and have different commands, I would have to make a new treeviewitem class each time. I am definitely violating MVVM here and coupling too much so I want to fix this old code (been trying to adopt mvvm practices lately, but there is still a lot of old stuff that I didn't do correctly).
What I would like is a way to attach the keybinding in xaml, but I am still a little hazy on what determines whether a keybinding works. is a keybinding only available if the item on which the keybinding exists is in focus? can you access parent keybindings or child keybindings of the currently focused item? Based on my testing it looked like you can't access keybindings from the parent and I read another stackoverflow post that made it seem like you can't access children either. Is that the case?
I just need a nice customizeable way to set keybindings on different tiers of a treeview that is templateable so that I don't have to make new classes every time.
edit:
it occurs to me that one possible solution is to attach the input bindings to the textbox at each tier with a different command for each, then stretch the textbox to fill the item and put the focus on the textbox instead of the treeview. is that the best option? ideally I kind of wish hierarchicaldatatemplate had input bindings or something.

Setting DataContext in Catel with WPF

So I've started looking at the Catel MVVM framework and it looks like it will solve a couple of problems I have encountered, but I have one really silly issue. I think I'm just missing something small.
I took one of my smaller WPF projects to switch over the Catel as a way for me to learn it. I have a simple 'Player Registration' form, with fields like name and surname. I recreated my original view model by using the vm codesnippet and all is good, all the properties and attributes I've set up as I've read in the documentation.
I then changed the UserControl I used for 'Player Registration' (PlayerRegistrationView) to a catel:UserControl. I placed PlayerRegistrationView on a standard WPF Window (nothing else, just a xmlns for the View and the view as the only content on the window, no attributes)
But here is my problem:
I have a MainWindow with a button on to open the Window for the player registration. The on-click event simply is this:
private void ButtonPlayerClick(object sender, RoutedEventArgs e)
{
var playerRegistration = new PlayerRegistrationDialog
{
Owner = this,
DataContext = new PlayerRegistrationViewModel(),
};
playerRegistration.Show();
}
Running my program and then clicking on the button results in an NotSupportedException on my PlayerRegistrationView: The view model of the view could not be resolved. Use either the GetViewModelType() method or IViewModelLocator
I tried making the ViewModel a static resource on the window and setting the datacontext there, but it produces the same error.
I am at a loss. What have I missed?
Thanks
The whole point of Catel is that it automatically wires up all the views and view models. The "complex" thing that you are trying to achieve is that you have a view which is placed on a window. You want the window to have the same data context as the view in order to do some stuff in the window as well.
In Catel, it is possible to place any view with datacontext management on a DataWindow (window in Catel). Then it will work like this:
DataWindow
|=> View
If the DataWindow and the View share the same view model type, then they share the same view model. For example:
PlayerRegistrationWindow => derives from catel:DataWindow
PlayerRegistrationView => derives from catel:UserControl
Since both start with PlayerRegistration, they will both be resolved to PlayerRegistrationViewModel automatically.
To show the window, the only thing you have to do is this:
var viewModel = new PlayerRegistrationViewModel();
var uiVisualizerService = ServiceLocator.Default.ResolveType<IUIVisualizerService>();
uiVisualizerService.Show(viewModel);
All will work automatically and you don't have to worry about setting any datacontext yourself.

Using MVVM show new window and get updates data

I'm working on a WPF MVVM application. I'm showing some data in a datagrid. I've two buttons to Add and Edit the selected record. I've data in ViewModel and I've to show another window (view) and make sure that ViewModels should have no information about views.
Where should I create its view and viewmodel?
How to get the data back and update datagrid?
How can I achieve this in MVVM?
We have not yet decided to use any framework, so I've to create my own interface.
Note: This ended up being quite a long answer - please ask me if anything is unclear
The implementation of dialog windows is a contentious issue in MVVM designs, and different people use different approaches.
Like you, I've decided not to use any framework and implement most things by hand. When it comes to dialog windows, I choose to be pragmatic about my implementation of MVVM, by launching the Dialog Window from inside my ViewModel. Also, I allow each Dialog ViewModel to have a reference to the Window it is displayed in, so it can close it when appropriate (details below). This breaks some of the strict MVVM "rules", but it gets the job done.
The main downside of this is that it might break unit testing if you are testing something that goes through a dialog. However, you can go a long way without running into that problem and it has not bothered me yet.
I've built up a bit of a library of dialog ViewModels which I can easily extend. It's way too much code to post here, but I'll show you the highlights.
Base ViewModel for Dialogs
Each of my dialog windows has a ViewModel that inherits from DialogViewModelBase, which is similiar to my regular ViewModelBase in that it provides support for INotifyPropertyChanged etc. The interesting part is this public method, which I call from wherever to launch the Dialog:
/// <summary>
/// Creates window instance for this dialog viewmodel and displays it, getting the dialog result.
/// </summary>
public void ShowDialogWindow()
{
// This is a property of the DialogViewModelBase class - thus, each DialogViewModel holds a reference to its own DialogWindow:
this.DialogWindow = new Dialogs.Views.DialogWindow();
// Tell the DialogWindow to display this ViewModel:
this.DialogWindow.DataContext = this;
// Launch the Window, using a method of the Window baseclass, that only returns when the window is closed:
this.DialogWindow.ShowDialog();
}
Window launched in the above method will close when its Window.DialogResult property is set. This is why the DialogWindow is a property of the DialogViewModelBase class - when the subclassing dialog ViewModel wants to close the dialog window, it simply sets the result:
protected void CloseDialogWithResult(bool dialogWindowResult)
{
// Setting this property automatically closes the dialog window:
this.DialogWindow.DialogResult = dialogWindowResult;
}
Host Window for Dialog Views
The Dialogs.Views.DialogWindow class that the ShowDialogWindow method instantiates is defined in XAML and is a subclass of Window. It has two important features. The first is that it's primary content element is simply a ContentControl that binds to the current context. This allows me to define different Views for different subclasses of DialogViewModelBase, and the DialogWindow will host the corresponding View based on the type of the context:
<ContentControl Content="{Binding}" /> <!-- In reality this is inside a border etc but its simplified here for demonstration -->
The second important feature of the DialogWindow XAML is that it defines which dialog Views go with which dialog ViewModels. Here is a sample:
<Window.Resources>
<!-- DEFAULT ViewModel-View TEMPLATES -->
<DataTemplate DataType="{x:Type dialogs:YesNoMessageBoxDialogViewModel}">
<views:MessageBoxView />
</DataTemplate>
<DataTemplate DataType="{x:Type dialogs:ErrorDialogViewModel}">
<views:ErrorDialogView/>
</DataTemplate>
</Window.Resources>
What all this does, is that I can define dialogs as subclasses to DialogViewModelBase and implement a View for each, and then tell DialogWindow which View its ContentControl must show for which dialog ViewModel.
Launching a Dialog and getting results
Below is a sample from one of my application ViewModels, in which I launch a Dialog Window that allows the user to select an Asset Type for creation:
public void CreateNewAsset()
{
// Instantiate desired Dialog ViewModel:
Dialogs.NewAssetTypeSelectionDialogViewModel dialog = new Dialogs.NewAssetTypeSelectionDialogViewModel();
// Launch Dialog by calling method on Dialog base class:
dialog.ShowDialogWindow();
// Execution will halt here until the Dialog window closes...
// The user's selection is stored in a property on the dialog ViewModel, and can now be retrieved:
CalculatorBase.AssetTypeEnum newAssetType = dialog.AssetType;
switch (newAssetType)
{
// Do stuff based on user's selection...
}
}
PS: I should really write a blog entry about this - when I do, I will post the link here, as the blog entry will probably have more complete code samples.
It depends how you are handling the data. I will assume that changes made in the popup window can be accepted only when user clicks something like save in other case they should be discarded.
So firstly, I would suggest using MVC approach as controller is perfect for such tasks. You build viewmodels in it, assign them o views and show the views. VM's simply keeps data and commands, commands execute methods are kept in controller. In other words you have singleton class which manages your VM's and views.
You should check out Prism framework. It offers great things like view regios where you can inject different user controls on the runtime, commanding and MVC layering out of the box alongside IOC and DI patterns.

WPF MVVM application - ICollectionView/Threading/General Questions

I have a few questions regarding to building WPF MVVM applications.
1) I'm using ICollectionView objects for databound controls such as ListView and ComboBox. I found this was the simplest way of gaining access to/tracking the selected item of these controls. What is the best way to replace the contents of ICollectionView? Currently I'm doing it like so:
private ICollectionView _files;
public ICollectionView Files {
get { return _files; }
}
void _service_GetFilesCompleted(IList<SomeFile> files) {
this.IsProcessing = false;
_files = CollectionViewSource.GetDefaultView(files);
_files.CurrentChanged += new EventHandler(FileSelectionChanged);
OnPropertyChanged("Files");
}
I didn't know whether it was necessary to reattach the handler every time I refresh the list of files?
2) Now that I've got my head round it, I am starting to like the MVVM pattern. However, one concept I'm not completely sure about is how to send notifications back down to the view. Currently I am doing this by binding to properties on my ViewModel. For example, in the above code I have an "IsProcessing" property that I use to determine whether to display a ProgressBar. Is this the recommended approach?
3) Following on from 2) - there doesn't seem to be a standard way to handle exceptions in an MVVM application. One thought I had was to have one method on my ViewModel base class that handled exceptions. I could then inject an IMessagingService that was responsible for relaying any error messages. A concrete implementation of this could use MessageBox.
4) I have a few tasks that I want to perform asynchronously. Rather than adding this logic directly in my service I created a decorator service that runs the underlying service methods on a new thread. It exposes a number of events that my ViewModel can then subscribe to. I have listed the code below. I understand that BackgroundWorker is a safer option but did not know whether it was suitable for running multiple asynchronous tasks at once?:
public void BeginGetFiles()
{
ThreadStart thread = () => {
var result = _serviceClient.GetUserFiles(username, password);
GetFilesCompleted(result.Files);
};
new Thread(thread).Start();
}
Finally, I realize that there are a number of MVVM frameworks projects that handle some of these requirements. However, I want to understand how to achieve the above using built-in functionality.
Thanks
If you have ListViews and ComboBoxes, you should really be considering an ObservableCollection<> to bind to these controls. Adding and removing items from the collection will automatically notify the control the property has changed.
If you're doing background processing, you can look at the BackgroundWorker or DispatcherTimer to handle updates to the UI. These both have the capability of acting on the UI thread, and can be thread safe.
To get the selected item from a combo box, expose an INotifyCollectionChanged object such as ObservableCollection and bind it to the itemsource, then create another property for the current item and binding ComboBox.SelectedItem (or ComboBox.SelectedValue if required) to it. You will need to manage the selection when updating the collection.
On the face of it, ICollectionView seems like the obvious choice but the WPF implementation really forces your hand on some threading code that you really shouldn't be troubled with.
I used ICollectionView and CollectionViewSource recently (for filtering) and have become frustrated with how many dispatcher issues have crept in. Today I am probably going to revert to the method i describe above.

Resources