【问题标题】:XAML Binding in Style Setter using the Binding Path from the target control使用来自目标控件的绑定路径的样式设置器中的 XAML 绑定
【发布时间】:2015-12-02 05:23:05
【问题描述】:

我有一个客户提出的有趣要求,其中涉及许多挑战,我认为最简单的一个,结果却是最难的。

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

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>

和索引器一样,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,我更接近了 :)

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

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 个机会导致绑定错误或意外跳过一个。

【问题讨论】:

  • 与其使用这么多标志,不如为文本框创建一个附加属性,如 IsTextLocallyChanged 并为所有文本框创建一个样式。如果我理解错误,请纠正我。
  • 这并没有绕过标志,但它应该绕过样式问题,这将简化 XAML。然后我可以绑定到视图模型中的标志。问题是视图模型持有标志,所以我们仍然必须声明 XAML。不过,您已将我引向正确的方向...我将研究如何对自定义附加属性进行全局绑定

标签: c# wpf xaml binding


【解决方案1】:

我建议您将“验证器”模式(查看规范 INotifyDataErrorInfo)与自定义行为相结合。 Validator 根据 item 和 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>

【讨论】:

  • 你说得对,我确实想使用一种与验证器模式非常相似的模式。事实上,这就是这个问题的意义所在。但是我的控件已经有了验证模板,我现在想用额外的模板来扩展它们,比如值已经改变但还没有被持久化,以及验证。
  • 我可以做界面和模型视图方面的事情,但是因为我想针对现有的控件(密封的)我认为我能想出的最接近的东西是某种自定义扩展它采用主绑定路径并在源上的自定义索引器中使用它。我已经通过将模板绑定到错误的属性而犯了错误,如果绑定源没有实现所需的接口,我希望它是自动的并且静默失败。
  • 因此,一些具有属性的背景逻辑和反射引擎可以与旧值进行比较并为行为提供索引器。行为可以是Data模板中的模板吗?
猜你喜欢
  • 1970-01-01
  • 2013-07-23
  • 2015-12-10
  • 1970-01-01
  • 2018-06-14
  • 2017-06-04
  • 2020-03-14
  • 2013-03-29
  • 1970-01-01
相关资源
最近更新 更多