I've got a prism app, containing a Shell.xaml (with a MainRegion), ShellViewModel.cs.
This Shell window is opened when the app starts. Now I want to open a second Popup-Window containing the very same shell window (Shell.xaml, ShellViewModel).
The Shell definition is like in the prism StockTraderRI example. Shell.xaml contains a MainRegion (very simplified source):
<Window x:Class="Bsoft.Test.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.codeplex.com/CompositeWPF"
Title="MainWindow" Height="550" Width="825">
<Grid>
<ContentControl cal:RegionManager.RegionName="MainRegion"/>
</Grid>
</Window>
Code behind contains just the basic ViewModel reference:
namespace Bsoft.Test.bmedApp
{
[Export]
public partial class Shell : Window
{
[ImportingConstructor]
public Shell()
{
InitializeComponent();
}
[Import]
ShellViewModel ViewModel
{
set
{
this.DataContext = value;
}
}
}
}
The ShellViewModel is automatically inserted by the MEF loader:
namespace Bsoft.Test.bmedApp
{
[Export]
public class ShellViewModel : NotificationObject
{
[ImportingConstructor]
public ShellViewModel()
{
}
}
}
This does work like intended.
Now I want to open the shell window a second time as a popup window. It's easy enough to mark the Shell and ViewModel as not being shared using:
[PartCreationPolicy(CreationPolicy.NonShared)]
But my problems are:
1) I load other View(Models) into the MainRegion. How do I tell the program if the View(Model) should be loaded into the main Shell MainRegion or into the popup window MainRegion? I guess I need scoped RegionManagers, but I got no clue how to use them for this.
2) I've got some events (EventAggregator) for the Views loaded into a region to communicate notification and commands (status update, view closing, errors) for the Shell to report. How can I seperate the main shell events from the popup window events (since both are the same shell)?
I want to be able to open several of the popup windows, so using different region names for both is not enough for me, I need more separation. Maybe there is a way to create a separate internal prism/mef/region/container framework??
I do not completely understand what do you mean by opening two shells ?
If you run your silverlight application in two different windows or you have 2 instances of your WPF app then your Shells do not conflict.
Even if you have one application with 2 instances of Bootstrapper there is no conflict - your two shells work completely independently.
Let me know whether this help.
What you are trying to achieve is possible, although there might be some things I don't completely understand about your approach.
I assume that when you are talking about having two Shells you actually mean having two active windows at the same time.
There are many ways to achieve this in Prism, so let's get on with your doubts.
For (1) the best thing I can think of is creating a different instance of the Region manager an attaching it to the other Shell (the popup one). This is similar to working with scoped regions (as you would have a separate RegionManager), but you create the manager and attach it to the Shell instead. Then register the new RegionManager in MEF with a string Id so you can differentiate it from the MainWindow RegionManager and simply add regions to the correct region manager.
(2) is a different subject, as you are trying to get the same code to behave differently. Perhaps, if you require such different behaviors, using the same Shell class for both windows is not the best approach. If you require this kind of differentiability but would still like to reuse code I'd recommend using some form of inheritance and combining virtual methods in a BaseShell with template methods to perform the things that are different for each Shell.
I hope this illustrates my point.
Related
I'm building an application using PRISM and MVVM. I have a view model that needs to display a non-modal dialog box to the user indicating an operation is in progress. I'm using essentially an abstracted IDialogService.
My question is: where should I store the strings for the title and the message shown in this dialog box? The view model's logic causes the dialog box to be displayed and determines when it should be closed. Hence, I have code that looks like this in my view model:
let! closeDlgAction =
dialogSvc.ShowDialogModeless (
"Opening File",
"Please wait while your selected file is opened.") |> Async.AwaitTask
I'm thinking about localization scenarios. WPF has its own mechanism for providing localization through resource dictionary, etc. It seems like these strings belong in a resource dictionary, but the view model shouldn't have a dependency on WPF resource directories - especially because the same view model is going to be used on a Xamarin Forms application later.
The best solution that comes to mind is to use a service that abstracts the resource library away (e.g. IDialogStringService), but I wonder if there's a better or more preferred approach?
You shouldn't use resource dictionaries (xaml) to store text. Instead you have to use Resources (*.resx). In VS:
Right click on project
Add -> New Item...
Find "Resources File" template, type name, and click Add
Opt. Open this file (special editor will opened) and on top bar switch Access Modifier to Public, if you want get access to text from another project or from XAML. Add some key\value strings.
Right click on resource file and click Run Custom Tool. New class will generated with static properties with names based on your keys from Step 4.
How to use (if file has name Localizations.resx and has string with key "AppTitle")
From code:
let! closeDlgAction =
dialogSvc.ShowDialogModeless (
Localizations.AppTitle,
"Please wait while your selected file is opened.") |> Async.AwaitTask
From xaml:
<Window
x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{x:Static Localizations.AppTitle}"/>
*.resx file and *.cs file that is generated both don't depend on any WPF assemblies, so you can use them in different assemblies: in shared view models, from wpf views and from xamarin views. Just put you *.resx file in separate netstandard assembly and refer to it where do you need it from
Cons of this way:
resx generates class with strings and each string is public property, so static code analyze works
You don't have add new abstraction level
You can ref strings from code files or from XAML
I liked Vadim's answer, and I have used that approach before. If my View Models lived in the same project as the WPF project, that would be the best solution.
However, my View Models are in a different library (and a different language) and will be shared between a Prism MVVM WPF project and a Prism MVVM Xamarin Forms project. I could still use resources in the View Model library, but then localization concerns would exist separately in both the WPF project (for the Views) and the View Model library. IMO the localization concern should be centralized.
As such, I decided to abstract the resources behind a service. Implementing the resource service turned out to be more straightforward than I thought. To use an indexer intuitively, I defined a "resource container object" that is returned by IResourceService, as seen below:
public struct ResourceContainer
{
private readonly Func<string, string> _resourceGetter;
public string this[string resourceId] => _resourceGetter(resourceId);
public ResourceContainer(Func<string, string> resourceGetter) => _resourceGetter = resourceGetter;
}
public interface IResourceService
{
ResourceContainer Resources { get; }
}
And the service implementation in the WPF library is as follows:
public class ResourceService : IResourceService
{
public ResourceService()
{
Resources = new ResourceContainer((s) => Application.Current.Resources[s] as string);
}
public ResourceContainer Resources { get; }
}
In the WPF layer's XAML resource directory:
<s:String x:Key="FileOpenDialogTitle">Opening File</s:String>
<s:String x:Key="FileOpenDialogMessage">Please wait while your selected file is opened.</s:String>
And, finally, the View Model consumes this service by requesting IResourceService on its constructor, and is used as follows:
let! closeDlgAction =
dialogSvc.ShowDialogModeless (
resourceSvc.Resources.["FileOpenDialogTitle"],
resourceSvc.Resources.["FileOpenDialogMessage"]) |> Async.AwaitTask
This approach will ultimately require implementing the resources twice - once for the WPF project and once for the XF project, but I have to implement the Views twice, anyway. At least the localization concerns are centralized in both cases (or perhaps a shared resource library can be used between both projects).
EDIT: This technique could also leverage Vadim's suggestion by putting the localization resource (.resx) in the WPF project as well, and either having the XAML resource directory reference the static resources, or have the ResourceService return the resource directly. Having the resources in .resx format may make sharing them between multiple projects more straightforward.
I am creating a WPF application. Naturally my entry point is MainWindow.xaml, which is opened up by App.xaml
var mainWindow = container.Resolve<MainWindow>();
Application.Current.MainWindow = mainWindow;
Application.Current.MainWindow.Show();
I am using Dependency Injection and so far all the dependencies are passed as parameters in the ctor of the MainWindow's View Model.
i.e. my Main Window is
public partial class MainWindow : MetroWindow
{
private readonly MainWindowModel mainViewModel;
public MainWindow(MainWindowModel mainViewModel)
{
and its View model is:
public MainWindowModel(IDataRepository dataRepo, ICommand command1, ICommand command2, etc ...)
{
However, I am now starting to realize this might be a problem. Given that the MainWindow is the entry point to the entire app, it seems like any dependency, anywhere in the application will have to first pass through the MainWindow View Model constructor. This seems crazy.
I am coming from the background of ASP.NET MVC and there we have Controllers, which receive only the dependencies that they need. i.e. the concept of a main entry point there is missing and this makes things easier and more manageable.
Here is an example in my WPF app. A control, on the Main View needs to open up a dialog. This dialog is another Window and of course that window receives its ViewModel in its ctor. To me, it seems like to be able to resolve the dialog properly, I need to pass it through the Main Window View Model ctor first, keep it as private readonly field of the Main Window View Model and launch it when necessary. Ok, but what if I have 100 dialogs. That's just one of the examples. I have such issue with the ICommand implementations too.
To sum up my question:
How do I manage the dependencies in WPF properly, without using the Service Locator anti-pattern and without passing every single abstraction through the ctor of the main window view model? I could very easily pass a Container around and let, e.g., the create ABC command solve the ABCDialog before opening it, but I feel this will cause more issues than it would solve.
I am probably doing something wrong. Please advise me what is the best practice.
I've be struggling for a while trying to make it work. Basically I have a Silverlight application using MVVM/PRISM/Unity combination.
My shell consists by two Regions RootContent and RootMenu. My RegionManager.Regions are able to see those two regions just fine, and the application runs correctly.
The problem starts when one of my Views inside the RootContent opens a ChildWindow, it contains more two Regions, as follows:
<ContentControl Region:RegionManager.RegionName="WOFSCustomerLookup" />
<ContentControl Region:RegionManager.RegionName="WOFSCustomerView" />
The ViewModel of this View that has this XAML above, even inheriting and properly resolved, the IRegionManager.Regions collection do not contains those two new Regions above, just the RootContent and RootMenu.
More Information
This is How my ChildWindow is called (it calls the "View"):
ChildWindow editor = this.container.Resolve<WorkOrderFieldServiceEditor>();
editor.show();
And this is the Constructor of my ViewModel:
public WorkOrderFieldServiceViewModel(IUnityContainer container, IRegionManager regionManager)
{
this.container = container;
this.regionManager = regionManager;
// Still have just the two Root regions:
// this.regionManager.Regions[]
}
Did I miss anything?
Pretty sure the problem is because you are not showing the WorkOrderFieldServiceEditor view through Prism but are just getting an instance of it through the container and then calling Show method directly on it. So, Prism is not really involved. When the main Shell is created through the bootstrapper, the regions defined in the View are then created in the region manager. So, you will need to look at how you Navigate to a popup window using Prism and not call the Show method directly.
Checkout the RegionPopupBehaviors.cs file in the StockTrader reference application.
http://msdn.microsoft.com/en-us/library/ff921074(v=PandP.40).aspx
My application can have multiple designer windows. Each window constitutes of several user controls which communicates dynamically with the help of RelayCommands. I created the following class as the backbone of the commanding infrastructure.
public static class Commands
{
public static readonly RoutedCommand EntityEditRequest = new RoutedCommand();
public static RelayCommand EntityEditorChangeRequest;
public static RelayCommand XMLUpdateRequest;
public static RelayCommand SaveRequest;
}
Each viewmodel for the user controls will do something like this in the constructor
public XMLEditorViewModel()
{
Commands.Commands.SaveRequest = new RelayCommand(Save_Executed);
Commands.Commands.XMLUpdateRequest = new RelayCommand(UpdateXML);
}
However, I completely missed the point that the application can have multiple windows. When each window is opened the static Commands are set for that particular window.
Example:
Window A is opened-the constructors for the usercontrols set the RelayCommands and all is well.
Window B opened-the constructors for the usercontrols set the RelayCommands. Window A's command binding is lost!
So when I change the tab to Window A(the windows are tabbed) no commands are working.
I need some idea so that when I change the tab the active window always sets the commands. I can try to put the commanding in tab_selection_changed event, but somehow it is looking bad to me. Is there a proper way to do this? Any help is much appreciated.
Edit:
The question proved a bit confusing among the readers. I am not trying to make multiple subscribers for a command. At any given point only one window is active. This window consists several user controls-some of them loaded dynamically with the help of commands; but each command is handled by a single view model class-so no multi subscribers. My problem is the application can load multiple windows in tabs-only one window is active at any given point-but the user can do to a different tab and make another window active. As the view model constructor assigns the static RelayCommands, when each new window is loaded the static command is set to a new binding.
Window A opened-window A view model constructor sets the static command bind to its object command handler. Window A is active. Commanding is fine.
Window B loaded-window B view model constructor sets the static command bind to its object command handler. Window B is active. Commanding is fine.
Now, User select the Window A tab to set the Window A as active. Commanding wont work. Of course it wont as the Command is bind to Window B command handler.
Theoretically static commands can handle the scenario as at any given point there will be only one active window. But how??
The global command should be a CompositeCommand or similar approach (CompositeCommand is from Prism). This will allow multiple children to register with the command.
public static CompositeCommand SaveCommand = new CompositeCommand();
The command can then be accessed across the ViewModels or where applicable like so...
SaveCommand = new DelegateCommand<object>(Save, CanExecuteSave);
GlobalCommands.SaveCommand.RegisterCommand(SaveCommand);
You can then leverage the IActiveAware interface to define which Window is the active Window and act on the command accordingly.
There is also an MSDN posting on creating globally available commands. Don't forget to unregister the command to avoid a memory leak.
Any reason why have you decided to put it into static class?
class XMLEditorViewModel
{
public ICommand SaveRequest { get; private set; }
public XMLEditorViewModel()
{
SaveRequest = new RelayCommand(Save_Executed)?
}
}
Commands that are not view specific can be defined on static classes.
Commands that are view specific should either be defined on view-model,
passed as DataContext to view, enabling separate implementation
for different views with different view models,
or at least have the views pass a CommandParameter that
can be used to either identify them (e.g. reference to the view) or their DataContext.
If the commands are static, register them once only,
perhaps on a singleton used by view models.
create a globally available command, create an instance of the DelegateCommand or the CompositeCommand and expose it through a static class.
public static class GlobalCommands
{
public static CompositeCommand MyCompositeCommand = new CompositeCommand();
}
In your module, associate child commands to the globally available command.
GlobalCommands.MyCompositeCommand.RegisterCommand(command1);
GlobalCommands.MyCompositeCommand.RegisterCommand(command2);
To increase the testability of your code, you can use a proxy class to access the globally available commands and mock that proxy class in your tests.
The following code example shows how to bind a button to the command in WPF.
Execute My Composite Command
Like any MVVM WPF app I have a handful of view models. Each has a few commands. My view implements a Fluent UI (Office ribbon) so there are some items that light up based on the context of the application. The ribbon is a child to the main application.
The basic structure of my app is that it manages a COURSE. A COURSE has multiple MODULES in it, so I have a VM for course & module... and each has commands.
When the app loads I set the data context of the main window to the course so binding the course commands to the ribbon is easy and works fine.
The challenge comes when the user starts to work with a module. When a module is selected from a list the details are shown in another user control. Now... my challenge is how to wire up the commands to the ribbon.
I assume I could have some event handler that programatically wires up the current module's commands to all the relevant controls in the ribbon and removes everything when the context goes away. But that seems like a lot of unnecessary work. Is there a cleaner way of doing this?
I thought about routed commands/events, but someone told me that this wouldn't work because they won't bubble all thew ay up to the Window and back down to the ribbon.
Looking for some guidance here... I'm a bit of a noob to MVVM (but loving it!).
Idea: Introduce a ShellCommands class which is exposed as a service.
public class ShellCommands : IShellCommands
{
public ICommand SaveCommand { get; set; }
...
}
Then the CourseViewModel and the ModuleViewModel can use the same service to register their commands.
public class CourseViewModel : ViewModel
{
public CourseViewModel(IShellCommands shellCommands, ...)
{
this.ShellCommands = shellCommands;
...
}
public IShellCommands ShellCommands { get; private set; }
}
In XAML you can access the service via the ShellCommands property.
<MenuItem Header="Save" Command="{Binding ShellCommands.SaveCommand}"/>
.
More Informations: WPF Application Framework (WAF)