I'm writing an application in Prism. I have a user control and contains two <ContentControl> controls. These both have Regions assigned to them. The usercontrol is being hosted in a Window that is being shown using ShowDialog().
I'm adding the one of my views to a region using view discovery and the other I want to inject the view into its region. The view discovery works fine. However when I try and reference the other region to inject the view I get the exception:
KeyNotFoundException
The region manager does not contain the MyRegion region.
When I look in the RegionManagers regions neither the one that I'm trying to inject the view exists or the one being that's using view discovery.
Does it matter that I'm in a different window to the Shell? I thought there was only one RegionManager, but there must be two for my view discovery to still be working...? Or is it because I have two new regions being created later in the applications life cycle? Or is it because the new regions aren't inside the my MainRegion?
EDIT:
After doing some digging it looks like the Region is created but it can't find an instance of the RegionManager so it doesn't get added. Any clues?
Sorted now. I needed to register the region manager in the constructor of my presenter class.
That way the regions in my new window could find my global region manager.
RegionManager.SetRegionManager(view as DependencyObject, regionManager);
Related
I'm trying to find solution for the following problem. I have a WPF app, I used mvvm and prism (most recent version 7) to build it. Here is the draft of the form/dialog I work on:
MainView has region - region1, I inject SubViewA into region1 based on what is selected in treeview. This view represents treeitem content. SubViewA itslef has region - region2, and another view - SubViewB is injected into region2 based on combobox selection.
I use INavigationAware to manage injection to the region.
So to inject view into region I use from MainViewModel the following:
_regionManager.RequestNavigate(regionName, viewName, callBack, parameters);
In the SubViewAViewModel I implement INavigationAware, and to reuse created views I check if view per treeitem was created. To do it I add into parameters a treeitemId and then I check this id in IsNavigationTarget method like this:
bool IsNavigationTarget(NavigationContext navigationContext)
{
// get id parameter from navigationContext.Parameters
// check if subviewA for treeitemId was already shown and return true,
// i use dictionary, where i store ids of all items that were selected in the past
// otherwise return false.
}
The same method I use when I want to inject SubViewB into region 2 of SubViewA. Mostly when user changes dropdown selection new SubViewB is injected.
Now my question is - if I use INavigationAware in SubViewBViewModel and when IsNavigationTarget always returns true - all is good. When I try to reuse views and again chose what to return true or false, then when I select second item in treeview I got an exception: "Region with the given name is already registered" - prism complains that region2 was already registered.
I know that I can have service and always populate data from the service when View is shown, and because of that I don't need to reuse views. But it's more academic question - what is the proper way to solve it?
P.S. I tried to register scoped region manager, but I was not successful, my problem is I don't know where is the best place to create new scoped manager and how to inject it into viewmodel. Parent's ViewModel is not good place to do it, because I have to expose view. If I try to use attached behavior, then it seems, region content is updated before behavior is invoked.
P.S.2 I found an example from pluralsight (by Brian Lagunas), where he creates two shells, but it differs from what I want to achieve. He creates new scope manager per window in the moment when window is created. And also if window itself will have the same structure as I showed above, it will fail too.
P.S.3 I watched recent streams from Brian Lagunas where he is writing outlook from scratch, his approach is based on behavior, he associates some view with dependent views, it works fine, but again in his example dependent views don't contain regions.
Thank you.
For those who are interested in details, you have to watch the following pluralsight courses: pluralsight.pxf.io/XVxR5 & pluralsight.pxf.io/B6X99. One is about multiple shells and another is about tabbed control, which is called 'Prism Problems & Solutions: Mastering TabControl' - this course helped me.
In short, the problem is about how to inject scope region in the main viewmodel. To solve it we have override ScopedRegionNavigationContentLoader plus to control either we want to inject scoped region manager or global one we have to us marking interfaces.
I created another question which is similar to current one: please check Prism 7 throws and exception when working with nested views. It will give you more details.
RequestNavigate(uri) is nice when you want to navigate to region using a string and immediately make it the active view. But what if you want to add a region without activating it? Is there a way to do that with a string?
I have a view model that needs to add some views to a docking control dynamically. These views should not be activated when they are added. Adding a region using Region.Add works but I have to give it a type:
RegionManager.Regions[KnownRegionNames.DockingRegion].Add(typeof(MyView));
I feel like this violates some MVVM principals of making the ViewModel completely independent from views. It's not absolutely terrible since I can probably mock out the region manager in testing, but is there another way?
You'll have to somehow identify the view you want to add to your region, either by type or by string.
You can use the string to resolve it from the container (basically what the region manager does) and add the resolved view to the region.
I'm trying to implement modularity and have some complications. I implemented one module which called ModuleA which shows new childWindow in its initialize function;
public ModuleA()
{
personViewModel = new PersonViewModel();
detail = new ViewDetail(personViewModel);
}
public void Initialize()
{
detail.Show();
}
My problem is that i can't show the view again because of missing opportunity of reload function. My module loaded on demand, i mean that i want to load module when user clicks button so, i do not have a chance to load module at the beginning and control its functions from its own events. then i tried to show view from my application like that;
private void ButtonModelA_Click(object sender, RoutedEventArgs e)
{
this.moduleManager.LoadModule(MyBootstrapper.ModuleAName);
ChildWindow detail = new ModuleA.ViewDetail(new ModuleA.ViewModel.PersonViewModel());
detail.Show();
}
in this way, loading module became unnecessary.
Is there a way to load module from out of it as on demand and show its view multiple times ?
I'm not quite sure how this works in Silverlight, but I think there is a misunderstanding of Prism.
Prism is based on regions. That means that the applications user interface consists of ContentControls (or other region capable controls) that state to be a region. The region manager now adds all views that want to reside inside a specific region into exactly this region.
The modules just have to tell the region manager inside which region the views implemented in the specific module wants to reside:
RegionManager.RegisterViewWithRegion( "RegionName", typeof( View ) );
If the specific region is currently not a part of the user interface, because the view that contains the control that hosts the region isn't part of the user interface itself, the view that wants to resied inside the region cannot be placed inside this region. The region manager just doesn't know of the region. To have the view shown you have to add the control that hosts the region to the user interface by hand.
Another way is to add a specific into a region by hand. Using this approach you don't have to register the view to the region manager. So when the region manager discovers the region it stays empty. Now you can add the view manually into the region using the region manager:
IRegion region = RegionManager.Regions["RegionName"];
region.Add( new View(), "ViewName" );
If you want to place views into a region depending on any state or user action you have to add the into the region by hand. Have a look at the Stock Trader Reference Inplementation. It explains in a very simple manner how to add views to regions triggered by user action.
I have a WPF project based upon Prism Feb 2009 release set up as:
Shell exposes a single ContentControl as "MainRegion"
Another view (user control) defined in the Infrastructure project called SplitView exposes two additional regions "LeftRegion" and "RightRegion" also as ContentControl.
Some of my application's modules need to display their view in the MainRegion (one user control), while others need to display their views (two user controls in a split fashion) in the LeftRegion and RightRegion.
I have tried using scoped regions, assuming that specific Controllers would hold references to the scoped regions. So basically each controller interested in SplitView functionality should instantiate a new SplitView (user control) and activate it in the MainRegion while activating its two user controls in the LeftRegion and RightRegion of the newly created scoped SplitView regions.
I am using MVVM with View Injection to display the views.
Needless to say, something has gone horrifically wrong with this approach.
At runtime I get this exception, "An exception occurred while creating a region with name 'LeftRegion'. The exception was: System.InvalidOperationException: Specified element is already the logical child of another element. Disconnect it first."
Am I correct at assuming that the LeftRegion and RightRegion are trying to register themselves with the main RegionManager every time I instantiate the SplitView?
Sorry about the confusing/verbose post. Any suggestions? Best practices to achieve this?
Thanks in advance,
Ali
The exception of "Specified element is already the logical child..." is what happens when you try to add something to two places in the tree, so I imagine there might be some logical error in your code, or you are adding something twice.
I generally create my sub regions like this:
m_scopedRegionName = Guid.NewGuid().ToString(); /* EXAMPLE ! */
m_scopedRegionManager = m_regionManager.Regions[RegionNames.WORKSPACE_REGION].Add(myViewModel.View, m_scopedRegionName, true);
m_someThingRegion = m_scopedRegionManager.Regions[RegionNames.SOME_THING_REGION];
Then I add any new stuff into the "m_someThingRegion".
I have a composite WPF application. In one of my modules I want to make a wizard and have the steps show up in a region so I can switch between the steps easier. Originally I had this wizard showing up in a tab region and the nested region worked fine. Now I want to make it into a modal dialog box, but after I open it the inner region never gets registared with the region manager; So I can't add my wizard steps.
I was under the impression that the region manager was global, and just adding cal:RegionManager.RegionName="WizardSteps" would do it, but apparently not.
If i pass the region manager to the view I might be able to use it...Does anyone know how to add a region to a ContentControl in code behind?
The problem is that regions search up the visual tree for the RegionManager attached property, and then register themselves with that manager. In the main window that's fine, but in a child window this doesn't happen.
In the Bootstrapper, after the shell is created, the following code is performed.
RegionManager.SetRegionManager(shell, this.Container.Resolve<IRegionManager>());
RegionManager.UpdateRegions();
To get the region manager to work with your child window do the same thing right after you've created the window.
EDIT
To set the region name of a control, you also set the attached property of the RegionManager, like so...
RegionManager.SetRegionName(control, "MyRegion");
However you can do this in xaml aswell. The reason why your regions in a separate window don't work is because the RegionManager needs to be set on the base window, like I showed above.
It is actually quite simple.
In your popup xaml add a regionname as you do in the shell.
Then in the popups constructor, add the following call:
public Popup(IRegionManager regionManager)
{
InitializeComponent();
RegionManager.SetRegionManager(this,regionManager);
}
This works for me in Prism v.1 - shouldn't be too much different in later versions.
I found something thats almost working. I'm sure if i could bind the region's active view to the contentContol's content property then it would work, but I haven't managed that yet.
IRegionManager MyRegionManager = container.Resolve<IRegionManager>();
SingleActiveRegion newRegion = new SingleActiveRegion();
MyRegionManager.Regions.Add("WizardSteps", newRegion);
//Binding
Binding myBinding = new Binding("ActiveViews");
myBinding.Source = newRegion;
view.stepControl.SetBinding(ContentControl.ContentProperty, myBinding);