列表框项的样式模板,导致事件触发器在删除项后触发

本文关键字:触发器 删除 事件 样式 列表 | 更新日期: 2023-09-27 17:57:27

编辑:我发现问题在于ListBoxItem在资源中的风格。如果我评论它,它工作正常。但是它有什么问题呢?我想用蓝色选择等覆盖默认的ListBoxItem样式。

我有一个包含Border的项目模板的ListBox。边框具有触发器,使删除按钮仅在鼠标悬停时显示。现在DeleteCommand命令中发生的情况是我删除此特定项出现在列表框中的数据。因此,边框消失,但仍调用MouseLeave触发器,我得到以下异常:

Exception type: System.InvalidOperationException
Exception message: 'controlBox' name cannot be found in the name scope of 'System.Windows.Controls.Border'.
Exception stack trace: 
   at System.Windows.Media.Animation.Storyboard.ResolveTargetName(String targetName, INameScope nameScope, DependencyObject element)
   at System.Windows.Media.Animation.Storyboard.ClockTreeWalkRecursive(Clock currentClock, DependencyObject containingObject, INameScope nameScope, DependencyObject parentObject, String parentObjectName, PropertyPath parentPropertyPath, HandoffBehavior handoffBehavior, HybridDictionary clockMappings, Int64 layer)
   at System.Windows.Media.Animation.Storyboard.ClockTreeWalkRecursive(Clock currentClock, DependencyObject containingObject, INameScope nameScope, DependencyObject parentObject, String parentObjectName, PropertyPath parentPropertyPath, HandoffBehavior handoffBehavior, HybridDictionary clockMappings, Int64 layer)
   at System.Windows.Media.Animation.Storyboard.BeginCommon(DependencyObject containingObject, INameScope nameScope, HandoffBehavior handoffBehavior, Boolean isControllable, Int64 layer)
   at System.Windows.Media.Animation.BeginStoryboard.Begin(DependencyObject targetObject, INameScope nameScope, Int64 layer)
   at System.Windows.EventTrigger.EventTriggerSourceListener.Handler(Object sender, RoutedEventArgs e)
...

下面是一个示例 XAML:

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication2"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <local:MyItems x:Key="myItems">
            <local:MyItem Name="Item 1" />
            <local:MyItem Name="Item 2" />
            <local:MyItem Name="Item 3" />
        </local:MyItems>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="OverridesDefaultStyle" Value="True" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <ContentPresenter Margin="0,0,0,4" />
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <ListBox ItemsSource="{StaticResource myItems}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border Margin="5" Padding="5" BorderBrush="Blue" BorderThickness="1">
                        <Border.Triggers>
                            <EventTrigger RoutedEvent="MouseEnter">
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="controlBox" Storyboard.TargetProperty="Opacity" To="1" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger>
                            <EventTrigger RoutedEvent="MouseLeave">
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="controlBox" Storyboard.TargetProperty="Opacity" To="0" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger>
                        </Border.Triggers>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Text="{Binding Name}" />
                            <StackPanel Grid.Column="1" x:Name="controlBox" Opacity="0">
                                <Button Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}"
                                        CommandParameter="{Binding}">Delete</Button>
                            </StackPanel>
                        </Grid>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

这是示例代码隐藏:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication2
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public ICommand DeleteCommand { get; private set; }
    public MainWindow()
    {
      InitializeComponent();
      DeleteCommand = new MyCommand<MyItem>( Delete );
    }
    private void Delete( MyItem myItem )
    {
      MyItems myItems = Resources[ "myItems" ] as MyItems;
      myItems.Remove( myItem );
    }
  }
  public class MyItem : DependencyObject
  {
    public static readonly DependencyProperty NameProperty = DependencyProperty.Register( "Name", typeof( string ), typeof( MyItem ) );
    public string Name { get { return (string) GetValue( NameProperty ); } set { SetValue( NameProperty, value ); } }
  }
  public class MyItems : ObservableCollection<MyItem>
  { 
  }
  public class MyCommand<T> : ICommand
  {
    private readonly Action<T> executeMethod = null;
    private readonly Predicate<T> canExecuteMethod = null;
    public MyCommand( Action<T> execute )
      : this( execute, null )
    {
    }
    public MyCommand( Action<T> execute, Predicate<T> canExecute )
    {
      executeMethod = execute;
      canExecuteMethod = canExecute;
    }
    public event EventHandler CanExecuteChanged;
    public void NotifyCanExecuteChanged( object sender )
    {
      if( CanExecuteChanged != null )
        CanExecuteChanged( sender, EventArgs.Empty );
    }
    public bool CanExecute( object parameter )
    {
      return canExecuteMethod != null && parameter is T ? canExecuteMethod( (T) parameter ) : true;
    }
    public void Execute( object parameter )
    {
      if( executeMethod != null && parameter is T )
        executeMethod( (T) parameter );
    }
  }
}

列表框项的样式模板,导致事件触发器在删除项后触发

覆盖模板时,您必须覆盖系统值,该值将负责在项目删除时删除情节提要

为什么需要覆盖它只是为了给内容演示者留出边距?通常避免为这些小东西覆盖模板,除非您想给它完全不同的外观。

您可以通过样式库在列表框项本身上提供边距

<Style TargetType="{x:Type ListBoxItem}">
    <Setter Property="Margin" Value="0,0,0,4" />
</Style>

情节提要动画将保留对项目的引用,直到动画完成。

您应该执行以下操作:

  1. 将情节提要移动到资源中,使您能够从代码隐藏访问它们。
  2. 创建一个字段以引用要删除的 MyItem。
  3. 单击"删除"按钮时设置该字段
  4. 将 StoryBoard.Done 事件处理程序附加到"淡出"情节提要,一旦情节提要动画完成,该情节提要从集合中删除要删除的 MyItem。

这是一个有效的解决方案:

代码隐藏:

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
namespace WpfApplication2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MyItem _itemToDelete;
        public ICommand DeleteCommand { get; private set; }
        public MainWindow()
        {
            InitializeComponent();
            DeleteCommand = new MyCommand<MyItem>(Delete);
            this.Loaded += MainWindow_Loaded;
        }
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            var fadeOutStoryBoard = TryFindResource("fadeOut");
            if (fadeOutStoryBoard != null && fadeOutStoryBoard is Storyboard)
            {
                (fadeOutStoryBoard as Storyboard).Completed += fadeOutStoryBoard_Completed;
            }
        }
        void fadeOutStoryBoard_Completed(object sender, EventArgs e)
        {
            if (this._itemToDelete != null)
            {
                MyItems myItems = Resources["myItems"] as MyItems;
                myItems.Remove(this._itemToDelete);
            }
        }
        private void Delete(MyItem myItem)
        {
            this._itemToDelete = myItem;
        }
    }
    public class MyItem : DependencyObject
    {
        public static readonly DependencyProperty NameProperty =     DependencyProperty.Register("Name", typeof(string), typeof(MyItem));
        public string Name { get { return (string)GetValue(NameProperty); } set {     SetValue(NameProperty, value); } }
    }
    public class MyItems : ObservableCollection<MyItem>
    {
    }
    public class MyCommand<T> : ICommand
    {
        private readonly Action<T> executeMethod = null;
        private readonly Predicate<T> canExecuteMethod = null;
        public MyCommand(Action<T> execute)
            : this(execute, null)
        {
        }
        public MyCommand(Action<T> execute, Predicate<T> canExecute)
        {
            executeMethod = execute;
            canExecuteMethod = canExecute;
        }
        public event EventHandler CanExecuteChanged;
        public void NotifyCanExecuteChanged(object sender)
        {
            if (CanExecuteChanged != null)
                CanExecuteChanged(sender, EventArgs.Empty);
        }
        public bool CanExecute(object parameter)
        {
            return canExecuteMethod != null && parameter is T ?     canExecuteMethod((T)parameter) : true;
        }
        public void Execute(object parameter)
        {
            if (executeMethod != null && parameter is T)
                executeMethod((T)parameter);
        }
    }
}

XAML:

    <Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication2"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <local:MyItems x:Key="myItems">
            <local:MyItem Name="Item 1" />
            <local:MyItem Name="Item 2" />
            <local:MyItem Name="Item 3" />
        </local:MyItems>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="OverridesDefaultStyle" Value="True" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <ContentPresenter Margin="0,0,0,4" />
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Storyboard x:Key="fadeIn">
            <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="controlBox"     Storyboard.TargetProperty="Opacity" To="1" />
        </Storyboard>
        <Storyboard x:Key="fadeOut">
            <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="controlBox"     Storyboard.TargetProperty="Opacity" To="0" />
        </Storyboard>
    </Window.Resources>
    <Grid>
        <ListBox ItemsSource="{StaticResource myItems}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border Margin="5" Padding="5" BorderBrush="Blue"     BorderThickness="1">
                        <Border.Triggers>
                            <EventTrigger RoutedEvent="MouseEnter">
                                <BeginStoryboard Storyboard="{StaticResource fadeIn}">    </BeginStoryboard>
                            </EventTrigger>
                            <EventTrigger RoutedEvent="MouseLeave">
                                <BeginStoryboard Storyboard="{StaticResource fadeOut}">    </BeginStoryboard>
                            </EventTrigger>
                        </Border.Triggers>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Text="{Binding Name}" />
                            <StackPanel Grid.Column="1" x:Name="controlBox"     Opacity="0">
                                <Button Command="{Binding DataContext.DeleteCommand,     RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}"
                                                          CommandParameter="    {Binding}">Delete</Button>
                            </StackPanel>
                        </Grid>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>