Нажатие свойств GUI только для чтения обратно в ViewModel

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

В частности, мой GUI содержит FlowDocumentPageViewer, который отображает одну страницу за раз из FlowDocument. FlowDocumentPageViewer предоставляет два свойства зависимостей только для чтения, называемые CanGoToPreviousPage и CanGoToNextPage. Я хочу, чтобы мой ViewModel всегда знал значения этих двух свойств View.

Я решил, что могу сделать это с привязкой данных OneWayToSource:

 

Если бы это было разрешено, это было бы прекрасно: всякий раз, когда свойство CanGoToNextPage FlowDocumentPageViewer изменилось, новое значение будет перенесено в свойство NextPageAvailable ViewModel, которое именно то, что я хочу.

К сожалению, это не скомпилируется: я получаю сообщение об ошибке: свойство CanGoToPreviousPage доступно только для чтения и не может быть установлено из разметки. По-видимому, свойства только для чтения не поддерживают какой- либо привязку данных, даже привязку данных, которая доступна только для чтения в отношении этого свойства.

Я мог бы сделать свои свойства ViewModel DependencyProperties и сделать привязку OneWay другим способом, но я не сумасшедший по поводу нарушения разделения (для ViewModel нужна ссылка на представление, для которой требуется привязка данных MVVM ).

FlowDocumentPageViewer не предоставляет событие CanGoToNextPageChanged, и я не знаю, какой хороший способ получить уведомления об изменениях из DependencyProperty, не создавая другого DependencyProperty, чтобы связать его, что кажется излишним.

Как я могу сохранить в своем представлении ViewModel изменения в свойствах только для чтения?

Да, я делал это в прошлом с свойствами ActualWidth и ActualHeight , оба из которых доступны только для чтения. Я создал прикрепленное поведение, которое имеет прикрепленные свойства ObservedWidth и ObservedHeight . Он также имеет свойство Observe , которое используется для первоначального подключения. Использование выглядит следующим образом:

  

Таким образом, модель представления имеет свойства Width и Height , которые всегда синхронизируются с прикрепленными свойствами ObservedWidth и ObservedHeight . Свойство Observe просто присоединяется к событию SizeChanged элемента FrameworkElement . В ручке он обновляет свойства ObservedWidth и ObservedHeight . Ergo, Width и Height модели просмотра всегда синхронизируются с ActualWidth и ActualHeight UserControl .

Возможно, это не идеальное решение (я согласен - только для чтения DP должны поддерживать привязки OneWayToSource ), но он работает, и он поддерживает шаблон MVVM. Очевидно, что ObservedWidth и ObservedHeight DP не доступны только для чтения.

UPDATE: вот код, который реализует описанную выше функциональность:

 public static class SizeObserver { public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached( "Observe", typeof(bool), typeof(SizeObserver), new FrameworkPropertyMetadata(OnObserveChanged)); public static readonly DependencyProperty ObservedWidthProperty = DependencyProperty.RegisterAttached( "ObservedWidth", typeof(double), typeof(SizeObserver)); public static readonly DependencyProperty ObservedHeightProperty = DependencyProperty.RegisterAttached( "ObservedHeight", typeof(double), typeof(SizeObserver)); public static bool GetObserve(FrameworkElement frameworkElement) { frameworkElement.AssertNotNull("frameworkElement"); return (bool)frameworkElement.GetValue(ObserveProperty); } public static void SetObserve(FrameworkElement frameworkElement, bool observe) { frameworkElement.AssertNotNull("frameworkElement"); frameworkElement.SetValue(ObserveProperty, observe); } public static double GetObservedWidth(FrameworkElement frameworkElement) { frameworkElement.AssertNotNull("frameworkElement"); return (double)frameworkElement.GetValue(ObservedWidthProperty); } public static void SetObservedWidth(FrameworkElement frameworkElement, double observedWidth) { frameworkElement.AssertNotNull("frameworkElement"); frameworkElement.SetValue(ObservedWidthProperty, observedWidth); } public static double GetObservedHeight(FrameworkElement frameworkElement) { frameworkElement.AssertNotNull("frameworkElement"); return (double)frameworkElement.GetValue(ObservedHeightProperty); } public static void SetObservedHeight(FrameworkElement frameworkElement, double observedHeight) { frameworkElement.AssertNotNull("frameworkElement"); frameworkElement.SetValue(ObservedHeightProperty, observedHeight); } private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { var frameworkElement = (FrameworkElement)dependencyObject; if ((bool)e.NewValue) { frameworkElement.SizeChanged += OnFrameworkElementSizeChanged; UpdateObservedSizesForFrameworkElement(frameworkElement); } else { frameworkElement.SizeChanged -= OnFrameworkElementSizeChanged; } } private static void OnFrameworkElementSizeChanged(object sender, SizeChangedEventArgs e) { UpdateObservedSizesForFrameworkElement((FrameworkElement)sender); } private static void UpdateObservedSizesForFrameworkElement(FrameworkElement frameworkElement) { // WPF 4.0 onwards frameworkElement.SetCurrentValue(ObservedWidthProperty, frameworkElement.ActualWidth); frameworkElement.SetCurrentValue(ObservedHeightProperty, frameworkElement.ActualHeight); // WPF 3.5 and prior ////SetObservedWidth(frameworkElement, frameworkElement.ActualWidth); ////SetObservedHeight(frameworkElement, frameworkElement.ActualHeight); } } 

Я использую универсальное решение, которое работает не только с ActualWidth и ActualHeight, но и с любыми данными, которые вы можете связать, по крайней мере, в режиме чтения.

Разметка выглядит так, если ViewportWidth и ViewportHeight являются свойствами модели представления

         

Вот исходный код для пользовательских элементов

 public class DataPiping { #region DataPipes (Attached DependencyProperty) public static readonly DependencyProperty DataPipesProperty = DependencyProperty.RegisterAttached("DataPipes", typeof(DataPipeCollection), typeof(DataPiping), new UIPropertyMetadata(null)); public static void SetDataPipes(DependencyObject o, DataPipeCollection value) { o.SetValue(DataPipesProperty, value); } public static DataPipeCollection GetDataPipes(DependencyObject o) { return (DataPipeCollection)o.GetValue(DataPipesProperty); } #endregion } public class DataPipeCollection : FreezableCollection { } public class DataPipe : Freezable { #region Source (DependencyProperty) public object Source { get { return (object)GetValue(SourceProperty); } set { SetValue(SourceProperty, value); } } public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(object), typeof(DataPipe), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSourceChanged))); private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((DataPipe)d).OnSourceChanged(e); } protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e) { Target = e.NewValue; } #endregion #region Target (DependencyProperty) public object Target { get { return (object)GetValue(TargetProperty); } set { SetValue(TargetProperty, value); } } public static readonly DependencyProperty TargetProperty = DependencyProperty.Register("Target", typeof(object), typeof(DataPipe), new FrameworkPropertyMetadata(null)); #endregion protected override Freezable CreateInstanceCore() { return new DataPipe(); } } 

Если кого-то интересует, я закодировал здесь приближение решения Кента:

 class SizeObserver { #region " Observe " public static bool GetObserve(FrameworkElement elem) { return (bool)elem.GetValue(ObserveProperty); } public static void SetObserve( FrameworkElement elem, bool value) { elem.SetValue(ObserveProperty, value); } public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached("Observe", typeof(bool), typeof(SizeObserver), new UIPropertyMetadata(false, OnObserveChanged)); static void OnObserveChanged( DependencyObject depObj, DependencyPropertyChangedEventArgs e) { FrameworkElement elem = depObj as FrameworkElement; if (elem == null) return; if (e.NewValue is bool == false) return; if ((bool)e.NewValue) elem.SizeChanged += OnSizeChanged; else elem.SizeChanged -= OnSizeChanged; } static void OnSizeChanged(object sender, RoutedEventArgs e) { if (!Object.ReferenceEquals(sender, e.OriginalSource)) return; FrameworkElement elem = e.OriginalSource as FrameworkElement; if (elem != null) { SetObservedWidth(elem, elem.ActualWidth); SetObservedHeight(elem, elem.ActualHeight); } } #endregion #region " ObservedWidth " public static double GetObservedWidth(DependencyObject obj) { return (double)obj.GetValue(ObservedWidthProperty); } public static void SetObservedWidth(DependencyObject obj, double value) { obj.SetValue(ObservedWidthProperty, value); } // Using a DependencyProperty as the backing store for ObservedWidth. This enables animation, styling, binding, etc... public static readonly DependencyProperty ObservedWidthProperty = DependencyProperty.RegisterAttached("ObservedWidth", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0)); #endregion #region " ObservedHeight " public static double GetObservedHeight(DependencyObject obj) { return (double)obj.GetValue(ObservedHeightProperty); } public static void SetObservedHeight(DependencyObject obj, double value) { obj.SetValue(ObservedHeightProperty, value); } // Using a DependencyProperty as the backing store for ObservedHeight. This enables animation, styling, binding, etc... public static readonly DependencyProperty ObservedHeightProperty = DependencyProperty.RegisterAttached("ObservedHeight", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0)); #endregion } 

Не стесняйтесь использовать его в своих приложениях. Это работает хорошо. (Благодарю Кент!)

Вот еще одно решение этой «ошибки», о которой я писал здесь:
Связывание OneWayToSource для свойства ReadOnly Dependency

Он работает с использованием двух свойств зависимостей, прослушивателя и зеркала. Listener привязано OneWay к TargetProperty и в PropertyChangedCallback обновляет свойство Mirror, которое привязано OneWayToSource к тому, что было указано в Binding. Я называю это PushBinding и его можно установить на любое свойство зависимостей только для чтения, подобное этому

       

Загрузите демо-проект здесь .
Он содержит исходный код и краткое использование примера, или посетите мой блог WPF, если вас интересуют детали реализации.

Последнее замечание, начиная с .NET 4.0, мы еще далеки от встроенной поддержки для этого, поскольку привязка OneWayToSource считывает значение из Источника после его обновления

Мне нравится решение Дмитрия Ташкинова! Однако он разбил мой VS в режиме дизайна. Вот почему я добавил строку в метод OnSourceChanged:

     private static void OnSourceChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
         if (! ((bool) DesignerProperties.IsInDesignModeProperty.GetMetadata (typeof (DependencyObject)). DefaultValue))
             ((DataPipe) г) .OnSourceChanged (е);
     }

Я думаю, это можно сделать немного проще:

XAML:

 behavior:ReadOnlyPropertyToModelBindingBehavior.ReadOnlyDependencyProperty="{Binding ActualWidth, RelativeSource={RelativeSource Self}}" behavior:ReadOnlyPropertyToModelBindingBehavior.ModelProperty="{Binding MyViewModelProperty}" 

CS:

 public class ReadOnlyPropertyToModelBindingBehavior { public static readonly DependencyProperty ReadOnlyDependencyPropertyProperty = DependencyProperty.RegisterAttached( "ReadOnlyDependencyProperty", typeof(object), typeof(ReadOnlyPropertyToModelBindingBehavior), new PropertyMetadata(OnReadOnlyDependencyPropertyPropertyChanged)); public static void SetReadOnlyDependencyProperty(DependencyObject element, object value) { element.SetValue(ReadOnlyDependencyPropertyProperty, value); } public static object GetReadOnlyDependencyProperty(DependencyObject element) { return element.GetValue(ReadOnlyDependencyPropertyProperty); } private static void OnReadOnlyDependencyPropertyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { SetModelProperty(obj, e.NewValue); } public static readonly DependencyProperty ModelPropertyProperty = DependencyProperty.RegisterAttached( "ModelProperty", typeof(object), typeof(ReadOnlyPropertyToModelBindingBehavior), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); public static void SetModelProperty(DependencyObject element, object value) { element.SetValue(ModelPropertyProperty, value); } public static object GetModelProperty(DependencyObject element) { return element.GetValue(ModelPropertyProperty); } } 
  • Когда следует использовать # и = в элементах управления ASP.NET?
  • Привязать к методу в WPF?
  • Связывающие свойства в коде
  • Похоже, что привязки данных не обновляются
  • WPF перед записью
  • Image UriSource и привязка данных
  • ItemsControl с несколькими DataTemplates для viewmodel
  • Связывание Richtextbox wpf
  • Связывание WPF ComboBox с пользовательским списком
  • Что означает «{Binding Path =.}» В привязке WPF?
  • Какие подходы доступны для фиктивных данных времени разработки в WPF?
  • Interesting Posts

    Android TabWidget обнаружит щелчок на текущей вкладке

    Как отключить политику безопасности с помощью защищенного клиента VPN-1 контрольной точки?

    Как избежать апострофа (‘) в MySql?

    Передача параметров на идентификатор страницы в jQuery Mobile

    Обновление операционной системы Windows 8 Enterprise до Windows 8.1 Enterprise KMS

    Количество ссылок на методы в файле .dex не может превышать 64k API 17

    Множество групп ggplot

    Есть ли какие-либо преимущества в производительности от дефрагментации накопителя USB?

    Клавиши-заполнители Windows 8

    Должны ли статические статические методы C #, которые * могут быть статическими?

    Как заставить мои клавиши Super (Windows Key) вести себя так же, как Ctrl / Alt / Shift в Linux

    OwnCloud MySQL таблица «oc_filecache» повреждена, могу ли я ее восстановить?

    Как можно угадать кодировку строки в Perl?

    c # datatable to csv

    javax.net.ssl.SSLPeerUnverifiedException: имя хоста не соответствует теме сертификата, предоставленной партнером

    Давайте будем гением компьютера.