在样式设置器中使用来自目标控件的绑定路径进行XAML绑定

本文关键字:绑定 控件 路径 XAML 目标 设置 样式 | 更新日期: 2023-09-27 18:11:21

我有一个来自客户的有趣的请求,涉及到一些挑战,我认为是最容易的,结果是最难的。

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

ModelView实现了一个自定义接口:ILocalValueCache这有一个索引器,它应该返回布尔值来指示当前值自上次数据刷新以来是否发生了变化。

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

我想做的是使用单个样式或控件模板简化XAML,这很难,因为每个控件现在有两个绑定,一个到Value,另一个到ValueIsLocal:

更具体地说,我希望开发人员不需要知道它如何工作的内部机制(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>

和索引器一样,视图模型上的每个属性都有一个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,我离目标更近了一步:)

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

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个机会得到错误的绑定,或者不小心跳过一个。

在样式设置器中使用来自目标控件的绑定路径进行XAML绑定

我建议您将"验证器"模式(查看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>