WPF Prism - To use Scoped Regions or not? - wpf

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".

Related

Working with nested views using Prism with IsNavigationTarget which can return false

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.

How to add a region without knowing the type and without activating it

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.

Does calling a ScriptableMember method on a Silverlight control from JavaScript create a new Instance of the control?

I have a Silverlight control with a method named DoSomething() decorated with the <ScriptableMember()> attribute. I then call this method successfully from JavaScript and proved by a little message box that apprears from the SL side that says "Method Called!".
Point is all that works. The problem I am having is that prior to calling this method I build up an ObservableCollection on the Silverlight control containing 1..n FileInfo objects. This works fine too and builds up as I add files to it. Each time I add a file, a messagebox tells me the count from Silverlight (i.e. "Count = 2").
Now the problem: when I call the method DoSomething() from JS and access that ObservableCollection the count = 0! To see what is going on I placed a message in the Silverlight control's constructor to see if it gets entered upon being called from JS, and indeed it does and appears to recreate the control.
If this is the case it kind of makes sense that my ObservableCollection has a count = 0 because it is not the same control instance where I built up the FileInfo collection.
So how in the world do I preserve the collection, and why would simply calling a method exposed to JS from Silverlight, recreate the control and not allow me to access it's given state? I don't want a new control, I need to manipulate it as-is. Or am I off base and doing something else wrong to cause this beahvior?
Thoughts? Thanks!
It turns out the instance registered was the culprit. The MSDN examples show registering a new instance of the type, but in my case I needed the actual instance of the Page Control itself which solved the problem.
So at the completion of my page's initialization, I could register the current page's instance like below:
HtmlPage.RegisterScriptableObject("SLControl", Me)
This allowed me from JS to access the control in it's current state which included all objects in the ObservableCollection as required. I blogged about this topic with code examples and the article below expands on this situation:
Get A Silverlight Control's Current Instance For Communicating Via The HTML Bridge:
http://allen-conway-dotnet.blogspot.com/2012/03/get-silverlight-controls-current.html

Cannot find Region in RegionManager (using PRISM)

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

WPF - CAL - Multiple parents for single instance of control?

I am working on a PRISM / CAL solution, but the problem may be WPF specific:
If I create one instance of an control (e.g. TextBlock) and add it as child to a StackPanel, there is no way to add it as "child" to another StackPanel (parent already set error). I kind of understand the reason (it also occurs when using the RegionManager).
But what is the suggested way if a visual control is very complex and should be created only one time and used in two places? I agree that is does not really make sense to show an identical control 2 times on the screen, but there might be cases where it is useful (e.g. an "Close All" Button).
I know that in the button case, I should just create two buttons both databound to one ICommand. But does this rule also apply with much more complex controls (always create new instances)...
I stumbled on this problem when creating a layout switcher, which creates the button list and the stack panel for each GUI seperately, but uses a static ObservableCollection of buttons as source (which causes strange bugs)..
Any ideas on this topic?
Chris
This is normally handled by templates. That is, you abstract out the data into a particular type, and associate a template with that type. Then you place the instance of that data any number of times into your visual tree and have WPF render it with the template.
In short, don't add a TextBlock to your StackPanel. Instead, add an instance of your data type (eg. Customer) and associate a DataTemplate with the Customer type. There is no way to parent the same UIElement in multiple places.
You can add your control (or collection of controls) as a resource and refer to them via binding in your controls. This will implicitly create a copy (they will be Freezable and WPF will copy them).
Generally you should be using DataTemplates as Kent suggests, but if you have a special case, this will likely work.

Resources