MVVM show new window from VM when seperated projects - c#

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();
}
}
}

Related

Two ViewModels are using one Model which holds data

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 to load ViewModel/View in WPF under CaliburnMicro

I have a timer in my wpf application wich every 5 minutes ask a WCF service. If the service have message for my application, I get a list which contains text data and a specific code.
This code give an information about the view which must be loaded to print the data.
I have two ViewModel (the data source is the same for both): One for a Ticker > one view and One for Popup > two view
Project files :
View
Popup
PopHighView.xaml
PopMediumView.xaml
Ticker
TickerLayout.xaml
TickerNormal.xaml
ViewModel
PopViewModel
TickerViewModel
Models
AlertModel.cs
ViewParsers
AlertParser.cs
Datasource :
public class AlertParser : IAlertParser{
AlertServiceClient service;
public List<AlertModel> TickerAlertData()
{
try
{
service = new AlertServiceClient();
List<AlertModel> items = (from item in service.GetActiveAlert() select new AlertModel
{
Alertid= item.AlertId,
Alertstartdate = item.AlertStartDate,
Alerttitle = item.AlertTitle,
Alerttxt = item.AlertText
}).ToList();
return items;
}
catch
{
return null;
}
}
}
When my application is launched, there is no loaded view, only a icon in the systray(with wpf notifyicon).
My problem is, under theses circonstances, I don't understand how I could loaded a couple ViewModel/View, and pass the data to them, when my timer return a true from my service.
Many examples on the web have a mainview loaded, that's why I'm lost (like Conductor example on caliburn micro page).
Thanks for any help !
Edit :
Ok, My timer look like that :
if (service.IsAlertReady()=true)
{
string hostName = Dns.GetHostName();
string myIP = Dns.GetHostEntry(hostName).AddressList[0].ToString();
service.IsAlertForMe(myIP);
if(service.IsAlertForMe(myIP) == true)
{
ShellViewModel shell = new ShellViewModel();
shell.ShowMediumPop();
}
else
...
ShellViewModel
public class ShellViewModel : Conductor<object>
{
public void ShowMediumPop()
{
ActivateItem(new PopViewModel());
}
}
PopViewModel
public class PopViewModel : screen
{
protected override void OnActivate()
{
base.OnActivate();
}
}
PopView.Medium
<UserControl x:Class="TerminalClientProto.View.PopView"
...
cal:View.Model="{binding}"
cal:View.Context="Medium"
>
I'm sorry but I don't understand how I could launch my view when my Ticker tick. I've read the documentation, but I need some hints to understand this mechanism.
A program, any program, including the very program that contains the views you want to display can show a view in a number of ways. Here's a few:
var app = new App();
app.InitializeComponent();
app.Run();
Or you can start the view directly:
var view = new MyView();
view.Show();
// or
view.ShowDialog();
If the view is a MainWindow, then you can create a ContentControl area within the view to inject the Usercontrol containing the sub-view of what you want displayed. This still requires the MainWindow to be open... So the examples above would also work when injecting UserControls into a MainWindow. The act of injecting a User control is setting the ContentControl's Content to an instance of the User Control itself. Eventhandlers can handle this scenario nicely...
public void NewUserControlInMainWindow(object sender, UserControl uc){
//XCC = the Xaml content control in main window
XCC.Content = uc;
}
I'm not really sure how Caliburn does view injection....

Is Loosely Coupled on MVVM even posible?

I'm currently working on my first MVVM application using Caliburn.Micro. If understand correctly the concept of Loosely Coupled, I must be able to extract every class of my project, isolated it on a blank solution, and it must compile without problems because there is no direct reference to others classes, right?
So my question is this:
Where is the place to create the viewmodels for distinct views and mantain the loosely coupled model?
I ask this because many examples on the web do something like this:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive {
int count = 1;
public void OpenTab() {
ActivateItem(new TabViewModel {
DisplayName = "Tab " + count++
});
}
}
Where TabViewModel is the viewmodel for the desired view. But here, shellviewmodel is tightly coupled to TabViewModel
Other option i've try is to create some sort of menu class like this:
class MenuItem
{
public string Title { get; set; }
public Type ViewType { get; set; }
}
and use it like this:
new MenuItem(){Title="My menu",ViewType=typeof(ProductosViewModel)}
And use a generic creation procedure, but again this class is coupled with ProductosViewModel.
So what other options do i have? Or should i forget about strict loosely coupled?
Thank you!

Advice on Views navigation using Caliburn.Micro MVVM WPF

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>();
}

Using one view model in multiple windows

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

Categories

Resources