Обработка событий OnNavigatedFrom/OnNavigatedTo в ViewModel

Я пытаюсь выяснить способ, которым мой ViewModel обрабатывает сохранение или восстановление состояния страницы, когда страница перемещается с или на.

Первое, что я попробовал, это добавить поведение EventToCommand на страницу, но события (OnNavigatedFrom и OnNavigatedTo) объявлены защищенными, а EventToCommand не видит привязки к событиям.

Далее я подумал, что попробую использовать класс Messenger для передачи сообщения в ViewModel с использованием кода в коде просмотра позади:

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Messenger.Default.Send<PhoneApplicationPage>(this);
    base.OnNavigatedFrom(e);
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Messenger.Default.Send<PhoneApplicationPage>(this); 
    base.OnNavigatedTo(e);
}

Но у этого, похоже, есть две проблемы: сначала этот код находится в коде за страницей. Во-вторых, ViewModel не может отличить события OnNavigatedFrom и OnNavigatedTo, не создавая набор классов-оболочек для объекта PhoneApplicationPage (см. UPDATE ниже).

Каков наилучший способ MVVM-Light для обработки этих событий?

UPDATE: Мне удалось решить вторую проблему, отправив такие сообщения:

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Messenger.Default.Send<PhoneApplicationPage>(this,"NavigatedFrom");
    base.OnNavigatedFrom(e);
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Messenger.Default.Send<PhoneApplicationPage>(this, "NavigatedTo"); 
    base.OnNavigatedTo(e);
}

и зарегистрировать их следующим образом:

Messenger.Default.Register<PhoneApplicationPage>(this, "NavigatedFrom", false, (action) => SaveState(action));
Messenger.Default.Register<PhoneApplicationPage>(this, "NavigatedTo", false, (action) => RestoreState(action));

Ответ 1

Выполнение команды из кода позади намного чище, чем перехват всего беспорядка сообщений. В конце концов, нет ничего плохого в представлении о его DataContext.

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
        viewModel.NavigatedToCommand.Execute(e.Uri);
    }

    ProfileViewModel viewModel
    {
        get
        {
            return this.DataContext as ProfileViewModel;
        }
    }

Обновление: Передача в NavigationContext.QueryString, вероятно, более полезна, поскольку она уже анализирует параметры и значение.

Ответ 2

Извините за то, что на этот вопрос три года. Да, я все еще использую Silverlight. Хорошо, я хочу записать его в Page code-behind вот так:

// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    this.HandleOnNavigatedTo(e);
}

Я использую метод расширения следующим образом:

public static void HandleOnNavigatedTo(this Page page, NavigationEventArgs e)
{
    var vm = page.DataContext as IPageNavigationViewModel;
    if (vm == null) return;
    vm.HandleOnNavigatedTo(e);
}

Метод расширения подразумевает, что Page должен иметь модель просмотра, которая реализует IPageNavigationViewModel в DataContext. Для меня это компромисс между разделяющими факторами, когда страница знает только о наиболее общих типах данных в Домене. Это интерфейс:

using System.Windows.Navigation;

namespace Fox.Silverlight.ViewModels
{
    /// <summary>
    /// Defines View Model members for frame-navigation pages.
    /// </summary>
    public interface IPageNavigationViewModel
    {
        /// <summary>
        /// Handles the <see cref="Page.OnNavigatedTo"/> method in the View Model.
        /// </summary>
        /// <param name="e">The <see cref="NavigationEventArgs"/> instance containing the event data.</param>
        void HandleOnNavigatedTo(NavigationEventArgs e);

        /// <summary>
        /// Handles the <see cref="Page.OnNavigatedFrom"/> method in the View Model.
        /// </summary>
        /// <param name="e">The <see cref="NavigationEventArgs"/> instance containing the event data.</param>
        void HandleOnNavigatedFrom(NavigationEventArgs e);
    }
}

Ответ 3

Похоже, у вас уже есть решение проблемы. Я также предлагаю следующее:

Посмотрите на использование одного из значений сообщений, представленных в mvvm-toolkit, например:

    NotificationMessage<T>

Вот так:

    Messenger.Default.Send<NotificationMessage<PhoneApplicationPage>>(
new NotificationMessage<PhoneApplicationPage>(this, "Message"));

Ответ 4

Я думаю, что Райан получал, был факт, что вы используете PhoneApplicationPage как отправляемое сообщение вместо фактического сообщения.

Вы делаете это:

Messenger.Default.Send<PhoneApplicationPage>(this);

который отправляет сообщение типа PhoneApplicationPage. Вероятно, вам не нужно отправлять весь файл PhoneApplicationPage в качестве сообщения.

Вы можете сделать некоторые сообщения для NavigatingTo/NavigatingFrom, т.е.

Messenger.Default.Send<NavigatingToMessage>(new NavigatingToMessage());

и др.

Я уверен, что есть миллион лучших способов сделать это, я просто соглашался с тем, как вы все наладили. Лично у моего класса ViewModelBase есть методы NavigatingTo/NavigatingFrom, и я переопределяю соответствующие методы в представлении и отправляю их в свою ViewModel.

Ответ 5

Я делаю образец, используя обновленный ответ внутри вопроса:

MainViewModel.xaml.cs:

public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        Messenger.Default.Register<PhoneApplicationPage>(this, "NavigatedTo", false, ExecuteNavigatedTo);
    }

    // action contains everything you want.
    private void ExecuteNavigatedTo(Page page)
    {
        // example
        bool b = page.NavigationContext.QueryString.ContainsKey("id");
    }
}

MainViewModel.xaml.cs:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    Messenger.Default.Send<PhoneApplicationPage>(this, "NavigatedTo");
    base.OnNavigatedTo(e);
}