Добавление в журнал специальных элементов

41

Вместе с методом RemoveBackEntry() класс NavigationService также предоставляет метод AddBackEntry(). Этот метод позволяет сохранять в списке предыдущих страниц "виртуальные" записи. Например, предположим, что имеется одна страница, которая позволяет пользователю выполнять довольно сложную задачу по конфигурированию. Если нужно сделать так, чтобы пользователь мог возвращаться к предыдущему состоянию этого окна, его можно сохранить с помощью метода AddBackEntry(). Несмотря на то что страница всего одна, она может иметь несколько связанных записей в списке.

Вопреки возможным ожиданиям, при вызове метода AddBackEntry() объект JournalEntry передавать не требуется. (На самом деле класс JournalEntry имеет защищенный конструктор, поэтому создать его экземпляр не получится.) Вместо этого понадобится создать специальный класс, унаследованный от абстрактного класса System.Windows.Navigation.CustomContentState и сохраняющий всю необходимую информацию. Например, взгляните на приложение, показанное на рисунке, которое позволяет перемещать элементы из одного списка в другой:

Динамический список

Теперь предположим, что состояние этого окна должно сохраняться при каждом перемещении элемента из одного списка в другой. Первое, что потребуется — это класс, унаследованный от CustomContentState и отслеживающий эту необходимую информацию. В данном случае нужно просто записать содержимое обоих списков. Поскольку этот класс будет сохраняться в журнале (для того, чтобы страница могла при необходимости "восстанавливаться"), он должен допускать сериализацию:

[Serializable()]
    public class ListSelectionJournalEntry : CustomContentState
    {
        private List<String> sourceItems;
        public List<String> SourceItems
        {
            get { return sourceItems; }
        }

        private List<String> targetItems;
        public List<String> TargetItems
        {
            get { return targetItems; }
        }
        ...
}

Это дает хорошее начало, но все равно еще нужно много чего сделать. Например, вряд ли захочется, чтобы страница появлялась в хронологии навигации с одним и тем же заголовком множество раз. Потребуется использовать какое-то более описательное имя. Для этого придется переопределить свойство JournalEntryName.

В данном примере никакого очевидного и логичного способа для описания состояния обоих списков нет. Поэтому имеет смысл позволить странице самой выбирать имя при сохранении записи в журнале. В таком случае страница сможет добавлять описательное имя на основе самого последнего действия (вроде Added Blue или Removed Yellow). Для реализации такого проектного решения необходимо сделать свойство JournalEntryName зависимым от переменной, установить которую можно непосредственно в конструкторе:

private string _journalName;
public override string JournalEntryName
{
    get
    {
         return _journalName;
    }
}

Система навигации WPF будет обращаться к свойству JournalEntryName для получения имени, которое должно быть показано в списке.

Следующий шаг состоит в переопределении метода Replay(). WPF вызывает этот метод, когда пользователь переходит к записи в списке предыдущих или следующих страниц, позволяя применять предыдущее сохраненное состояние.

Существуют два подхода, которые можно использовать в методе Replay(). Первый — извлечь ссылку на текущую страницу с помощью свойства NavigationService.Content, а затем привести эту страницу к типу соответствующего класса страницы и вызвать любой требуемый для реализации задуманного изменения метод. Второй подход (который иллюстрируются здесь) — полагаться на обратный вызов:

public delegate void ReplayListChange(ListSelectionJournalEntry state);

private ReplayListChange replayListChange;

public override void Replay(NavigationService navigationService, NavigationMode mode)
{
     this.replayListChange(this);
}

public ListSelectionJournalEntry(List<String> sourceItems, List<String> targetItems,
     string journalName, ReplayListChange replayListChange)
{
     this.sourceItems = sourceItems;
     this.targetItems = targetItems;
     this._journalName = journalName;
}

Чтобы добавить эту функциональность к странице, потребуется выполнить три описанных ниже шага:

Интерфейс IProvideCustomContentState является часто пропускаемой, но очень существенной деталью. Когда пользователь выполняет навигацию с помощью списка следующих или предыдущих страниц, должны происходить две вещи: страница должна добавить текущее представление в журнал (с помощью IProvideCustomContentState), а затем восстановить выбранное представление (с помощью обратного вызова ListSelectionJournalEntry).

Для начала при каждом щелчке на кнопке Add (Добавить) нужно создать новый объект ListSelectionJournalEntry и вызвать метод AddBackReference(), чтобы предыдущее состояние сохранилось в хронологии. Этот процесс выносится в отдельный метод для того, чтобы его можно было использовать в нескольких местах на странице (например, при щелчке на кнопке Add (Добавить) или Remove (Удалить)):

 private void btn_Add_Click(object sender, RoutedEventArgs e)
        {
            if (one_lbx.SelectedIndex != -1)
            {
                // Определяем подходящее имя для использования в хронологии навигации
                NavigationService nav = NavigationService.GetNavigationService(this);
                string itemText = one_lbx.SelectedItem.ToString();
                string journalName = "Added" + itemText;

                // Обновляем журнал
                nav.AddBackEntry(GetJournalEntry(journalName));

                one_lbx.Items.Add(itemText);
                two_lbx.Items.Remove(itemText);
            }
        }
private ListSelectionJournalEntry GetJournalEntry(string journalName)
        {
            List<String> source = GetListState(lstSource);
            List<String> target = GetListState(lstTarget);

            return new ListSelectionJournalEntry(
              source, target, journalName, Replay);
        }
Специальные записи в журнале
Пройди тесты
Лучший чат для C# программистов