WPF 将 VisualState 附加到对象 属性

WPF Attach VisualState to Object Property

我正在为 VS2015 开发表达式混合,我有一个 ListBox 绑定到一个 ObservableCollection 自定义对象。这些对象公开了 NotifyPropertyChanged 出现的 Properties,并且一切正常。

如果 ItemTemplate 与那些 Properties 和我的列表工作良好,我可以绑定部件,但我想做的是根据某个 [=18] 设置 VisualState =](是否已配置)。我还创建了一些事件(已配置、confLost)并尝试在触发器面板中定位这些事件,但是……没有任何效果。

如何将 VisualStates 绑定到绑定对象的成员??

ItemTemplate 属性 与其他 DependencyProperty 一样工作,它可以随时 set/reset,它的视觉效果将反映在 UI 上。请参阅下面的示例,其中我已将 bool 值绑定到 ToggleButton 状态,并且 ItemControl's ItemTemplate 相应更改呈现不同的 visual.

更新: 我设计了一个Device class 有设备名称和它的状态来制造类似的情况。另一个 class MyVisualStateManager 创建一个可绑定的 属性。因为 VisualStateManager class 没有暴露任何 属性 直接绑定。代码如下:

XMAL

<Window x:Class="WpfWhosebugTempProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"  Width="525"
    DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
    xmlns:local="clr-namespace:WpfWhosebugTempProject"
    >
    <ItemsControl ItemsSource="{Binding list}" >
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <local:UserControl1  DataContext="{Binding Name}" Width="200" BorderBrush="Black" BorderThickness="2" Padding="2">
                    <local:UserControl1.Style>
                        <Style TargetType="{x:Type local:UserControl1}">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.DeviceState}" Value="0">
                                    <Setter Property="local:MyVisualStateManager.VisualState" Value="State1" />
                                </DataTrigger>
                                <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.DeviceState}" Value="1">
                                    <Setter Property="local:MyVisualStateManager.VisualState" Value="State2" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </local:UserControl1.Style>
                </local:UserControl1>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

用户控件:

<UserControl x:Class="WpfWhosebugTempProject.UserControl1"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>        
    <VisualStateManager.VisualStateGroups>            
        <VisualStateGroup x:Name="Common">                
            <VisualState x:Name="State1">                    
                <Storyboard>                        
                    <DoubleAnimation To="1" Duration="0:00:2" Storyboard.TargetName="State1Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />                        
                    <DoubleAnimation To="0" Duration="0:00:3" Storyboard.TargetName="State2Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />                        
                </Storyboard>                    
            </VisualState>                
            <VisualState x:Name="State2">                    
                <Storyboard>                        
                    <DoubleAnimation To="0" Duration="0:00:3" Storyboard.TargetName="State1Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />                        
                    <DoubleAnimation To="1" Duration="0:00:2" Storyboard.TargetName="State2Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />                        
                </Storyboard>                    
            </VisualState>                
        </VisualStateGroup>            
    </VisualStateManager.VisualStateGroups>        
    <Border Name="State2Panel" Background="Green" Opacity="0"/>        
    <Border Name="State1Panel" Background="Red" Opacity="1"/>
    <TextBlock Text="{Binding Path=.}" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>        
</Grid>

DataContext:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        list = new List<Device>();
        list.Add(new Device() {Name="Device 1",DeviceState = 0 });
        list.Add(new Device() { Name = "Device 2", DeviceState = 1 });
        list.Add(new Device() { Name = "Device 3", DeviceState = 0 });
        list.Add(new Device() { Name = "Device 4", DeviceState = 2 });
        list.Add(new Device() { Name = "Device 5", DeviceState = 1 });
        InitializeComponent();
    }

    public List<Device> list { get; set; }

}

public class Device : INotifyPropertyChanged
{
    private string name;

    public string Name
    {
        get { return name; }
        set 
        { 
            name = value;
            updateProperty("Name");
        }
    }
    private int deviceState;

    public int DeviceState
    {
        get { return deviceState; }
        set 
        { 
            deviceState = value;
            updateProperty("DeviceState");
        }
    }



    public event PropertyChangedEventHandler PropertyChanged;
    public void updateProperty(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
}

Helper Class: 这个 class 公开了一个附加的 属性 VisualState 可以绑定到 xaml.

中的任何值
public class MyVisualStateManager
{        
    public static string GetVisualState(DependencyObject obj)
    {
        return (string)obj.GetValue(VisualStateProperty);
    }

    public static void SetVisualState(DependencyObject obj, string value)
    {
        obj.SetValue(VisualStateProperty, value);
    }

    // Using a DependencyProperty as the backing store for VisualState.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty VisualStateProperty =
        DependencyProperty.RegisterAttached("VisualState", typeof(string), typeof(MyVisualStateManager), new PropertyMetadata(new PropertyChangedCallback(VisualStateChanged)));

    public static void VisualStateChanged(DependencyObject Do, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue != null)
        {
            string state = e.NewValue.ToString();
            var control = Do as FrameworkElement;
            VisualStateManager.GoToState(control, state, true);
        }
    }
}

输出

Different Item representing different devices and visual is changed on basis of theirDevicestateproperty which causes aTriggerto get executed inUserControl1.

I mean in the user interface of Blend, where to click to get the conditional working,

  1. 在 Blend 中寻找 Objects and Timeline 面板。
  2. Select 您定义的 ItemTemplate 中的控件(您已经设置了一个,对吗?),您希望其状态发生变化。 在上面 Kylo 的示例中,我们将 selected TextBox
  3. 右键单击并 select Edit Style 创建新样式(最有可能)或 Edit a Copy 适用于任何现有继承样式的样式。

从那里可以在属性选项卡上更改细节。您很可能会直接在 xaml 中进行特定操作。


尽管这些文档适用于版本 2,但它们仍然适用,如果没有别的可以让您对 Blend 有一个概览

Create a style resource

Style and template overview

尽管 Kylo 的解决方案可以证明是可行的,但 Microsoft 的人员已经为如此简单的操作开发了一个无需代码、只需单击 3 次的解决方案。

解决方案是行为,有一种行为称为 GoToStateAction,您必须将其中之一添加到您的控件中,然后您可以在其中设置触发器(可以设置为 DataTrigger)。在我的例子中,我绑定了枚举类型的 属性。 然后你可以设置比较和值(等于"ReadyToUse")

然后作为比较的结果,您可以触发特定对象的状态更改,设置对象,然后 select 来自漂亮组合框的状态。甚至还有一个用于使用您的转换的复选框。