How can I use one view model for many windows in WPF? I need model to be updated from one window only and handle these changes in others (for ex. property 'Locked').
I have one view model incapsulating the most general info that should be used not only on the A (suppose it is 'Company') window but also on windows child window B (suppose it is 'Person'). So the 'General' view model should be determined by A entity but be passes to all children entity. While updating this view model on A window - we should se changes on all B windows.
public partial class A : WindowBase
{
private GeneralViewModel general;
public GeneralViewModel General
{
get
{
return this.general ?? (this.general = new GeneralViewModel ());
}
}
}
public partial class B : WindowBase
{
private GeneralViewModel general;
public GeneralViewModel General
{
get
{
return this.general ?? (this.general = new GeneralViewModel ());
}
}
public B(GeneralViewModel g)
{
this.general = g;
}
}
I wish the model should be updated only in A and B was simply displaying that changes were maid. In case I pass model as it is shown in this code or if I implement 'General' as property with getter and setter changes are not applied.
Thanks for any help.
You could use a singleton-class as your ViewModel.
Example:
public Window()
{
this.DataContext = ViewModel.Instance.
}
EDIT:
public GeneralViewModel
{
public DataType Model
{
get { return DataType.Instance; }
}
}
Now everytime you access the Model in one of your GeneralViewModels, it is locked for all others.
Initialise your view model in a static member somewhere and have the windows return the value as their GeneralViewModel.
In the scenario you mentioned, your GeneralViewModel is a kind of Dependency to both your Window classes and for these purposes you can use some of the available IoC containers or MEF built into .Net 4. You can register your Dependencies including the GeneralViewModel in some application startup event.
Below is some sample code that will make your GeneralViewModel instance to be then located from the container it is registered with (MEF in below case):
[Export(typeof(B))]
public partial class B : WindowBase
{
private GeneralViewModel general;
public GeneralViewModel General
{
get
{
return this.general ?? (this.general = new GeneralViewModel ());
}
}
[ImportingConstructor]
public B(GeneralViewModel g)
{
this.general = g;
}
}
To learn more about MEF,see these articles:
CodePlex
Ten Reasons to use the Managed Extensibility Framework
Managed Extensibility Framework Overview
An Introduction to Managed Extensibility Framework (MEF) - Part I
There are many other DI and IoC containers available as Open Source downloads.
There is no problem if you use MVVM. In this case your ViewModel will correspond to some View which is basically the UserControl and you can place it to as many Windows as you wish.
And When you implement MVVM you should also use INotifyPropertyChanged or ObservableCollections
Related
My question involves something where thousand and one topics are created about,
and if I overlooked an answer to my question, I'm sorry, but as far as I looked none really could answer my question. For example:
Opening new window in MVVM WPF
The answers are okay when if you use just one WPF project (including models, vms and views) but since I'm learning how to implement MVVM the correct way (and I've read multiple times that best practice is to create seperate class lib (dll's) for model, viewmodel and a seperate gui project) that doesn't seem to work in my eyes because if I want to create an interface like IWindowService (described on previous url and also here, It's impossible to access Window or Control class because then I should have a reference to the gui project and the whole goal of the pattern is wrecked.
So my question is how to show a new window (with a new viewmodel) from for example the MainViewModel, while respecting the loosely coupled MVVM principles AND seperate projects.
More in depth example of what I'm trying to achieve:
I have following structure:
MODEL (dll project)
Profile
VIEWMODEL (dll project)
MainViewModel
AddProfileViewModel
VIEW (WPF) (exe project)
MainWindow
AddProfileWindow
I open MainWindow and I want to press the button AddProfile, then AddProfileWindow needs to show up with the AddProfileViewModel attached to it.
Define the IWindowService interface in the model project.
Reference the model project from the view model project.
Implement the IWindowService in the WPF application (view) project.
The button should then be bound to an ICommand that uses IWindowService to open the window. Something like this:
public class MainWindowViewModel
{
private readonly IWindowService _windowService;
public MainWindowViewModel(IWindowService windowService)
{
_windowService = windowService;
AddProfile = new DelegateCommand(() =>
{
_windowService.OpenProfileWindow(new AddProfileViewModel());
});
}
public ICommand AddProfile { get; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(new WindowService());
}
}
public class WindowService : IWindowService
{
public void OpenProfileWindow(AddProfileViewModel vm)
{
AddProfileWindow win = new AddProfileWindow();
win.DataContext = vm;
win.Show();
}
}
I was struggling with this and found the answer posted by #mm8 very helpful as well as a few other posting. I wasn't crazy about the idea of having to create a class (or method within a class) for each view, so I created my own variation. This is one of my first projects with WPF while attempting to use MVVM, so I'm sharing this in case it is helpful, and also to get feedback from the more experienced folks.
public class WindowDialogService : IWindowDialogService
{
private static readonly Dictionary<Type, Type> viewModelPairs =
new Dictionary<Type, Type>
{
[typeof(DetailsViewModel)] = typeof(DetailsView)
};
public void ShowWindowDialog(IViewModelBase viewModel)
{
if (!viewModelPairs.TryGetValue(viewModel.GetType(), out Type viewType))
{
throw new ArgumentException("View Model not mapped", nameof(viewModel));
}
if (viewType.GetConstructor(Type.EmptyTypes).Invoke(new object[] { }) is Window view)
{
view.DataContext = viewModel;
view.Owner = Application.Current.MainWindow;
view.ShowDialog();
}
}
}
I've searched for almost 3 hours and found alot about PRISM and VM data sharing, but I can't get my brain to understand it.
I have the two ViewModels MainViewModel and ListViewModel. The MainViewModel has a dockpanel where I want to have a common menu to add, remove items and close the application for example. There is also a usercontrol in the XAML which is showing the ListView. The ListView contains a few ObservableCollection<ProjectModel>.
From the MainVM I want to add more Projects - so to a collection. To access the ListModel, which contains the data, I pass it to the constructor of both ViewModels:
public MainModuleViewModel(ListModel listModel)
{
ListModel = listModel;
InitializeCommands();
}
public ListModuleViewModel(ListModel listModel)
{
ListModel = listModel;
InitializeCommands();
}
But it seems that during initializaiton to different objects of ListModel are created.
So my question is: What do I have to do, to work just on the one object of ListModel, which I'm passing to the constructors?
You have two options, as always:
register the ListModel as singleton (ContainerControlledLifetimeManager in Unity)
or register a service as singleton that gives the data to the view model
I'd go with the second one as it's far more flexible - you can use a different ListModel instance elsewhere with different content, you can make the service to give each view model its own instance (though with the same content) or you can make it give each view model the same instance and so on...
public interface IDatasource
{
ListModel Data { get; }
}
internal class StephensService : IDatasource
{
ListModel Data { get; } = new ListModel(); // or however you plan to procure the data
}
// ... bootstrapper / module initialization ...
Container.RegisterType<IDatasource, StephensService>( new ContainerControllerLifetimeManager() );
// ...
internal class ListModuleViewModel
{
public ListModuleViewModel( IDatasource datasource )
{
var heresMyData = datasource.Data;
}
}
Depending on your needs, the service can implement INotifyPropertyChanged or the ListModel can be an ObservableCollection...
How do I bind data from host to a plugin with MEF?
So the thing is:
I work with MVVM so I have my Models, ViewModels and Views.
I want to use MEF to be able to expand my application.
I want to store all the data in the MainViewModel so every plugin can work with the actual data.
The plugin is a UserControl wich will be displayed as a ContentControl in the MainViewModel.
What I have so far:
MainViewModel
Models
Databinding from MainViewModel to View.
Import plugins from folder X
What I need:
- the plugins need to bind the data from the MainViewModel to the plugin UI.
- changing the property in the plugin UI must update the data in the MainViewModel and update the UI from all other plugins.
The PluginInterfaces:
public interface IPlugin
{
}
public interface IPluginData
{
string Name { get; }
}
The MainViewModel: (part of it)
private MyModel myfirstmodel;
private DirectoryCatalog catalog;
private CompositionContainer container;
[ImportMany]
IEnumerable<Lazy<IPlugin, IPluginData>> Plugins;
public MainWindowViewModel()
{
string pluginPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
pluginPath = Path.Combine(pluginPath, "plugins");
if (!Directory.Exists(pluginPath))
Directory.CreateDirectory(pluginPath);
catalog = new DirectoryCatalog(pluginPath, "*.dll");
container = new CompositionContainer(catalog);
try
{
this.container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
Console.WriteLine(compositionException.ToString());
}
}
The Model
public class MyModel
{
private string message;
private int number;
private DateTime date;
public string Message { get { return message; } set { message = value; } }
public int Number { get { return number; } set { number = value; } }
public DateTime Date { get { return date; } set { date = value; } }
}
The Plugin
[Export(typeof(IPlugin))]
[ExportMetadata("Name", "MyFirstPlugin")]
public partial class MyFirstPlugin : UserControl, IPlugin
{
public MyFirstPlugin()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//Change the message in MainWindowViewModel and the date when it gets changed.
}
}
I tried using INotifyPropertyChanged but did not came that far..
Does anybody got a really good tutorial for that or can show me how to do this?
I would appreciate a "how to" and not just a "just use INotifyPropertyChanged".
Is this even possible?
Personally I think you're going about this the wrong way, MEF was specifically designed to do this type of plumbing so that you don't have to.
In a proper MVVM application the views usually get created by way of data templates, and even if you don't do this there is usually other global data you need to import like brushes and behaviours etc. And if you do use templates (which you really should be doing) then you don't need to explicitly export your controls; simply referencing them by the templates will result in them getting imported as well.
You can achieve this in practice with another pass of the importer. Give each plugin a custom ResourceDictionary and put all the global data/templates/UI resources etc in there (so it's effectively the plugin's version of App.xaml). The trick is to then give those ResourceDictionaries their own code-behind file and decorate those classes with Export. Then all your main application has to do is Import all the classes of type ResourceDictionary (or preferably a derived class or interface that returns the dictionary) and add each one to Application.Current.Resources.MergedDictionaries. From that point on the development of your plugins is pretty much the same as if they were in a DLL project that you were statically linking the usual way.
For exchange data with ModelView you will need an interface representing the host. You will also need a container or method to insert a visual control representing the plugin.
interface IPluginHost
{
MyModelView ModelView {get;}
MyMainWindow View {get;}
}
interface IPlugin
{
void Register(IPluginHost host);
void Unregister();
}
After the composition you register plugins with the host, which will gives them an access to the model view:
this.container.ComposeParts(this);
foreach(var plugin in Plugins)
{
plugin.Value.Register(this);
}
What I need: - the plugins need to bind the data from the
MainViewModel to the plugin UI.
Plugin has access to MainViewModel in it's Register method were it can perform all the necessary bindings. Here is one of many ways it could be done:
public partial class MyFirstPlugin : UserControl, IPlugin
{
...
void Register(IPluginHost host)
{
_host = host;
// Attach main model view as data context
this.DataContext = host.ModelView;
// Add control to the host's container
var mainWindow = host.View;
mainWindow.AddPluginControl((UserControl)this);
}
void Unregister()
{
if (_host == null)
{
return;
}
this.DataContext = null;
_host.View.RemovePluginControl(this);
_host = null;
}
IPluginHost _host;
}
AddPluginControl() ,RemovePluginControl() are public methods to insert a visual element of the plugin into container.
I have a base ViewModel and associated View. I also have multiple derived ViewModels from the base ViewModel, but I'd like to use the base View for display.
Base ViewModel and View:
vm: MyCompany.MyApp.Modules.Wizard.ViewModels.WizardViewModel
vw: MyCompany.MyApp.Modules.Wizard.Views.WizardView
Derived from WizardViewModel:
vm: MyCompany.MyApp.Modules.NewSpec.ViewModels.NewSpecViewModel : WizardViewModel
vw: (map to MyCompany.MyApp.Modules.Wizard.Views.WizardView)
vm: MyCompany.MyApp.Modules.NewSpec.ViewModels.NewMaterialViewModel : WizardViewModel
vw: (map to MyCompany.MyApp.Modules.Wizard.Views.WizardView)
I think this should be possible using the mapping in ViewLocator or ViewModelLocator or NameTransformer, but I haven't figured it out yet.
I am using the Gemini Framework with Caliburn.Micro v1.5.2 (I plan on upgrading to v2 soon).
Here is one of the things I have tried:
public class NewSpecViewModel : WizardViewModel
{
// ...
static NewSpecViewModel()
{
// Escape the '.' for the regular expression
string nsSource = typeof(NewSpecViewModel).FullName.Replace(".", #"\.");
string nsTarget = typeof(WizardViewModel).FullName;
nsTarget = nsTarget.Replace("WizardViewModel", "Wizard");
// nsSource = "MyCompany\\.MyApp\\.Modules\\.NewSpec\\.ViewModels\\.NewSpecViewModel"
// nsTarget = "MyCompany.MyApp.Modules.Wizard.ViewModels.Wizard"
ViewLocator.AddTypeMapping(nsSource, null, nsTarget);
}
// ...
}
P.S. I know there are existing Wizard frameworks (Extended WPF Toolkit, Avalon Wizard, etc), but I don't want to add another 3rd party assembly and the Extended WPF Toolkit Wizard wasn't working properly.
P.P.S. I also want to use this style of base ViewModel/View mapping elsewhere.
Here's [a link] (https://caliburnmicro.codeplex.com/discussions/398456) to right way to do this.
EDIT: Since codeplex is shutting down, here is the code from the discussion:
var defaultLocator = ViewLocator.LocateTypeForModelType;
ViewLocator.LocateTypeForModelType = (modelType, displayLocation, context) =>
{
var viewType = defaultLocator(modelType, displayLocation, context);
while (viewType == null && modelType != typeof(object))
{
modelType = modelType.BaseType;
viewType = defaultLocator(modelType, displayLocation, context);
}
return viewType;
};
I know it's late...
but there is an option to bind the ViewModel to a view directly, and maybe this helps others.
I would also append this binding to the base classes constructor. The following works for me:
public abstract class WizardViewModel {
protected WizardViewModel() {
// this --> points the child class
ViewModelBinder.Bind(this, new WizardView(), null);
}
}
With this, each child now uses the WizardView (without any additional programming in the child class).
public class NewSpecViewModel : WizardViewModel {}
I'm new on Caliburn Micro and want some advice on which path to take to devolop my app interface and navigation between views.
My idea is to have a MainWindow which will contain a menu of buttons, each one related with a specific view. Each view will be stored in a separated WPF UserControl. The mainWindow will also contain a TabControl bound to an ObservableCollection of tabs on viewmodel. Everytime a button on menu is clicked, I want to add a new tab with a ContentPresenter inside that will dynamically load a view and its corresponding viewmodel.
So my questions:
1) Should I use a Screen Collection here?
2) Should the UserControl implement Screen interface?
3) How do I tell MainWindow ViewModel which view to load on the new added tab maintaining viewmodels decoupled?
Thanks to everyone in advance.
UPDATE
After a lot of reading and some help of the community I managed to resolve this. This is the resultant AppViewModel:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
I have to keep the ICommand from OpenTabCommand because the name convention of Caliburn.micro doesn't seems to work inside DataTemplate. Hope it could help someone else. Thanks to all
I've done something very similar using Caliburn.Micro, and based it on the SimpleMDI example included with the examples, with a few tweaks to fit my needs.
Much like in the example, I had a main ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
with a corresponding ShellView containing a TabControl - <TabControl x:Name="Items">, binding it to the Items property of the the Conductor.
In this particular case, I also had a ContextMenu on my ShellView, bound (using the Caliburn.Micro conventions), to a series of commands which instantiated and Activated various other ViewModels (usually with a corresponding UserControl, using the ActivateItem method on the Conductor.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
In that case, I didn't require the ViewModels to be created with any particular dependency, or from any other locations in the program.
At other times, when I've needed to trigger ViewModel from elsewhere in the application, I've used the Caliburn.Micro EventAggregator to publish custom events (e.g. OpenNewBrowser), which can be handled by classes implementing the corresponding interface (e.g. IHandle<OpenNewBrowser>), so your main ViewModel could have a simple Handle method responsible for opening the required View:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
This section of the documentation will probably be useful, especially the Simple MDI section.
Additional code I mentioned in the comments:
I sometimes use a generic method along these lines ensure that if I have an existing instance of a screen of a particular type, switch to it, or create a new instance if not.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Used like:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}