Communication between Modules/VMs

Topics: Prism v4 - Silverlight 4
Jan 25, 2011 at 3:45 PM

Hi,

I have a delegate command on my viewmodel within ModuleA, which needs to call a function on another viewmodel in ModuleB.

I have been studying this piece of PRISM documentation 

http://msdn.microsoft.com/en-us/library/ff921122(v=PandP.40).aspx

but I am still unsure about what strategy is the best. The documentation says to use commanding 

Use [commanding ]when there is an expectation of immediate action from the user interaction.

But then I am thinking shall I create a static delegateCommand and bind the button directly to the global delegate command rather than to the underlying command in the viewmodel?
Or shall I use an AggregateEvent? Which is really very similar the previous approach; created on the Infrastructure project and needs to be resolved through the IoC.

Isn't the latter a better choice due its weak event nature while the delegatecommand holds a hard reference?  The way the documentation implies teh difference, it almost sounds like AggregateEvents are slighly delayed...


Many Thanks for advice,
Houman

 

 

Developer
Jan 25, 2011 at 4:41 PM

Hi Houman,

As you've mentioned, it could be useful for you to use the Event Aggregator to achieve your scenario. Even so, you could use a Delegate Command and publish a message through the event aggregator in the Execute method of your command.

As for the CompositePresentationEvents, they shouldn't imply a delay in the execution of the operation you've defined in the subscribers to that event. However, although possible, the Event Aggregator isn't designed to the purpose of achieving a direct action-reaction communication. They are mostly meant to achieve communication between different modules.

You might find the following thread useful to clarify the difference between Delegate Commands and Event Aggregation:

EventAgg or COmmand

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Jan 27, 2011 at 12:13 PM

Hi Guido,

 

Thank you very much for your help. I have studied the documentation and your example these days.  In fact the way of using Event Aggregator thourgh the delegate command proved very successful. (Perhaps the best and only way until Prism 3)

Now I also understand how the RegionContext could be utilized to achieve the same thing but simpler. I think this feature is new to Prism 4, and I found it very useful and like to share it here:

The scenario is that you have a NavigationModule that has a button bound to a Delegate Command. When pressed it would navigate (if not existing then instantiate) a View within MainModule.
NavigationModule has no idea about the MainModule and we won't be using any global Composite Commands nor any global Event Aggregator. Its Pure magic. :)

For now I have switched back to Unity, with MEF its even simpler.

First you would make your ViewModel to implement INavigationAware. Three methods need to be implemented. Leave all empty but the IsNavigationtarget(..). This one should return false, so that everytime the command is called a new instance of the view is created.

Within the Initialize() of the MainModule you would have to register the type of the View.

_Container.RegisterType<Object, ContactView>("ContactView");

Now that the view can be resolved by IoC, we can just make the call in the Delegate Command's method on NavigationModule, which has no idea about the View nor its region. It just says put that View I heard about in that region I heard about.

_regionManager.RequestNavigate("MainRegion", new Uri("ContactView", UriKind.Relative);

The IoC container does the magic, no need for composite commands nor any events.  The Uri points to the view indirectly and the IoC can resolve the view on the MainModule's MainRegion, since the IsNavigationTarget() returns false, it would in this case always create a new View in that region. A logic can be implemented in that IsNavigationTarget() to return true if the Id's are equal. In that case it would navigate to an existing view rather than creating a new one. Perfect for TabControl scenarios.

I hope this helps someone else, and if I am wrong with anything please correct me.

Guido,

I still have another Navigation related question for you.  I do understand how the Confirmation/Cancellation works. But it seems to be only connected with Navigation, hence you get a chance to cancel or confirm when navigating away. However what if I wanted to add the confirmation to the CloseButton I have created on each TabItem?  The summary says: 

1.Navigation operation is initiated via a RequestNavigate call.

What do you suggest? Is there any other pattern implemented for this or do I have to write my own?

Many Thanks,
Houman

 

Developer
Jan 27, 2011 at 4:46 PM

Hi Houman,

I'm glad that you've benefited from Navigation to address your scenario. Thank you for sharing your insight about this, as it might be useful for other users that might be pursuing a similar scenario.

As for the scenario you're describing about confirmation/cancellation, the IConfirmNavigationRequest interface in the Prism Library "Provides a way for objects involved in navigation to determine if a navigation request should continue.", so it isn't meant to provide a means of confirming the close of a tab. In case you'd like to initiate the close action from within the ViewModel (as a result of the execution of a DelegateCommand, for example), you could make use of an InteractionRequest<Confirmation>. You can read more about it in the "Using Interaction Request Objects" section of this chapter from the Prism MSDN documentation.

You might also find the following threads useful to find some possibilities regarding the ways to implement your scenario:

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Jan 30, 2011 at 12:16 AM
Edited Jan 30, 2011 at 12:19 AM

Hi Guido,

 

Many thanks for pointing me into the right direction.

I have been working since three days on this specific problem and finally I have solved it in a clean way. While the first link is tempting, it also throws in the kitchen sink in there.  I have managed doing it much simpler. I think eventually I would have to blog about it. I have further down one Prism question to you. Would be great if you could help me with this. I have used this link as help, but I had to change the WPF code to Silverlight.  http://blogs.infosupport.com/blogs/willemm/archive/2008/07/31/Creating-closeable-tabitems-for-use-in-CompositeWPF.aspx

I have extended the TabItem header with the X button and attached a Command in there.  Since Silverlight doesn't allow RelativeSource on anything else than Self and TemplatedParent, I had to pass the Button itself as a parameter to the Command in order to go up the visualtree on the view model.

 

<sdk:TabControl regions:RegionManager.RegionName="MainRegion" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">                
				<regions:TabControlRegionAdapter.ItemContainerStyle>
					<Style TargetType="sdk:TabItem">
						<Setter Property="HeaderTemplate">
							<Setter.Value>
								<DataTemplate>									
                                    <StackPanel Orientation="Horizontal">                                        
                                        <TextBlock Text="{Binding TabCaption}"/>
                                        <Button Margin="8,0,0,0" 
                                                Command="{Binding CloseContactCommand}" 
                                                CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}" 
                                                HorizontalContentAlignment="Center" 
                                                VerticalContentAlignment="Center">
                                            <Grid>
                                                <Canvas Width="8" Height="8">
                                                    <Line X1="2" X2="6" Y1="2" Y2="6" Stroke="Black" StrokeThickness="1"/>
                                                    <Line X1="6" X2="2" Y1="2" Y2="6" Stroke="Black" StrokeThickness="1"/>
                                                </Canvas>
                                            </Grid>
                                        </Button>                                        
                                    </StackPanel>
                                </DataTemplate>
							</Setter.Value>
						</Setter>
					</Style>					
				</regions:TabControlRegionAdapter.ItemContainerStyle>				
			</sdk:TabControl>

 

 

 

public static T FindParentControl<T>(DependencyObject outerDepObj) where T : DependencyObject
        {
            DependencyObject dObj = VisualTreeHelper.GetParent(outerDepObj);
            if (dObj == null)
                return null;

            if (dObj is T)
                return dObj as T;

            while ((dObj = VisualTreeHelper.GetParent(dObj)) != null)
            {
                if (dObj is T)
                    return dObj as T;
            }

            return null;
        }

 

 

on the view model a DelegateCommand of type object is waiting to retrieve the button.

 

 private void CloseTab(object obj)
        {
            var test = (((ContentControl)(obj)).Parent);
            var parent = Helper.FindParentControl<TabItem>(test);

            if (parent != null)
            {
                FrameworkElement view = (parent as TabItem).Content as FrameworkElement;
                string regionName = RegionManager.GetRegionName(view);
                if (string.IsNullOrEmpty(regionName))
                    regionName = Constants.MainRegion;
 
                _regionManager.Regions[regionName].Remove(view);
            }
        }

 

This way I find the TabItem and can remove the view from the initial ItemContainer.  The beauty of this is, you may implement an IInteraction and ask the user if they really want to close the tab and you may also unsubscribe from the composite commands at this stage.

Now my question, for somereason RegionManager.getRegionName() doesnt find the region name for the view wrapped by the tabitem. The name remains null and I put a hack in there to get the name from the constant.  It is not a big deal in my application. But it would be nice to have the option of finding the regionname of a given view. Do you  know what I am doing wrong? 

BTW, do you see any problem regarding separation of concerns that I am handling my view specific code - although generic - in my view model?  I see no cleaner way to do this in this scenario.

Many Thanks,
Houman 

 

Jan 31, 2011 at 9:51 PM

Hi Houman,

Based on my understanding of your scenario, it might be that you need to pass the ContentControl defined as the region, in order to obtain the region name. According to the Prism documentation, the parameter of GetRegionName method is “the object to adapt, this is typically a container (i.e a control)” not the view itself. That might be the reason why your string regionName variable is always null.

In the other hand, regarding the separation of concerns of this approach. I think it is ok, as far as your scenario remains testeable.

Thanks,

Miguel Bronzovic
http://blogs.southworks.net/mbronzovic