Командно-ориентированная навигация в xaml-приложениях
Недавно мы уже узнали о принципе прямых инжекции и эффективном связывании вью-моделей с представлениями, а также о том, каким образом создавать расширения привязки. Продолжим исследование библиотеки Aero Framework и рассмотрим ещё один архитектурный вопрос.Навигация между представлениями (экранами) в xaml-ориентированных приложениях довольно важная и интересная задача. Особенно это касается её реализации в рамках паттерна MVVM. В идеале вью-модели не должны содержать никаких прямых ссылок на представления, чтобы быть кросс-платформенными и сохранялась возможность повторного их использования в нескольких проектах. Сегодня мы узнаем, как этого достичь.С xaml-разработкой очень тесно связана концепция команд (Commands). Сегодня мы не будем рассматривать реализацию этого паттерна в библиотеке Aero Framework, поскольку она интуитивно понятна и уже кратко освещёна в документации.
Несмотря всю архитектурную значимость вопроса существует поразительно простой и надёжный способ навигации… Возможно, вы даже не поверите сразу, что это настолько просто.
В большинстве случаев навигация происходит после нажатия на визуальный элемент и исполнения на нём какой-либо команды, так почему не передать идентификатор (адрес или тип) целевого представления в качестве параметра команды? Ведь это очень легко и логично.
public UserData UserData { get { return Get (() => UserData); } set { Set (() => UserData, value); } }
public virtual void Expose () { this[() => Name].PropertyChanged += (sender, args) => Context.Login.RaiseCanExecuteChanged (); this[() => Password].PropertyChanged += (sender, args) => Context.Login.RaiseCanExecuteChanged (); this[() => Name].Validation += () => Error = Name == null || Name.Length < 3 ? Unity.App.Localize("InvalidName") : null; this[() => Password].Validation += (sender, args) => Error = Name == null || Name.Length < 4 ? Unity.App.Localize("InvalidPassword") : null;
this[Context.Login].CanExecute += (sender, args) => { args.CanExecute = string.IsNullOrEmpty (Error); // Error contains last validation fail message };
this[Context.Login].Executed += async (sender, args) => { try { UserData = await Bank.Current.GetUserData (); Navigator.GoTo (args.Parameter); } catch (Exception exception) { Error = Unity.App.Localize (exception.Message); } };
this[Context.Logout].Executed += (sender, args) => { UserData = null; Navigator.RedirectTo (args.Parameter); }; } } После навигации представление с помощью механизма прямых инжекций получает нужные вью-модели из контейнера.Всё это отчасти напоминает веб-навигацию, где каждая страница запрашивается у сервера по uri. Но как же в нашем случае передавать параметры? В связи с применением принципа прямых инжекций отпадает реальная необходимость в передаче каких-либо параметров, ведь каждое представление, по сути, может получить доступ к любой вью-модели и извлечь необходимую информацию непосредственно из неё!
Возможно, вам сразу захочется придумать каверзный пример, а что если возможен переход на несколько представлений и заранее не известно на какое именно… Но всё это решается элементарно несколькими способами: можно создать ряд кнопок, где у каждой в параметре команды находится уникальный идентификатор представления, и в зависимости от логических условий эти кнопки дизайблятся и/или скрываются; в другом случае допустимо создание конвертера и привязка к свойству CommandParameter.
Различных вариации может быть придумано очень много, но сама идея остаётся неизменной — идентификатор нужного представления при навигации передаётся в параметре команды. Но, возможно, кто-то возразит, а что если нужно передавать в команду и другой параметр? Однако и тут есть выход, запросто можно передавать множество аргументов в команду:
public class Set: ObservableCollection
Большое спасибо за внимание! Если есть вопросы и альтернативные мнения, то свободно выражайте их в комментариях!