Привязка OneWayToSource от свойства readonly в XAML
Я пытаюсь привязать свойство Readonly
с помощью OneWayToSource
как режим, но, похоже, это невозможно сделать в XAML:
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWayToSource}" />
Я получаю:
Свойство 'FlagThingy.IsModified' не может быть установлено, потому что у него нет доступного доступа для доступа.
IsModified
- это только текст DependencyProperty
на FlagThingy
. Я хочу привязать это значение к свойству FlagIsModified
в контейнере.
Чтобы быть ясным:
FlagThingy.IsModified --> container.FlagIsModified
------ READONLY ----- ----- READWRITE --------
Можно ли использовать только XAML?
Обновление: Ну, я исправил этот случай, установив привязку в контейнере, а не на FlagThingy
. Но я все равно хотел бы знать, возможно ли это.
Ответы
Ответ 1
Некоторые результаты исследований для OneWayToSource...
Вариант № 1.
// Control definition
public partial class FlagThingy : UserControl
{
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
}
<controls:FlagThingy x:Name="_flagThingy" />
// Binding Code
Binding binding = new Binding();
binding.Path = new PropertyPath("FlagIsModified");
binding.ElementName = "container";
binding.Mode = BindingMode.OneWayToSource;
_flagThingy.SetBinding(FlagThingy.IsModifiedProperty, binding);
Вариант № 2
// Control definition
public partial class FlagThingy : UserControl
{
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { throw new Exception("An attempt ot modify Read-Only property"); }
}
}
<controls:FlagThingy IsModified="{Binding Path=FlagIsModified,
ElementName=container, Mode=OneWayToSource}" />
Вариант № 3 (истинное свойство зависимости только для чтения)
System.ArgumentException: свойство IsModified не может быть привязано к данным.
// Control definition
public partial class FlagThingy : UserControl
{
private static readonly DependencyPropertyKey IsModifiedKey =
DependencyProperty.RegisterReadOnly("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
public static readonly DependencyProperty IsModifiedProperty =
IsModifiedKey.DependencyProperty;
}
<controls:FlagThingy x:Name="_flagThingy" />
// Binding Code
Same binding code...
Отражатель дает ответ:
internal static BindingExpression CreateBindingExpression(DependencyObject d, DependencyProperty dp, Binding binding, BindingExpressionBase parent)
{
FrameworkPropertyMetadata fwMetaData = dp.GetMetadata(d.DependencyObjectType) as FrameworkPropertyMetadata;
if (((fwMetaData != null) && !fwMetaData.IsDataBindingAllowed) || dp.ReadOnly)
{
throw new ArgumentException(System.Windows.SR.Get(System.Windows.SRID.PropertyNotBindable, new object[] { dp.Name }), "dp");
}
....
Ответ 2
Это ограничение WPF, и оно по дизайну. Сообщается о Connect здесь:
привязка OneWayToSource от свойства зависимости только для чтения
Я сделал решение, чтобы динамически было возможность перенаправлять свойства зависимости только для чтения к источнику, называемому PushBinding
, о котором я рассказал об этом, В приведенном ниже примере OneWayToSource
Привязки из DP ActualWidth
и ActualHeight
только для чтения к свойствам ширины и высоты DataContext
<TextBlock Name="myTextBlock">
<pb:PushBindingManager.PushBindings>
<pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
<pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
</pb:PushBindingManager.PushBindings>
</TextBlock>
PushBinding
работает с использованием двух свойств зависимостей, прослушивателя и зеркала. Listener привязан OneWay
к TargetProperty, а в PropertyChangedCallback
он обновляет свойство Mirror, которое привязано OneWayToSource
к тому, что было указано в Binding.
Демо-проект можно скачать здесь
Он содержит исходный код и краткое использование примера, или посетите мой блог WPF, если вас интересуют детали реализации.
Ответ 3
Написал это:
Использование:
<TextBox Text="{Binding Text}"
p:OneWayToSource.Bind="{p:Paths From={x:Static Validation.HasErrorProperty},
To=SomeDataContextProperty}" />
код:
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
public static class OneWayToSource
{
public static readonly DependencyProperty BindProperty = DependencyProperty.RegisterAttached(
"Bind",
typeof(ProxyBinding),
typeof(OneWayToSource),
new PropertyMetadata(default(Paths), OnBindChanged));
public static void SetBind(this UIElement element, ProxyBinding value)
{
element.SetValue(BindProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static ProxyBinding GetBind(this UIElement element)
{
return (ProxyBinding)element.GetValue(BindProperty);
}
private static void OnBindChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ProxyBinding)e.OldValue)?.Dispose();
}
public class ProxyBinding : DependencyObject, IDisposable
{
private static readonly DependencyProperty SourceProxyProperty = DependencyProperty.Register(
"SourceProxy",
typeof(object),
typeof(ProxyBinding),
new PropertyMetadata(default(object), OnSourceProxyChanged));
private static readonly DependencyProperty TargetProxyProperty = DependencyProperty.Register(
"TargetProxy",
typeof(object),
typeof(ProxyBinding),
new PropertyMetadata(default(object)));
public ProxyBinding(DependencyObject source, DependencyProperty sourceProperty, string targetProperty)
{
var sourceBinding = new Binding
{
Path = new PropertyPath(sourceProperty),
Source = source,
Mode = BindingMode.OneWay,
};
BindingOperations.SetBinding(this, SourceProxyProperty, sourceBinding);
var targetBinding = new Binding()
{
Path = new PropertyPath($"{nameof(FrameworkElement.DataContext)}.{targetProperty}"),
Mode = BindingMode.OneWayToSource,
Source = source
};
BindingOperations.SetBinding(this, TargetProxyProperty, targetBinding);
}
public void Dispose()
{
BindingOperations.ClearAllBindings(this);
}
private static void OnSourceProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(TargetProxyProperty, e.NewValue);
}
}
}
[MarkupExtensionReturnType(typeof(OneWayToSource.ProxyBinding))]
public class Paths : MarkupExtension
{
public DependencyProperty From { get; set; }
public string To { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetObject = (UIElement)provideValueTarget.TargetObject;
return new OneWayToSource.ProxyBinding(targetObject, this.From, this.To);
}
}
Не тестировали его в стилях и шаблонах, предположим, что для этого нужен специальный корпус.
Ответ 4
Здесь другое добавленное решение на основе SizeObserver, подробно описанное здесь Нажатие свойств GUI только для чтения обратно в ViewModel
public static class MouseObserver
{
public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
"Observe",
typeof(bool),
typeof(MouseObserver),
new FrameworkPropertyMetadata(OnObserveChanged));
public static readonly DependencyProperty ObservedMouseOverProperty = DependencyProperty.RegisterAttached(
"ObservedMouseOver",
typeof(bool),
typeof(MouseObserver));
public static bool GetObserve(FrameworkElement frameworkElement)
{
return (bool)frameworkElement.GetValue(ObserveProperty);
}
public static void SetObserve(FrameworkElement frameworkElement, bool observe)
{
frameworkElement.SetValue(ObserveProperty, observe);
}
public static bool GetObservedMouseOver(FrameworkElement frameworkElement)
{
return (bool)frameworkElement.GetValue(ObservedMouseOverProperty);
}
public static void SetObservedMouseOver(FrameworkElement frameworkElement, bool observedMouseOver)
{
frameworkElement.SetValue(ObservedMouseOverProperty, observedMouseOver);
}
private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = (FrameworkElement)dependencyObject;
if ((bool)e.NewValue)
{
frameworkElement.MouseEnter += OnFrameworkElementMouseOverChanged;
frameworkElement.MouseLeave += OnFrameworkElementMouseOverChanged;
UpdateObservedMouseOverForFrameworkElement(frameworkElement);
}
else
{
frameworkElement.MouseEnter -= OnFrameworkElementMouseOverChanged;
frameworkElement.MouseLeave -= OnFrameworkElementMouseOverChanged;
}
}
private static void OnFrameworkElementMouseOverChanged(object sender, MouseEventArgs e)
{
UpdateObservedMouseOverForFrameworkElement((FrameworkElement)sender);
}
private static void UpdateObservedMouseOverForFrameworkElement(FrameworkElement frameworkElement)
{
frameworkElement.SetCurrentValue(ObservedMouseOverProperty, frameworkElement.IsMouseOver);
}
}
Объявить прикрепленное свойство в управлении
<ListView ItemsSource="{Binding SomeGridItems}"
ut:MouseObserver.Observe="True"
ut:MouseObserver.ObservedMouseOver="{Binding IsMouseOverGrid, Mode=OneWayToSource}">
Ответ 5
Вот еще одна реализация для привязки к Validation.HasError
public static class OneWayToSource
{
public static readonly DependencyProperty BindingsProperty = DependencyProperty.RegisterAttached(
"Bindings",
typeof(OneWayToSourceBindings),
typeof(OneWayToSource),
new PropertyMetadata(default(OneWayToSourceBindings), OnBinidngsChanged));
public static void SetBindings(this FrameworkElement element, OneWayToSourceBindings value)
{
element.SetValue(BindingsProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
public static OneWayToSourceBindings GetBindings(this FrameworkElement element)
{
return (OneWayToSourceBindings)element.GetValue(BindingsProperty);
}
private static void OnBinidngsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((OneWayToSourceBindings)e.OldValue)?.ClearValue(OneWayToSourceBindings.ElementProperty);
((OneWayToSourceBindings)e.NewValue)?.SetValue(OneWayToSourceBindings.ElementProperty, d);
}
}
public class OneWayToSourceBindings : FrameworkElement
{
private static readonly PropertyPath DataContextPath = new PropertyPath(nameof(DataContext));
private static readonly PropertyPath HasErrorPath = new PropertyPath($"({typeof(Validation).Name}.{Validation.HasErrorProperty.Name})");
public static readonly DependencyProperty HasErrorProperty = DependencyProperty.Register(
nameof(HasError),
typeof(bool),
typeof(OneWayToSourceBindings),
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
internal static readonly DependencyProperty ElementProperty = DependencyProperty.Register(
"Element",
typeof(UIElement),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(UIElement), OnElementChanged));
private static readonly DependencyProperty HasErrorProxyProperty = DependencyProperty.RegisterAttached(
"HasErrorProxy",
typeof(bool),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(bool), OnHasErrorProxyChanged));
public bool HasError
{
get { return (bool)this.GetValue(HasErrorProperty); }
set { this.SetValue(HasErrorProperty, value); }
}
private static void OnHasErrorProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(HasErrorProperty, e.NewValue);
}
private static void OnElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
BindingOperations.ClearBinding(d, DataContextProperty);
BindingOperations.ClearBinding(d, HasErrorProxyProperty);
}
else
{
var dataContextBinding = new Binding
{
Path = DataContextPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, DataContextProperty, dataContextBinding);
var hasErrorBinding = new Binding
{
Path = HasErrorPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, HasErrorProxyProperty, hasErrorBinding);
}
}
}
Использование в xaml
<StackPanel>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}">
<local:OneWayToSource.Bindings>
<local:OneWayToSourceBindings HasError="{Binding HasError}" />
</local:OneWayToSource.Bindings>
</TextBox>
<CheckBox IsChecked="{Binding HasError, Mode=OneWay}" />
</StackPanel>
Эта реализация специфична для привязки Validation.HasError
Ответ 6
WPF не будет использовать средство определения свойств CLR, но, похоже, он использует некоторую нечетную проверку на основе этого.
Возможно, в вашей ситуации это нормально:
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { throw new Exception("An attempt ot modify Read-Only property"); }
}
Ответ 7
Хммм... Я не уверен, что согласен с любым из этих решений. Как насчет указания обратного вызова принуждения в вашей регистрации собственности, который игнорирует внешние изменения? Например, мне нужно было реализовать свойство зависимостей позиции только для чтения, чтобы получить позицию элемента управления MediaElement внутри пользовательского элемента управления. Вот как я это сделал:
public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(double), typeof(MediaViewer),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnPositionChanged, OnPositionCoerce));
private static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = d as MediaViewer;
}
private static object OnPositionCoerce(DependencyObject d, object value)
{
var ctrl = d as MediaViewer;
var position = ctrl.MediaRenderer.Position.TotalSeconds;
if (ctrl.MediaRenderer.NaturalDuration.HasTimeSpan == false)
return 0d;
else
return Math.Min(position, ctrl.Duration);
}
public double Position
{
get { return (double)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
Другими словами, просто игнорируйте изменение и возвращайте значение, поддерживаемое другим членом, у которого нет общедоступного модификатора. - В приведенном выше примере MediaRenderer фактически является частным элементом управления MediaElement.
Ответ 8
Вы делаете привязку в неправильном направлении прямо сейчас. OneWayToSource будет пытаться обновлять FlagIsModified на контейнере всякий раз, когда IsModified изменяет элемент управления, который вы создаете. Вы хотите противоположное, которое должно иметь привязку IsModified к контейнеру. FlagIsModified. Для этого вы должны использовать режим привязки OneWay
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWay}" />
Полный список участников перечисления: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx