XAML 使用目标控件中的绑定路径以样式 Setter 绑定
XAML Binding in Style Setter using the Binding Path from the target control
我收到了一个来自客户的有趣请求,其中涉及许多挑战,我认为最简单的一个,结果却是最难的。
用户需要知道某个值已在本地更改,但尚未持久化到后端存储。 (脏状态)我们通过在页面上每个控件内声明的样式上的数据触发器解决了这个问题。更改值时控件背景将填充黄色,然后在按下保存按钮时重置回控件默认值。
ModelView 实现了一个自定义接口:ILocalValueCache 它有一个索引器,该索引器应该 return 布尔值以指示自上次数据刷新以来当前值是否已更改。
- ModelView 还实现了 IDataErrorInfo 并使用 DataAnnotations 属性进行验证,所以我不能简单地使用验证模板。
What I would like to do is simplify the XAML using a single Style or a control template this is hard because each control now has two bindings, one to Value and another to ValueIsLocal:
To be more specific, I would prefer a solution where the developer doesn't need to know the inner mechanics of how it works (what the field names for the xIsLocal flags are) or if the binding source even supports it.
Because the ViewModel implements this interface, (like IDataErrorInfo) I should be able to globally target control styles bound to the states described by the interface.
这是 XAML 的一部分,其中包含一些文本框:
<TextBox Text="{Binding ScaleName}" Margin="5,2,5,2" Grid.Row="1" Grid.Column="2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ScaleNameIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="nbScaleCap" Text="{Binding ScaleCap}" Grid.Row="3" Grid.Column="0" Margin="5,2,5,2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ScaleCapIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="nbTareTol" Text="{Binding TareTol}" Grid.Row="3" Grid.Column="1" Margin="5,2,5,2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding TareTolIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
和indexer一样,ViewModel上的每个属性都有一个xxxIsLocal倒数属性,所以下面的分部模型对应上面的例子:
string ScaleName { get; set; }
bool ScaleNameIsLocal { get; set; }
string ScaleCap { get; set; }
bool ScaleCapIsLocal { get; set; }
string TareTol { get; set; }
bool TarTolIsLocal { get; set; }
我尝试过使用界面上的索引器来获取 IsLocal 值,但在 INotifyPropertyChanged 实现(让模型引发索引器值更改事件)方面遇到了困难,除此之外,更大的问题是如何制作一个具有基于目标控件上的内容或文本绑定路径而不是绑定结果值的绑定的单一样式。
我受到 IDataErrorInfo 模式的启发并使用 Validation.ErrorTemplate,它表面上看起来很简单,像这样一个简单的重复模式似乎是 WPF 应该能够处理的东西而不会出现太多问题。
我不确定我多久需要这个确切的模板,但我确定我想再次使用这个模式,其中每个 属性 都有可能有多个状态(不仅仅是错误)并使用状态作为触发器来应用不同的样式。
I've edited this post because I haven't quite found what I wanted but thanks to Nikkita I am a step closer :)
通过使用自定义附加 属性,我们可以直接在控件中声明绑定到标志字段,现在可以在全局样式字典中正确定义样式触发器。
ViewModel 没有改变,但是上面的 XML 现在被简化了:
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<Trigger Property="att:IsLocal.Value" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</Trigger>
</Style.Triggers>
</Style>
<TextBox Text="{Binding ScaleName}" Margin="5,2,5,2" Grid.Row="1" Grid.Column="2" att:IsLocal.Value="{Binding ScaleNameIsLocal}"></TextBox>
<TextBox Text="{Binding ScaleCap}" Grid.Row="3" Grid.Column="0" Margin="5,2,5,2" att:IsLocal.Value="{Binding ScaleCapIsLocal}"></TextBox>
<TextBox Text="{Binding TareTol}" Grid.Row="3" Grid.Column="1" Margin="5,2,5,2" att:IsLocal.Value="{Binding TareTolIsLocal}"></TextBox>
当前解决方案的最大问题是,如果我想将此(或其他)界面模式应用于现有应用程序,我仍然需要编辑大量现有 XAML。即使在当前表单中也有 20 多个字段,因此有 20 次机会将绑定错误或不小心跳过一个。
我建议您将 "validator" 模式(查看规范 INotifyDataErrorInfo)与自定义行为相结合。
验证器根据项目中的绑定 属性 名称创建包含结果的集合,并且 Bahaviour 会更改元素。查看 MSDN 帮助。
Xaml Example:
<TextBox
Name="a"
Text="{Binding *Variable*, Mode=OneWay}"
Header="Start"
Style ="{StaticResource MitfahrenDefaultTextEdit}" IsReadOnly="true"
Tapped="StartLocation_OnTapped">
<interactivity:Interaction.Behaviors>
<behaviors:RedFormFieldOnErrors
PropertyErrors="{Binding Path=Record.ValidationCollection[*Variable*]}"/>
</interactivity:Interaction.Behaviors>
</TextBox>
我收到了一个来自客户的有趣请求,其中涉及许多挑战,我认为最简单的一个,结果却是最难的。
用户需要知道某个值已在本地更改,但尚未持久化到后端存储。 (脏状态)我们通过在页面上每个控件内声明的样式上的数据触发器解决了这个问题。更改值时控件背景将填充黄色,然后在按下保存按钮时重置回控件默认值。
ModelView 实现了一个自定义接口:ILocalValueCache 它有一个索引器,该索引器应该 return 布尔值以指示自上次数据刷新以来当前值是否已更改。
- ModelView 还实现了 IDataErrorInfo 并使用 DataAnnotations 属性进行验证,所以我不能简单地使用验证模板。
What I would like to do is simplify the XAML using a single Style or a control template this is hard because each control now has two bindings, one to Value and another to ValueIsLocal:
To be more specific, I would prefer a solution where the developer doesn't need to know the inner mechanics of how it works (what the field names for the xIsLocal flags are) or if the binding source even supports it.
Because the ViewModel implements this interface, (like IDataErrorInfo) I should be able to globally target control styles bound to the states described by the interface.
这是 XAML 的一部分,其中包含一些文本框:
<TextBox Text="{Binding ScaleName}" Margin="5,2,5,2" Grid.Row="1" Grid.Column="2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ScaleNameIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="nbScaleCap" Text="{Binding ScaleCap}" Grid.Row="3" Grid.Column="0" Margin="5,2,5,2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ScaleCapIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="nbTareTol" Text="{Binding TareTol}" Grid.Row="3" Grid.Column="1" Margin="5,2,5,2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding TareTolIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
和indexer一样,ViewModel上的每个属性都有一个xxxIsLocal倒数属性,所以下面的分部模型对应上面的例子:
string ScaleName { get; set; }
bool ScaleNameIsLocal { get; set; }
string ScaleCap { get; set; }
bool ScaleCapIsLocal { get; set; }
string TareTol { get; set; }
bool TarTolIsLocal { get; set; }
我尝试过使用界面上的索引器来获取 IsLocal 值,但在 INotifyPropertyChanged 实现(让模型引发索引器值更改事件)方面遇到了困难,除此之外,更大的问题是如何制作一个具有基于目标控件上的内容或文本绑定路径而不是绑定结果值的绑定的单一样式。
我受到 IDataErrorInfo 模式的启发并使用 Validation.ErrorTemplate,它表面上看起来很简单,像这样一个简单的重复模式似乎是 WPF 应该能够处理的东西而不会出现太多问题。
我不确定我多久需要这个确切的模板,但我确定我想再次使用这个模式,其中每个 属性 都有可能有多个状态(不仅仅是错误)并使用状态作为触发器来应用不同的样式。
I've edited this post because I haven't quite found what I wanted but thanks to Nikkita I am a step closer :)
通过使用自定义附加 属性,我们可以直接在控件中声明绑定到标志字段,现在可以在全局样式字典中正确定义样式触发器。
ViewModel 没有改变,但是上面的 XML 现在被简化了:
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<Trigger Property="att:IsLocal.Value" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</Trigger>
</Style.Triggers>
</Style>
<TextBox Text="{Binding ScaleName}" Margin="5,2,5,2" Grid.Row="1" Grid.Column="2" att:IsLocal.Value="{Binding ScaleNameIsLocal}"></TextBox>
<TextBox Text="{Binding ScaleCap}" Grid.Row="3" Grid.Column="0" Margin="5,2,5,2" att:IsLocal.Value="{Binding ScaleCapIsLocal}"></TextBox>
<TextBox Text="{Binding TareTol}" Grid.Row="3" Grid.Column="1" Margin="5,2,5,2" att:IsLocal.Value="{Binding TareTolIsLocal}"></TextBox>
当前解决方案的最大问题是,如果我想将此(或其他)界面模式应用于现有应用程序,我仍然需要编辑大量现有 XAML。即使在当前表单中也有 20 多个字段,因此有 20 次机会将绑定错误或不小心跳过一个。
我建议您将 "validator" 模式(查看规范 INotifyDataErrorInfo)与自定义行为相结合。 验证器根据项目中的绑定 属性 名称创建包含结果的集合,并且 Bahaviour 会更改元素。查看 MSDN 帮助。
Xaml Example:
<TextBox
Name="a"
Text="{Binding *Variable*, Mode=OneWay}"
Header="Start"
Style ="{StaticResource MitfahrenDefaultTextEdit}" IsReadOnly="true"
Tapped="StartLocation_OnTapped">
<interactivity:Interaction.Behaviors>
<behaviors:RedFormFieldOnErrors
PropertyErrors="{Binding Path=Record.ValidationCollection[*Variable*]}"/>
</interactivity:Interaction.Behaviors>
</TextBox>