Próbuję powiązać z Readonly
właściwością OneWayToSource
w trybie as, ale wydaje się, że nie można tego zrobić w języku XAML:
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWayToSource}" />
Dostaję:
Nie można ustawić właściwości „FlagThingy.IsModified”, ponieważ nie ma ona dostępnego metody dostępu do zestawu.
IsModified
jest DependencyProperty
włączony tylko do odczytu FlagThingy
. Chcę powiązać tę wartość z FlagIsModified
właściwością kontenera.
Żeby było jasne:
FlagThingy.IsModified --> container.FlagIsModified
------ READONLY ----- ----- READWRITE --------
Czy jest to możliwe przy użyciu tylko XAML?
Aktualizacja: Cóż, naprawiłem ten przypadek, ustawiając wiązanie w kontenerze, a nie w FlagThingy
. Ale nadal chciałbym wiedzieć, czy jest to możliwe.
wpf
data-binding
xaml
readonly
Inferis
źródło
źródło
IsModified
do właściwości readwriteFlagIsModified
.Odpowiedzi:
Niektóre wyniki badań dla OneWayToSource ...
Opcja 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);
Opcja 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}" />
Opcja nr 3 (prawdziwa właściwość zależności tylko do odczytu)
System.ArgumentException: Właściwość „IsModified” nie może być powiązana z danymi.
// 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...
Reflector daje odpowiedź:
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"); } ....
źródło
DependencyProperty
DP). DP tylko do odczytu można modyfikować tylko przy użyciu skojarzonego plikuDependencyPropertyKey
. Aby zarejestrowaćBindingExpression
silnik, musi manipulować metadanymi docelowego DP. PonieważDependencyPropertyKey
jest uważany za prywatny, aby zagwarantować publiczną ochronę przed zapisem, silnik będzie musiał zignorować ten klucz, co spowoduje brak możliwości zarejestrowania powiązania na DP tylko do odczytu.Jest to ograniczenie WPF i jest zgodne z projektem. Jest zgłaszany w Connect here:
OneWayToSource z właściwości zależności tylko do odczytu
Stworzyłem rozwiązanie, aby dynamicznie móc przesyłać właściwości zależności tylko do odczytu do źródła o nazwie, o
PushBinding
którym pisałem tutaj na blogu . Poniższy przykład robiOneWayToSource
Wiązania z tylko do odczytu DPActualWidth
iActualHeight
na szerokość i wysokość właściwościDataContext
<TextBlock Name="myTextBlock"> <pb:PushBindingManager.PushBindings> <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/> <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/> </pb:PushBindingManager.PushBindings> </TextBlock>
PushBinding
działa przy użyciu dwóch właściwości zależności: Listener i Mirror. Listener jest powiązanyOneWay
z TargetProperty iPropertyChangedCallback
aktualizuje właściwość Mirror, która jest powiązanaOneWayToSource
z tym, co określono w Binding.Projekt demonstracyjny można pobrać tutaj.
Zawiera kod źródłowy i krótkie przykłady użycia.
źródło
Napisał to:
Stosowanie:
<TextBox Text="{Binding Text}" p:OneWayToSource.Bind="{p:Paths From={x:Static Validation.HasErrorProperty}, To=SomeDataContextProperty}" />
Kod:
Nie testowałem go jeszcze w stylach i szablonach, myślę, że wymaga specjalnej obudowy.
źródło
Oto kolejne dołączone rozwiązanie właściwości oparte na SizeObserver opisane tutaj Przekazywanie właściwości GUI tylko do odczytu z powrotem do 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); } }
Zadeklaruj dołączoną właściwość w kontroli
<ListView ItemsSource="{Binding SomeGridItems}" ut:MouseObserver.Observe="True" ut:MouseObserver.ObservedMouseOver="{Binding IsMouseOverGrid, Mode=OneWayToSource}">
źródło
Oto kolejna implementacja powiązania z Validation.HasError
Wykorzystanie w 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>
Ta implementacja jest specyficzna dla wiązania
Validation.HasError
źródło
WPF nie będzie używać metody ustawiającej właściwości CLR, ale wydaje się, że na jej podstawie przeprowadza pewne dziwne sprawdzanie poprawności.
Może być w Twojej sytuacji, tak może być w porządku:
źródło
Hmmm ... Nie jestem pewien, czy zgadzam się z którymkolwiek z tych rozwiązań. Co powiesz na określenie wymuszonego wywołania zwrotnego w rejestracji usługi, który ignoruje zewnętrzną zmianę? Na przykład musiałem zaimplementować właściwość zależności Position tylko do odczytu, aby uzyskać pozycję kontrolki MediaElement w kontrolce użytkownika. Oto jak to zrobiłem:
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); } }
Innymi słowy, po prostu zignoruj zmianę i zwróć wartość obsługiwaną przez innego członka, który nie ma modyfikatora public. - W powyższym przykładzie MediaRenderer jest w rzeczywistości prywatną kontrolką MediaElement.
źródło
Sposób obejścia tego ograniczenia polegał na ujawnieniu tylko właściwości Binding w mojej klasie, zachowując całkowicie prywatność DependencyProperty. Zaimplementowałem właściwość tylko do zapisu „PropertyBindingToSource” (ta nie jest DependencyProperty), którą można ustawić na wartość powiązania w xaml. W programie ustawiającym dla tej właściwości tylko do zapisu wywołuję BindingOperations.SetBinding, aby połączyć powiązanie z DependencyProperty.
W przypadku konkretnego przykładu PO wyglądałoby to następująco:
Implementacja FlatThingy:
Zwróć uwagę, że statyczny obiekt DependencyProperty tylko do odczytu jest prywatny. W kontrolce dodałem przycisk, którego kliknięcie jest obsługiwane przez Button_Click. Użycie kontrolki FlatThingy w moim 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>
Zauważ, że zaimplementowałem również ViewModel do powiązania z tym, który nie jest tutaj pokazany. Udostępnia właściwość DependencyProperty o nazwie „FlagIsModified”, jak można uzyskać z powyższego źródła.
Działa świetnie, pozwalając mi wypychać informacje z powrotem do ViewModel z widoku w luźno powiązany sposób, z wyraźnie zdefiniowanym kierunkiem przepływu informacji.
źródło
Robisz teraz wiązanie w złym kierunku. OneWayToSource spróbuje zaktualizować FlagIsModified w kontenerze za każdym razem, gdy IsModified zmieni się w tworzonej kontrolce. Chcesz czegoś przeciwnego, czyli powiązania IsModified z container.FlagIsModified. W tym celu należy użyć trybu powiązania OneWay
<controls:FlagThingy IsModified="{Binding FlagIsModified, ElementName=container, Mode=OneWay}" />
Pełna lista członków wyliczenia: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx
źródło
IsIsModified
, 2) OP chce zadeklarować powiązanie tej właściwości w XAML i 3) powiązanie powinno działać wOneWayToSource
trybie. Twoje rozwiązanie nie działa praktycznie, ponieważ, jak opisano w pytaniu, kompilator nie pozwoli ci zadeklarować powiązania dla właściwości tylko do odczytu i nie działa koncepcyjnie, ponieważIsModified
jest tylko do odczytu, a zatem jego wartość nie może być zmieniony (przez oprawę).