Prevent ConfirmNavigationRequest when navigating to same type of view as Active view

Topics: Prism v4 - WPF 4
Dec 2, 2011 at 2:46 PM

So I have a 2-region WPF application (toolbar region and main region).  The toolbar has 2 buttons that where each button loads a different view (Button1View and Button2View...original, I know) into the "main region".  Both viewmodels implement IConfirmNavigationRequest, but only Button1ViewModel raises an InteractionRequest.  This is working well...when I click on button 1, Button1ViewLoads.  Click on button 2, I get the confirmation request...both cases on the continuationCallback work (true and false).  My question is, suppose I have Button1View loaded, and I click on Button 1 again.  How do I prevent it from calling the ConfirmNavigationRequest again?  Can I verify in my MainToolbarViewModel (where the navigation request begins) that the view I'm going to is the one already loaded?  Here's some code:

MainToolbarView :

 

<UserControl x:Class="GPETPrismAttempt1.MainToolbarView"
             ...
             mc:Ignorable="d" 
             d:DesignHeight="72" d:DesignWidth="267" Background="Transparent">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="72" />
            <ColumnDefinition Width="72" />
            <ColumnDefinition Width="72" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.Resources>
            <Style x:Key="ToolbarButton" TargetType="Button" BasedOn="{StaticResource ToolbarButtonStyle}">
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="BorderBrush" Value="{x:Null}"/>
            </Style>
        </Grid.Resources>
        <Button Grid.Column="1" Style="{StaticResource ToolbarButton}" Command="{Binding Path=NavigateToViewCommand}" CommandParameter="Button1View">
            <Button.Content>
                <Image Source="{StaticResource ItemsImage}" HorizontalAlignment="Center" VerticalAlignment="Center" />
            </Button.Content>
        </Button>
        <Button Grid.Column="2" Style="{StaticResource ToolbarButton}" Command="{Binding Path=NavigateToViewCommand}" CommandParameter="Button2View">
            <Button.Content>
                <Image Source="{StaticResource ItemsImage}" HorizontalAlignment="Center" VerticalAlignment="Center" />
            </Button.Content>
        </Button>
        <Button Grid.Column="3" Style="{StaticResource ToolbarButton}">
            <Button.Content>
                <Image Source="{StaticResource ItemsImage}" HorizontalAlignment="Center" VerticalAlignment="Center" />
            </Button.Content>
        </Button>
    </Grid>
</UserControl>

 

MainToolbarViewModel :
    [Export(typeof(MainToolbarViewModel))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class MainToolbarViewModel : NotificationObject
    {
        private readonly IRegionManager regionManager;

        public ICommand NavigateToViewCommand { get; private set; }

        [ImportingConstructor]
        public MainToolbarViewModel(IRegionManager regionManager)
        {
            if (regionManager == null)
            {
                throw new ArgumentException("regionManager");
            }

            this.regionManager = regionManager;

            NavigateToViewCommand = new DelegateCommand<string>(NavigateToView);
        }

        private void NavigateToView(string viewToDisplay)
        {
            this.regionManager.RequestNavigate(RegionNames.MainRegion, new Uri(string.Format("{0}", viewToDisplay), UriKind.Relative), NavigationCompleted);
        }

        private void NavigationCompleted(NavigationResult result)
        {
            Console.WriteLine("Navigated COMPLETED...result: {0}", result.Result.ToString());
        }
    }

Button1View.xaml : 
<UserControl x:Class="GPETPrismAttempt1.Button1View"
             ...
             xmlns:interactionRequest="clr-namespace:Zeller.Infrastructure.MEF.InteractionRequestImplementation;assembly=Zeller.Infrastructure.MEF"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <DataTemplate x:Key="ConfirmNavigateAwayTemplate">
            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding}"/>
        </DataTemplate>
    </UserControl.Resources>
    
    <Grid>
        <ei:Interaction.Triggers>
            <prism:InteractionRequestTrigger SourceObject="{Binding ConfirmNavigateAwayRequest}">
                <interactionRequest:InteractionDialogAction ContentTemplate="{StaticResource ConfirmNavigateAwayTemplate}">
                    <interactionRequest:InteractionDialogAction.Dialog>
                        <interactionRequest:ConfirmationLocalModalInteractionDialog/>
                    </interactionRequest:InteractionDialogAction.Dialog>
                </interactionRequest:InteractionDialogAction>
            </prism:InteractionRequestTrigger>
        </ei:Interaction.Triggers>
        <Button Height="22" Width="100">Show Child View</Button>
    </Grid>
</UserControl>

Button1ViewModel :
[Export(typeof(Button1ViewModel))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class Button1ViewModel : ZNavigationViewModelBase
    {
        private readonly InteractionRequest<Confirmation> confirmNavigateAwayRequest;

        public InteractionRequest<Confirmation> ConfirmNavigateAwayRequest
        {
            get { return this.confirmNavigateAwayRequest; }
        }

        [ImportingConstructor]
        public Button1ViewModel()
        {
            this.confirmNavigateAwayRequest = new InteractionRequest<Confirmation>();
        }

        public override bool KeepAlive
        {
            get { return true; }
        }

        public override bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return true;
        }

        public override void OnNavigatedFrom(NavigationContext navigationContext)
        {
            Console.WriteLine("Navigated FROM Button1ViewModel");
        }

        public override void OnNavigatedTo(NavigationContext navigationContext)
        {
        }

        public override void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
        {
            string temp = navigationContext.Uri.ToString();
            if (/*normally we'd check if we need to offer the confirmation (e.g. changes made?)*/ true)
                this.confirmNavigateAwayRequest.Raise(new Confirmation { Content = "Going somewhere?", Title = "HEY!" }, 
                    c => 
                        {
                            continuationCallback(c.Confirmed);
                        });
            else
                continuationCallback(true);
        }
    }

Developer
Dec 2, 2011 at 6:58 PM
Edited Dec 2, 2011 at 6:58 PM

Hi,

The details of how to implement this depends mostly of your personal preferences and requirements of your scenario.

As a possible approach to achieve this, you could check if the DataContext of the view where you are going to navigate is the Button1ViewModel of the current view in the ConfirmNavigationRequest method and, if so, cancel the navigation request. As the NavigationContext that is received as a parameter does not contain the view where you are going to navigate, you could use the Service Locator obtain an instance of the view using the contract name of the view, which can be found in the Uri property of the NavigationContext parameter:

        public override void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
        {
            string temp = navigationContext.Uri.ToString();

            object targetView = ServiceLocator.Current.GetInstance<object>(temp);

            FrameworkElement targetViewAsElement = targetView as FrameworkElement;

            // If we are going to navigate to a view using this view model, cancel the navigation request
            if ((targetViewAsElement != null) && (targetViewAsElement.DataContext == this))
            {
                continuationCallback(false);
            }
            else
            {
                if (/*normally we'd check if we need to offer the confirmation (e.g. changes made?)*/ true)
                {
                    this.confirmNavigateAwayRequest.Raise(new Confirmation { Content = "Going somewhere?", Title = "HEY!" }, 
                        c => 
                            {
                                continuationCallback(c.Confirmed);
                            });
                }
                else
                {
                    continuationCallback(true);
                }
            }
        }

However, take into account that this approach might cause an unexpected behavior in your application if, for example, different views use the same Button1ViewModel view model in the same region.

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini

Dec 2, 2011 at 7:26 PM

Thanks for your quick response.  I think your suggestion will work fine for my scenario.  Would you agree it's safe to say that multiple views shouldn't be using the same viewmodel instance anyway, so as long as I abide by that "rule" I'll be good with your solution?

Developer
Dec 5, 2011 at 5:21 PM

Hi,

Based on my understanding, as long as an instance of a view model is not used for multiple views, the aforementioned approach might be useful to achieve the requirements of your scenario.

Regards,

Damian Cherubini
http://blogs.southworks.net/dcherubini