Я пытаюсь следовать шаблону MVVM в приложении WPF настолько хорошо, насколько могу, в основном, чтобы иметь возможность создавать модульные тесты для моей логики ViewModel.
В большинстве случаев привязка данных между свойствами ViewModel и свойствами визуальных элементов работает отлично и легко. Но иногда я сталкиваюсь с ситуациями, когда я не вижу очевидного и простого пути, в то время как решение для доступа и управления элементами управления из кода очень просто.
Вот пример того, что я имею в виду: Вставка фрагмента текста в TextBox
в текущей позиции каретки
Так как CaretIndex
не является свойством зависимости, он не может быть привязан непосредственно к свойству ViewModel. Здесь это решение, чтобы обойти это ограничение, создав свойство зависимости. И здесь - это решение для этого в кодировке. В этой ситуации я предпочел бы использовать код. Еще одна проблема, с которой я недавно столкнулся, - это привязка динамического набора столбцов к документу данных WPF. Было ясно и просто программировать код. Но для MVVM-дружественного подхода привязки данных я мог только найти работу вокруг в нескольких блогах, которые все выглядели довольно сложными для меня и имели различные ограничения в том или ином аспекте.
Я не хочу, чтобы архитектура MVVM всегда отличалась от логики логики кода любой ценой. Если объем работы слишком велик, для решения, совместимого с MVVM, требуется много кода, который я не полностью понимаю (я все еще начинаю WPF) и слишком трудоемкий. Я предпочитаю решение для кода и жертву автоматическая проверка нескольких частей моего приложения.
По указанным прагматическим причинам я смотрю теперь на "шаблоны", чтобы сделать контролируемое использование кода в приложении без нарушения архитектуры MVVM или не нарушая его слишком.
До сих пор я нашел и протестировал два решения. Я рисую грубые эскизы с примером позиции Caret Position:
Решение 1) Дайте ViewModel ссылку на представление через абстрактный интерфейс
-
У меня был бы интерфейс с методами, которые будут реализованы в представлении:
public interface IView { void InsertTextAtCaretPosition(string text); } public partial class View : UserControl, IView { public View() { InitializeComponent(); } // Interface implementation public void InsertTextAtCaretPosition(string text) { MyTextBox.Text = MyTextBox.Text.Insert(MyTextBox.CaretIndex, text); } }
-
Внедрить этот интерфейс в ViewModel
public class ViewModel : ViewModelBase { private readonly IView _view; public ViewModel(IView view) { _view = view; } }
-
Выполнение кода с помощью обработчика команд ViewModel с помощью методов интерфейса
public ICommand InsertCommand { get; private set; } // Bound for instance to a button command // Command handler private void InsertText(string text) { _view.InsertTextAtCaretPosition(text); }
Чтобы создать пару View-ViewModel, я бы использовал инъекцию зависимости, чтобы создать конкретный вид и ввести его в ViewModel.
Решение 2) Выполнение методов кода с помощью событий
-
ViewModel является издателем специальных событий, а обработчики команд поднимают эти события
public class ViewModel : ViewModelBase { public ViewModel() { } public event InsertTextEventHandler InsertTextEvent; // Command handler private void InsertText(string text) { InsertTextEventHandler handler = InsertTextEvent; if (handler != null) handler(this, new InsertTextEventArgs(text)); } }
-
The View подписывается на эти события
public partial class View : UserControl { public View() { InitializeComponent(); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { ViewModel viewModel = DataContext as ViewModel; if (viewModel != null) viewModel.InsertTextEvent += OnInsertTextEvent; } private void UserControl_Unloaded(object sender, RoutedEventArgs e) { ViewModel viewModel = DataContext as ViewModel; if (viewModel != null) viewModel.InsertTextEvent -= OnInsertTextEvent; } private void OnInsertTextEvent(object sender, InsertTextEventArgs e) { MyTextBox.Text = MyTextBox.Text.Insert(MyTextBox.CaretIndex, e.Text); } }
Я не уверен, что события Loaded
и Unloaded
UserControl
являются хорошими местами для подписки и отмены подписки на события, но я не мог найти проблем во время теста.
Я тестировал оба подхода в двух простых примерах, и они оба, похоже, работают. Теперь мои вопросы:
-
Какой подход, по вашему мнению, предпочтительнее? Есть ли какие-либо преимущества или недостатки одного из решений, которые я, возможно, не вижу?
-
Вы видите (и, возможно, практикуете) другие решения?
Благодарим вас за отзыв!