Estou tentando vincular a uma Readonly
propriedade com OneWayToSource
o modo, mas parece que isso não pode ser feito em XAML:
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWayToSource}" />
Eu recebo:
A propriedade 'FlagThingy.IsModified' não pode ser definida porque não possui um acessador de conjunto acessível.
IsModified
é um de somente leitura DependencyProperty
em FlagThingy
. Quero vincular esse valor à FlagIsModified
propriedade no contêiner.
Para ser claro:
FlagThingy.IsModified --> container.FlagIsModified
------ READONLY ----- ----- READWRITE --------
Isso é possível usando apenas XAML?
Atualização: Bem, eu consertei este caso definindo a ligação no contêiner e não no FlagThingy
. Mas ainda gostaria de saber se isso é possível.
wpf
data-binding
xaml
readonly
Inferis
fonte
fonte
IsModified
para propriedade readwriteFlagIsModified
.Respostas:
Alguns resultados de pesquisa para OneWayToSource ...
Opção 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);
Opção 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}" />
Opção nº 3 (propriedade de dependência somente leitura verdadeira)
System.ArgumentException: a propriedade 'IsModified' não pode ser vinculada a dados.
// 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...
O Reflector dá a resposta:
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"); } ....
fonte
DependencyProperty
DP). Um DP somente leitura só pode ser modificado usando o associadoDependencyPropertyKey
. Para registrar um,BindingExpression
o mecanismo deve manipular os metadados do DP de destino. ComoDependencyPropertyKey
é considerado privado para garantir a proteção pública contra gravação, o mecanismo terá que ignorar essa chave com o resultado de não ser capaz de registrar a vinculação em um DP somente leitura.Esta é uma limitação do WPF e é por design. É relatado no Connect aqui:
ligação OneWayToSource de uma propriedade de dependência somente leitura
Eu criei uma solução para poder dinamicamente enviar propriedades de dependência somente leitura para a fonte chamada, a
PushBinding
qual bloguei aqui . O exemplo abaixo fazOneWayToSource
ligações dos DPs somente leituraActualWidth
eActualHeight
para as propriedades de largura e altura doDataContext
<TextBlock Name="myTextBlock"> <pb:PushBindingManager.PushBindings> <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/> <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/> </pb:PushBindingManager.PushBindings> </TextBlock>
PushBinding
funciona usando duas propriedades de dependência, ouvinte e espelho. O ouvinte é vinculadoOneWay
a TargetProperty ePropertyChangedCallback
atualiza a propriedade Mirror, que é vinculadaOneWayToSource
a tudo o que foi especificado em Binding.O projeto de demonstração pode ser baixado aqui.
Ele contém código-fonte e uso de amostra curta.
fonte
Escreveu isto:
Uso:
<TextBox Text="{Binding Text}" p:OneWayToSource.Bind="{p:Paths From={x:Static Validation.HasErrorProperty}, To=SomeDataContextProperty}" />
Código:
Ainda não testei em estilos e modelos, acho que precisa de um invólucro especial.
fonte
Aqui está outra solução de propriedade anexada com base em SizeObserver detalhada aqui Empurrando propriedades GUI somente leitura de volta para 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); } }
Declarar propriedade anexada no controle
<ListView ItemsSource="{Binding SomeGridItems}" ut:MouseObserver.Observe="True" ut:MouseObserver.ObservedMouseOver="{Binding IsMouseOverGrid, Mode=OneWayToSource}">
fonte
Aqui está outra implementação para vincular a Validation.HasError
Uso em 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>
Esta implementação é específica para ligação
Validation.HasError
fonte
O WPF não usará o configurador de propriedade CLR, mas parece que faz algumas validações estranhas com base nele.
Pode ser na sua situação isso pode estar ok:
fonte
Hmmm ... Não tenho certeza se concordo com qualquer uma dessas soluções. Que tal especificar um retorno de chamada de coerção em seu registro de propriedade que ignora mudanças externas? Por exemplo, eu precisava implementar uma propriedade de dependência Position somente leitura para obter a posição de um controle MediaElement dentro de um controle de usuário. Veja como eu fiz:
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); } }
Em outras palavras, simplesmente ignore a alteração e retorne o valor apoiado por um membro diferente que não possui um modificador público. - No exemplo acima, MediaRenderer é realmente o controle MediaElement privado.
fonte
A maneira como contornei essa limitação foi expor apenas uma propriedade Binding em minha classe, mantendo DependencyProperty totalmente privado. Implementei uma propriedade somente gravação "PropertyBindingToSource" (esta não é uma DependencyProperty) que pode ser definida como um valor de ligação no xaml. No setter desta propriedade somente gravação, chamo BindingOperations.SetBinding para vincular a vinculação à DependencyProperty.
Para o exemplo específico do OP, seria assim:
A implementação FlatThingy:
Observe que o objeto DependencyProperty estático somente leitura é privado. No controle adicionei um botão cujo clique é manipulado por Button_Click. O uso do controle FlatThingy em meu 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>
Observe que também implementei um ViewModel para vinculação que não é mostrado aqui. Ele expõe uma DependencyProperty chamada "FlagIsModified", como você pode obter na fonte acima.
Funciona muito bem, permitindo-me enviar informações de volta para o ViewModel a partir da View de maneira fracamente acoplada, com a direção desse fluxo de informações explicitamente definida.
fonte
Você está fazendo a ligação na direção errada agora. OneWayToSource tentará atualizar FlagIsModified no contêiner sempre que IsModified mudar no controle que você está criando. Você quer o oposto, que é ter IsModified vinculado a container.FlagIsModified. Para isso você deve usar o modo de ligação OneWay
<controls:FlagThingy IsModified="{Binding FlagIsModified, ElementName=container, Mode=OneWay}" />
Lista completa de membros da enumeração: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx
fonte
IsIsModified
, que 2) o OP deseja declarar uma vinculação nessa propriedade em XAML e que 3) a vinculação deve funcionar noOneWayToSource
modo. Sua solução não funciona na prática porque, conforme descrito na pergunta, o compilador não permite que você declare um vínculo em uma propriedade somente leitura e não funciona conceitualmente porqueIsModified
é somente leitura e, portanto, seu valor não pode ser alterado (pela ligação).