在 DataTemplate WPF

问题描述

我们正在尝试根据窗口大小设置按钮的高度和宽度。我们希望使用 Visual States 来实现这一点。我们想将 Storyboard Target 设置为 DataTemplate 内的按钮。由于 DataTemplate 的名称范围,无法使用 StoryBoard.TargetName 直接按名称设置目标。有没有办法在 XAML 中做到这一点。

参考-

XAML:

<UserControl x:Class="ResizeSampleApp.Controls.ResizeUserControl"
             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" 
             xmlns:local="clr-namespace:ResizeSampleApp.Controls"
             mc:Ignorable="d" 
             SizeChanged="Currentwindow_SizeChanged"
             x:Name="DashBoard"
             >


    <Grid x:Name="grid">
        <visualstatemanager.VisualStateGroups>
            <VisualStateGroup x:Name="ResizeStates">
                <VisualState x:Name="FirstHorizontalBreakpoint">
                    <Storyboard >
                        <DoubleAnimation To="116" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Height"></DoubleAnimation>
                        <DoubleAnimation To="182" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Width"></DoubleAnimation>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="MinSize">
                    <Storyboard>
                        <DoubleAnimation To="100" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Height"></DoubleAnimation>
                        <DoubleAnimation To="197" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Width"></DoubleAnimation>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </visualstatemanager.VisualStateGroups>

        <StackPanel Orientation="Vertical">

            <Grid Margin="10">
                <ItemsControl Name="icTodoList">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Button x:Name="TargetBtn" Content="{Binding Title}" />
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </Grid>
        </StackPanel>
    </Grid>
</UserControl>

C#:

public class TodoItem
    {
        public string Title { get; set; }
       
    }
    public partial class ResizeUserControl : UserControl
    {
        public ResizeUserControl()
        {
            InitializeComponent();

            List<TodoItem> items = new List<TodoItem>();
            items.Add(new TodoItem() { Title = "Testing 1 " });
            items.Add(new TodoItem() { Title = "Testing 2" });
            items.Add(new TodoItem() { Title = "Testing 3" });

            icTodoList.ItemsSource = items;


        }
        public void Currentwindow_SizeChanged(object sender,SizeChangedEventArgs sizeChangedEventArgs)
        {
            

            if (this.ActualWidth > 847)
            { 
                visualstatemanager.GoToState(this.DashBoard,"FirstHorizontalBreakpoint",false);
            }
            else
            {
                visualstatemanager.GoToState(this.DashBoard,"MinSize",false);
            }
        }
}

提前致谢。

解决方法

无法使用视觉状态动画来为 DataTemplate 内的属性设置动画。 DataTemplate 只是一个模板,不是视觉树的一部分,在 Storyboard 被定义的那一刻。将为每个模板化目标(例如每个项目容器)复制模板。这意味着将有多个名为 TargetBtn 的按钮 - 从模板范围之外查看时。因此,您必须将动画移动到 DataTemplate 范围,这意味着在模板内部。

除了控件的视觉状态定义之外,您还应该添加属性以将视觉状态反映为对象状态。
Microsoft Docs 推荐的最佳做法是为每个状态执行此操作:添加 IsMouseOver 之类的属性以指示 MouseOver 状态或与 IsFocused 串联的 Focused状态,仅举出一些框架示例。
这允许您的控件将视觉状态表示为真实的实例状态,可以在控件的直接内容的上下文之外使用它 - 视觉状态只能由 VisualStateManager 使用。

然后您可以使用这个通常公开的只读依赖属性来触发例如一个 DataTrigger,您在 DataTemplate 中定义它以动画模板的内容:

ResizeUserControl.xaml.cs

public partial class ResizeUserControl : UserControl
{
  #region IsExpandedViewEnabled read-only dependency property

  protected static readonly DependencyPropertyKey IsExpandedViewEnabledPropertyKey = DependencyProperty.RegisterReadOnly(
    "IsExpandedViewEnabled",typeof(bool),typeof(ResizeUserControl),new PropertyMetadata(default(bool)));

  public static readonly DependencyProperty IsExpandedViewEnabledProperty = MyControl.IsExpandedViewEnabledPropertyKey.DependencyProperty;

  public bool IsExpandedViewEnabled
  {
    get => (bool) GetValue(MyControl.IsExpandedViewEnabledProperty);
    private set => SetValue(MyControl.IsExpandedViewEnabledPropertyKey,value);
  }

  #endregion IsExpandedViewEnabled read-only dependency property

  public ResizeUserControl()
  {
    InitializeComponent();
  }

  #region Overrides of FrameworkElement

  protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
  {
    base.OnRenderSizeChanged(sizeInfo);
    if (this.ActualWidth > 847)
    {
      VisualStateManager.GoToState(this,"FirstHorizontalBreakpoint",false);
      this.IsExpandedViewEnabled = true;
    }
    else
    {
      VisualStateManager.GoToState(this,"MinSize",false);
      this.IsExpandedViewEnabled = false;
    }
  }
}

ResizeUserControl.xaml

<UserControl>
  <Grid x:Name="grid">
    <StackPanel Orientation="Vertical">
      <Grid Margin="10">
        <ItemsControl Name="icTodoList"
                      ItemsSource="{Binding DataItems}">
          <ItemsControl.ItemTemplate>
            <DataTemplate>
              <Button x:Name="TargetBtn" Height="60"
                      Content="{Binding Title}" />

              <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=IsExpandedViewEnabled}"
                             Value="True">
                  <DataTrigger.EnterActions>
                    <BeginStoryboard>
                      <Storyboard>
                        <DoubleAnimation To="116" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Height" />
                        <DoubleAnimation To="182" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Width" />
                      </Storyboard>
                    </BeginStoryboard>
                  </DataTrigger.EnterActions>

                  <DataTrigger.ExitActions>
                    <BeginStoryboard>
                      <Storyboard>
                        <DoubleAnimation To="100" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Height" />
                        <DoubleAnimation To="197" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Width" />
                      </Storyboard>
                    </BeginStoryboard>
                  </DataTrigger.ExitActions>
                </DataTrigger>
              </DataTemplate.Triggers>
            </DataTemplate>
          </ItemsControl.ItemTemplate>
        </ItemsControl>
      </Grid>
    </StackPanel>
  </Grid>
</UserControl>