2
votes

Using UWP and MVVM Light, I have a program that uses the same page and ViewModel multiple times with different data. Each page/ViewModel pair is assigned a matching ID so they can more easily reference each other. On the page is a button that shows a ContentDialog.

When opening one of the pages for the first time, the ContentDialog opens properly, but if the page is left and then returned to, calling ShowAsync for ContentDialog causes the very descriptive ArgumentException: 'Value does not fall within the expected range.'

ViewModel

public RelayCommand LockButtonCommand => new RelayCommand(() => lockButtonClicked());

private void lockButtonClicked()
{
    System.Diagnostics.Debug.WriteLine("Button clicked on VM " + myID);
    Messenger.Default.Send(new ShowPassphraseDialogMessage(), myID);
}

Page Code-Behind

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    if (!idsRegistered.Contains(myID))
    {
        Messenger.Default.Register<ShowPassphraseDialogMessage>(this, myID, showDiag);

        System.Diagnostics.Debug.WriteLine("Registered messages for " + myID);

        idsRegistered.Add(myID);
    }
}

private async void showDiag(object msg)
{
    System.Diagnostics.Debug.WriteLine("Showing dialog for " + myID);
    if (activePageID != myID)
        return;
    await PassphraseDialog.ShowAsync();
}

ContentDialog XAML

<ContentDialog x:Name="PassphraseDialog"
                       x:Uid="Page_PassDialog"
                       PrimaryButtonText="Enter"
                       SecondaryButtonText="Cancel"
                       PrimaryButtonCommand="{x:Bind ViewModel.PassDialogEnterCommand}"
                       Closing="PassphraseDialog_Closing">
    <StackPanel>
        <TextBlock x:Uid="Page_PassDialogText" />
        <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
            <PasswordBox x:Name="PassphraseDialogInput"
                         Password="{x:Bind ViewModel.PassDialogInputText, Mode=TwoWay}"
                         helper:EnterKeyHelpers.EnterKeyCommand="{x:Bind ViewModel.PassDialogEnterCommand}" />
            <ProgressRing Margin="8,0,0,12"
                                  IsActive="{x:Bind ViewModel.PassDialogLoading, Mode=OneWay}" />
        </StackPanel>
        <TextBlock Text="{x:Bind ViewModel.PassDialogErrorText, Mode=OneWay}"
                   Foreground="{ThemeResource SystemErrorTextColor}"/>
    </StackPanel>
</ContentDialog>

My first concern has been that somehow in my setup the showDiag method was being called multiple times. So I've done several tests to know the following:

  1. The message is being registered once for each ID in the code-behind.
  2. The message is sent once.
  3. showDiag is called once.

I'm not sure if this is related, but I found that with this setup the page constructor is being called every time that page is navigated to.

1

1 Answers

2
votes

When you need to use the same page multiple times, please cache the page.

Try this:

private bool _isInit = false;
public MyPage()
{
    this.InitializeComponent();
    NavigationCacheMode = NavigationCacheMode.Enabled;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    if (e.NavigationMode == NavigationMode.Back || _isInit)
        return;

    // Do Somethings...

    _isInit = true;
}

When the page is cached, it maintains the current state of the page, which means two things:

  1. The ContentDialog you create will not be replaced by the ContentDialog of the newly created page, resulting in an error.
  2. When leaving and returning from the page, the page will not be repeatedly created.

Best regards.