【问题标题】:How to set VisualState INITIALIZATION via data binding in XAML, WPF如何通过 XAML、WPF 中的数据绑定设置 VisualState INITIALIZATION
【发布时间】:2018-06-02 16:44:36
【问题描述】:

我想知道如何使用 XAML 来使用正确的 VisualState 初始化我的 UI 元素。 没有背后的代码。因为我知道如何使用 C# 代码。

我的 UI 元素的 XAML 是这样的:

<Border x:Name="PART_Border" >

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CriticalnessStates">
            <VisualState x:Name="NonCritical"/>
            <VisualState x:Name="Critical"/>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <i:Interaction.Triggers>

        <ei:DataTrigger Binding="{Binding IsCritical}" Value="False">
            <ei:GoToStateAction StateName="NonCritical"/>
        </ei:DataTrigger>

        <ei:DataTrigger Binding="{Binding IsCritical}" Value="True">
            <ei:GoToStateAction StateName="Critical"/>
        </ei:DataTrigger>

    </i:Interaction.Triggers>

</Border>

IsCritical 属性设置 UI 元素加载后 时,它可以完美运行,但问题是,我需要该元素, 以 正确 VisualState 加载;我的意思是,

IsCritical = true => 元素以临界状态加载
IsCritical = false => 元素加载到 NonCritical 状态

通过 C# 代码隐藏文件是可行的,您可以为您的元素编写一个加载的事件处理程序,通过检查相应的 DataContext 属性来设置视觉状态。我想知道如何使用纯 XAML 来实现这一点。

顺便说一下,eii 指向这些 XML 命名空间:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

谢谢。

【问题讨论】:

  • IsCritical 是一个 source 属性,应该在视图模型或控件本身中以编程方式实现和设置。不要在 XAML 中设置 DataTrigger 的源属性。它属于您的 Control 类还是属于什么?
  • @mm8 是的,它是我的 ViewModel 的一个属性,应该管理状态
  • 那么你应该在视图模型中设置属性的默认值。看我的回答。尝试为视图中的源属性设置默认值是没有意义的。
  • @mm8 恐怕我猜你错了,我想以编程方式更改 IsCritical 属性,我不想设置默认值!问题是,这些绑定表达式,不要读取其目标的初始值!我的意思是,如果您的视图模型将 IsCritical 设置为 true,则不会触发此条件的绑定!但是,当您在构建并显示 UI 元素后更改属性的值时,绑定会触发。
  • @EdPlunkett 我真的很想听 :) 为什么你认为我不想学习?!你有什么建议?我应该怎么做才能让我的视图初始化为正确的视觉状态?

标签: c# wpf xaml


【解决方案1】:

我实现了这个 Behavior 类:

using System;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Interactivity;
using System.Windows.Markup;

[ContentProperty("Conditions")]
public class VisualStateInitializer : Behavior<FrameworkElement>
{
    private bool _useTransitions = true;
    public bool UseTransitions
    {
        get { return _useTransitions; }
        set { _useTransitions = value; }
    }

    public List<VisualStateCondition> Conditions { get; set; }

    public VisualStateInitializer()
    {
        Conditions = new List<VisualStateCondition>();
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Loaded += AssociatedObject_Loaded;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
    }

    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        if (AssociatedObject.DataContext == null)
            return;

        foreach (var item in Conditions)
        {
            var dataContextType = AssociatedObject.DataContext.GetType();
            System.Reflection.PropertyInfo propertyInfo;
            object value = null;

            var properties = item.PropertyName.Split('.');

            if (properties.Length > 1)
            {
                for (int i = 0; i < properties.Length; i++)
                {
                    if(value != null)
                    {
                        dataContextType = value.GetType();
                        propertyInfo = dataContextType.GetProperty(properties[i]);
                        value = propertyInfo.GetValue(value);
                    }
                    else
                    {
                        propertyInfo = dataContextType.GetProperty(properties[i]);
                        value = propertyInfo.GetValue(AssociatedObject.DataContext);
                    }
                }
            }
            else
            {
                value = AssociatedObject.DataContext.GetType().GetProperty(item.PropertyName).GetValue(AssociatedObject.DataContext);
            }

            if (value != null && value.Equals(item.Value))
                VisualStateManager.GoToElementState(AssociatedObject, item.DesiredState, UseTransitions);
        }
    }
}

VisualStateCondition类是这样的:

public class VisualStateCondition
{
    /// <summary>
    /// The PropertyName to look for in the associated object's <see cref="FrameworkElement.DataContext"/>.
    /// </summary>
    public string PropertyName { get; set; }
    /// <summary>
    /// The Value to check for equality.
    /// </summary>
    public object Value { get; set; }
    /// <summary>
    /// The <see cref="VisualState.Name"/> that is desired for the condition.
    /// </summary>
    public string DesiredState { get; set; }
}

而在正确的初始状态下初始化我的 View 类所需的只是定义 VisualStateConditions,如下所示:

注意 => xaml 中的“i”命名空间指的是 BlendSdk 的“System.Windows.Interactivity” xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

<Border>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CriticalnessStates">
            <VisualState x:Name="NonCritical"/>
            <VisualState x:Name="Critical"/>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <i:Interaction.Behaviors>
        <viewHelpers:VisualStateInitializer>

            <viewHelpers:VisualStateCondition PropertyName="IsCritical" DesiredState="Critical">
                <viewHelpers:VisualStateCondition.Value>
                    <system:Boolean>
                        True
                    </system:Boolean>
                </viewHelpers:VisualStateCondition.Value>
            </viewHelpers:VisualStateCondition>

            <viewHelpers:VisualStateCondition PropertyName="IsCritical" DesiredState="NonCritical">
                <viewHelpers:VisualStateCondition.Value>
                    <system:Boolean>
                        False
                    </system:Boolean>
                </viewHelpers:VisualStateCondition.Value>
            </viewHelpers:VisualStateCondition>

        </viewHelpers:VisualStateInitializer>
    </i:Interaction.Behaviors>

</border>

其中 ViewHelpers 是我的 VisualStateInitializer 和 VisualStateCondition 类的命名空间。 任何人都可以提出更好的方法吗?

UPDATE 1 => 编辑属性值检索,以便我们可以传递嵌套属性,例如:

<viewHelpers:VisualStateCondition PropertyName="X.Y.NestedProperty"
    Value="False"
    DesiredState="StateForNestedProperty"/>

【讨论】:

    【解决方案2】:

    IsCritical 是一个源属性,应该在视图模型或控件本身中以编程方式实现和设置。所以默认值应该在视图模型或者属性实现的地方设置。

    StyleDataTrigger 无法设置视图模型属性的值。

    此外,应该由控件的实现来定义它所处的视觉状态。您可以通过在控件类中以编程方式调用VisualStateManager.GoToState 来做到这一点。

    【讨论】:

    • 恐怕我猜你错了,我想以编程方式更改 IsCritical 属性,我不想设置默认值!问题是,这些绑定表达式,不要读取其目标的初始值!我的意思是,如果您的视图模型将 IsCritical 设置为 true,则不会触发此条件的绑定!但是,当您在构建并显示 UI 元素后更改属性的值时,绑定会触发。
    • 你做错了。控件的实现应该定义它所处的视觉状态。您可以通过以编程方式调用 VisualStateManager.GoToState 来做到这一点。此外,您不需要两个侦听相同 bool 源属性的 DataTrigger。
    • 正如我在问题中提到的,我不想用 C# 源代码来做,因为这个实现是针对我不想在文件后面有代码的样式。正如我所写,我通过 C# 代码知道解决方案。我的视觉状态是由viewmodel对象设置的,viewmodel负责UI的状态,我不认为这种做法是错误的!这是整个 MVVM 的想法。如果您能指出我如何使用一个 DataTrigger 来处理两个 VisualState 更改,我会很高兴。当这些触发器被正确的值触发时,您只能编写操作,而不是错误的值。
    • 为什么要使用视觉状态数据触发器?视图模型应该设置源属性。它对视觉状态一无所知。这就是控制的责任。当然,边框不能更改/设置源属性。它不是输入控件。这是一个装饰器。
    • “ViewModel 对视觉状态一无所知”?您确定吗?那么MSDN's page 中的这张图片是什么意思呢?那么,您在哪里维护通过 View 操作数据而实现的业务逻辑?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-13
    • 1970-01-01
    • 2015-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多