使用目标控件

时间:2015-09-06 15:52:01

标签: c# wpf xaml binding

我收到了客户提出的一个有趣的请求,涉及到一些挑战,我认为这是最简单的挑战,结果是最难的。

用户需要知道某个值已在本地更改,但尚未保留到后端存储中。 (脏状态)我们通过在页面上每个控件内声明的样式上的数据触发器解决了这个问题。更改值时,控件背景将以黄色填充,然后在按下保存按钮时重置为控件默认值。

ModelView实现了一个自定义接口: ILocalValueCache 这个索引器应返回布尔值,以指示自上次数据刷新后当前值是否已更改。

  • ModelView还实现了IDataErrorInfo并使用DataAnnotations属性进行验证,因此我不能简单地使用验证模板。
  

我想要做的是使用单个样式或控件模板简化XAML这很难,因为每个控件现在有两个绑定,一个到,另一个到值< EM> IsLocal

     

更具体地说,我更喜欢一种解决方案,开发人员不需要知道它的工作原理(xIsLocal标志的字段名称是什么)或者绑定源是否支持它的内部机制

     

因为ViewModel实现了这个接口,(比如IDataErrorInfo)我应该能够全局地定位绑定到接口描述的状态的控件样式。

以下是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>

与索引器一样,View Model上的每个属性都有一个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应该能够处理的问题而没有太多问题。

我不确定我多久会需要这个确切的模板,但这是一种我确定我想再次使用的模式,每个属性都有可能存在多个状态(不仅仅是错误),并使用状态作为触发器来应用不同的样式。

  

我已经编辑了这篇文章,因为我还没有找到我想要的东西但是感谢Nikkita我更近了一步:)

通过使用自定义附加属性,我们可以直接在控件中声明绑定到flag字段,现在可以在全局样式字典中正确定义样式触发器。

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个机会可以使绑定错误,或者意外地跳过一个字段。

1 个答案:

答案 0 :(得分:0)

我建议你结合自定义行为的“验证器”模式(看看规范INotifyDataErrorInfo)。 Validator根据item中的绑定属性名称对集合进行包装,并更改元素。查看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>