Я пытаюсь выполнить привязку к Readonly
свойству OneWayToSource
в режиме as, но, похоже, это невозможно сделать в XAML:
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWayToSource}" />
Я получил:
Невозможно установить свойство FlagThingy.IsModified, поскольку у него нет доступного метода доступа к набору.
IsModified
это только для чтения DependencyProperty
на FlagThingy
. Я хочу привязать это значение к FlagIsModified
свойству в контейнере.
Чтобы было ясно:
FlagThingy.IsModified --> container.FlagIsModified
------ READONLY ----- ----- READWRITE --------
Возможно ли это, используя только XAML?
Обновление: я исправил этот случай, установив привязку к контейнеру, а не к файлу FlagThingy
. Но я все же хотел бы знать, возможно ли это.
wpf
data-binding
xaml
readonly
Inferis
источник
источник
IsModified
readwriteFlagIsModified
.Ответы:
Некоторые результаты исследования 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"); } ....
источник
DependencyProperty
DP). DP только для чтения может быть изменен только с помощью связанногоDependencyPropertyKey
. Чтобы зарегистрировать,BindingExpression
движок должен манипулировать метаданными целевого DP. ПосколькуDependencyPropertyKey
он считается закрытым, чтобы гарантировать общедоступную защиту от записи, движок должен будет игнорировать этот ключ, что приведет к невозможности зарегистрировать привязку на DP, доступном только для чтения.Это ограничение WPF, и это сделано специально. Об этом сообщается в Connect здесь:
привязка OneWayToSource из свойства зависимости только для чтения
Я сделал решение, позволяющее динамически передавать свойства зависимостей, доступные только для чтения, в вызываемый источник, о
PushBinding
котором я писал здесь . В приведенном ниже примере выполняютсяOneWayToSource
привязки из DP, доступных только для чтения,ActualWidth
иActualHeight
к свойствам Width и Height объектаDataContext
<TextBlock Name="myTextBlock"> <pb:PushBindingManager.PushBindings> <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/> <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/> </pb:PushBindingManager.PushBindings> </TextBlock>
PushBinding
работает с использованием двух свойств зависимостей, прослушивателя и зеркала. Слушатель привязанOneWay
к TargetProperty и вPropertyChangedCallback
нем обновляет свойство Mirror, которое привязаноOneWayToSource
к тому, что было указано в привязке.Демо-проект можно скачать здесь.
Он содержит исходный код и небольшой пример использования.
источник
Написал это:
Применение:
<TextBox Text="{Binding Text}" p:OneWayToSource.Bind="{p:Paths From={x:Static Validation.HasErrorProperty}, To=SomeDataContextProperty}" />
Код:
Еще не тестировал в стилях и шаблонах, думаю, нужен специальный корпус.
источник
Вот еще одно присоединенное решение для свойств на основе SizeObserver, подробно описанное здесь. Перенос свойств графического интерфейса только для чтения обратно в 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}">
источник
Вот еще одна реализация для привязки к Validation.HasError
Использование в 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
источник
WPF не будет использовать установщик свойств CLR, но, похоже, на его основе выполняет некоторую странную проверку.
Может быть, в вашей ситуации это нормально:
источник
Хммм ... Я не уверен, что согласен ни с одним из этих решений. Как насчет указания обратного вызова принуждения в вашей регистрации собственности, который игнорирует внешние изменения? Например, мне нужно было реализовать свойство зависимости Position, доступное только для чтения, чтобы получить положение элемента управления 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.
источник
Я решил обойти это ограничение, предоставив в моем классе только свойство Binding, полностью сохранив DependencyProperty закрытым. Я реализовал свойство «PropertyBindingToSource» только для записи (это не DependencyProperty), которому можно присвоить значение привязки в xaml. В установщике для этого свойства только для записи я вызываю BindingOperations.SetBinding, чтобы связать привязку с DependencyProperty.
Для конкретного примера OP это будет выглядеть так:
Реализация FlatThingy:
Обратите внимание, что статический объект DependencyProperty только для чтения является частным. В элементе управления я добавил кнопку, нажатие которой обрабатывается Button_Click. Использование элемента управления FlatThingy в моем window.xaml:
<Window x:Class="ReadOnlyBinding.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:ReadOnlyBinding" mc:Ignorable="d" DataContext="{x:Static local:ViewModel.Instance}" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <TextBlock Text="{Binding FlagIsModified}" Grid.Row="0" /> <local:FlatThingy IsModifiedBindingToSource="{Binding FlagIsModified, Mode=OneWayToSource}" Grid.Row="1" /> </Grid>
Обратите внимание, что я также реализовал ViewModel для привязки к нему, который здесь не показан. Он предоставляет DependencyProperty с именем «FlagIsModified», как вы можете понять из приведенного выше источника.
Он отлично работает, позволяя мне возвращать информацию обратно в ViewModel из View в слабосвязанной манере с явным определением направления этого информационного потока.
источник
Вы сейчас делаете переплет в неправильном направлении. OneWayToSource будет пытаться обновить FlagIsModified в контейнере всякий раз, когда IsModified изменяет элемент управления, который вы создаете. Вам нужно обратное, а именно привязку IsModified к container.FlagIsModified. Для этого следует использовать режим привязки OneWay.
<controls:FlagThingy IsModified="{Binding FlagIsModified, ElementName=container, Mode=OneWay}" />
Полный список участников перечисления: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx
источник
IsIsModified
, 2) OP хочет объявить привязку к этому свойству в XAML и 3) привязка должна работать в этомOneWayToSource
режиме. Ваше решение практически не работает, потому что, как описано в вопросе, компилятор не позволит вам объявить привязку к свойству,IsModified
доступному только для чтения, и оно не работает концептуально, потому что доступно только для чтения и, следовательно, его значение не может быть изменено (по привязке).