列表框项的样式模板,导致事件触发器在删除项后触发
本文关键字:触发器 删除 事件 样式 列表 | 更新日期: 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>
情节提要动画将保留对项目的引用,直到动画完成。
您应该执行以下操作:
- 将情节提要移动到资源中,使您能够从代码隐藏访问它们。
- 创建一个字段以引用要删除的 MyItem。
- 单击"删除"按钮时设置该字段
- 将 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>