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.
Related
I have a UserControl view which require data to be pulled from the database to set the ObservableCollection in order to render a DataGrid.
I want to set the ObservableCollection when the UserControl is loaded not when the view model is constructed. Also, I prefer to asynchronously set the ObservableCollection so the UserContror opens immediacy then then the data-grid is set when the data becomes available.
In my view model I create a public method called SetVendors() which fetches the data from the database and sets ObservableCollection just before the view is notified. However, I am using the view class directly to set the SetVendors() method which sound like a bad practice for some reason to me.
Questions
Is it possible to set the data on view-load directly from the view-model? If so, how? Also, is it possible to set the data asynchronously so the view loads quick?
Here is my ViewModel code
public class VendorsListViewModel : ViewModel
{
protected IUnitOfWork UnitOfWork { get; set; }
public int CurrentPage { get; set; }
public int ModelsPerPage { get; set; }
public IPagedList PageMeta { get; set; }
private ObservableCollection<Vendor> _Vendors { get; set; }
public ObservableCollection<Vendor> Vendors
{
get
{
return _Vendors;
}
set
{
_Vendors = value;
NotifyPropertyChanged();
}
}
public VendorsListViewModel(IUnitOfWork unitOfWork)
: base()
{
UnitOfWork = unitOfWork;
CurrentPage = 1;
ModelsPerPage = 20;
}
public VendorsListViewModel()
: this(new UnitOfWork())
{
}
public void SetVendors()
{
var vendors = UnitOfWork.Vendors.Get(CurrentPage, ModelsPerPage);
Vendors = new ObservableCollection<Vendor>(vendors);
PageMeta = vendors.GetMetaData();
}
}
This is how currently set the data from the code behind the view which sound like a bad practice to me.
public partial class Index : UserControl
{
public Index()
{
InitializeComponent();
}
// This event is called from the view when the form is loaded using `Loaded="ViewLoaded"` XAML code
private void ViewLoaded(object sender, EventArgs e)
{
(DataContext as VendorsListViewModel).SetVendors();
}
}
Loading the data from the view´s code behind is indeed a bad practice as you are breaking the MVVM separation of concerns.
A better way to do that would be using a command, or, more precisely an event to command, check this article for a nice explanation.
To achieve this you can use the System.Windows.Interactivity library that comes with Blend for Visual Studio SDK for .NET, if you don't have it just run the Visual Studio installer and select it.
Then add a reference to the library by going to Add reference -> Assemblies -> Extensions.
Once you have this, things are simple really, you need to just:
First add the namespace to your View:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then, bind the event to a command in your view model like this:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Now in your ViewModel we have a little problem, we need to provide a command to be called when the event is triggered, a command is an implementation of the ICommand interface, all MVVM frameworks out there will provide you with ready to roll ICommand implementations so you can either use one of these frameworks or you can implement ICommand yourself if you just need it for this particular example, wait! you don't need to implement it, you can just get it from this question ,-)
For my example, however, I will use MVVM Light toolkit which I recommend you to use as it will relief you from most of the MVVM annoyances that you will need to deal with in any real-world application.
You can get MVVM Light from NuGet:
Install-Package MvvmLight -Version 5.4.1
So now you can use MvvmLight implementation of ICommand which is called RelayCommand, you would do so in your ViewModel as follows:
[...]
public ICommand LoadedCommand { get; private set; }
public VendorsListViewModel(IUnitOfWork unitOfWork) : base() {
UnitOfWork = unitOfWork;
CurrentPage = 1;
ModelsPerPage = 25;
LoadedCommand = new RelayCommand(this.Loaded);
}
private void Loaded()
{
SetVendors();
}
As per your second question: absolutely, you can load the data asynchronously if you want to. This is a whole different question though, take a look at this article for more info: https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/
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>();
}
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
I am trying to validate User Input in Windows Forms Application (using MVP design Pattern). Since this is my first project using MVP, I am not very clear where and how to put the user input validation code.
To be specific, I have a Products form which contains two text box controls, Namely ProductName and ProductPrice.
Below is the code for my ProductForm, IProductView and ProductPresenter
IProductView.cs
public interface IProductView
{
string ProductName { get; set; }
int ProductPrice { get; set; }
event EventHandler<EventArgs> Save;
}
frmProduct.cs
public partial class frmProduct : Form,IProductView
{
ProductPresenter pPresenter;
public frmProduct()
{
InitializeComponent();
pPresenter = new ProductPresenter(this);
}
public new string ProductName
{
get
{
return txtName.Text;
}
}
public int ProductPrice
{
get
{
return Convert.ToInt32(txtPrice.Text);
}
}
public event EventHandler<EventArgs> Save;
}
ProductPresenter.cs
public class ProductPresenter
{
private IProductView pView;
public ProductPresenter(IProductView View)
{
this.pView = View;
this.Initialize();
}
private void Initialize()
{
this.pView.Save += new EventHandler<EventArgs>(pView_Save);
void pView_Save(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
I do want to use the ErrorProvider(EP) Control + since I would be using EP control on many forms, I would really love if I could reuse most of the code by putting the EP code in some method and passing it the controls and appropriate message. Where should I put this validation code?
Regards,
I've used a base form with the error provider on and then had other forms inherit from this. I also put the visual error code in this base form also. This meant the same code is re-used. For Mvp, you could do something similar with a base form and an interface your application views inherit from. Your presenters would then see a uniform interface for setting validation states, messages, etc.
I'm playing around with MVVM, getting to know the stuff involved in the pattern. The first application I'm writing is a very small application which basically displays 2 settings from the App.Config.
My goal is to be able to write to this app.config when the button is clicked.
My problem lies in the fact I don't know exactly how to wire up a command to delegate this work to, or if this is even the way to go.
My App.config is very straight forward:
<configuration>
<appSettings>
<add key="duration" value="100" />
<add key="operators" value="10" />
</appSettings>
</configuration>
The model looks like:
get
{
// try to parse the setting from the configuration file
// if it fails return the default setting 0
int durationSetting = 0;
Int32.TryParse(ConfigurationManager.AppSettings["duration"], out durationSetting);
return durationSetting;
}
set
{
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings.Remove("duration");
config.AppSettings.Settings.Add("duration", Convert.ToString(value));
ConfigurationManager.RefreshSection("appSettings");
config.Save();
}
}
So, the model is responsible for the actual data access, this is what we want, right?
Furthermore I have a ViewModel (ViewModelBase implements INotifyPropertyChanged):
public class SettingsViewModel : ViewModelBase
{
private Settings Settings { get; set; }
private SaveCommand saveCommand = new SaveCommand();
public ICommand SaveCommand
{
get
{
return saveCommand;
}
}
public SettingsViewModel(Settings settings)
{
if (settings == null)
throw new ArgumentNullException("Settings", "Settings cannot be null");
Settings = settings;
}
public int Duration
{
get { return Settings.Duration; }
set
{
if (Settings.Duration != value)
{
Settings.Duration = value;
RaisePropertyChanged("Duration");
}
}
}
The view is a xaml usercontrol, instantiated like this:
public partial class MainWindow : Window
{
public SettingsViewModel SettingsViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Settings settings = new Settings();
SettingsViewModel = new SettingsViewModel(settings);
}
}
Lastly there's a SaveCommand which implements ICommand, which is basically empty at this point. I've hooked up the command to a button in the view.
But basically, now what? What's the best way to handle the saving of the values?
Is the example I'm working on too contrived?
I would recommend using the very useful MVVM Light toolkit.
Basically, you will expose a ICommand public property that returns an instance of a RelayCommand:
private RelayCommand myCommand;
public ICommand MyCommand
{
get
{
return this.myCommand;
}
}
...
// in constructor of the view model:
this.myCommand = new RelayCommand(this.MyCommandImplementation);
...
private void MyCommandImplementation()
{
// Save your parameters here from your model
}
Something strange in your code is that you actually already save your settings in the setter of the public property named Duration.
What you could do instead (to prevent from saving each time the property is modified) is to simply use a private variable in your ViewModel:
private int duration;
public int Duration
{
get { return this.duration; }
set
{
if (this.duration != value)
{
this.duration = value;
RaisePropertyChanged("Duration");
}
}
}
So when you modify a UI field binded to your Duration property, you only update the private duration field. Thus you'll only save it to the app.config file in the MyCommandImplementation method.
private void MyCommandImplementation()
{
this.Settings.Duration = this.Duration;
}
Also note that your Settings management is a bit complicated (you remove then add again a settings, why?).
Last thing: in your view, you're currently using the view itself as datacontext. You have to specify your ViewModel instead:
this.SettingsViewModel = new SettingsViewModel(settings);
this.DataContext = this.SettingsViewModel;
Also, I don't think it's the role of your view to instantiate your model. I would instantiate Settings from the ViewModel instead.
All you need to do is to implement ICommand interface in a class and set SaveCommand property to an instance of this class. You can either you some third party command classes, like DelegateCommand of prism library or RelayCommand.
Refer to the following web page for a sample implementation of ICommand;
http://weblogs.asp.net/fredriknormen/archive/2010/01/03/silverlight-4-make-commanding-use-lesser-code.aspx
then the actions to be employed shall be registered in the command;
this.SaveCommand= new SaveCommand((o) =>
{
//change the model
});