0

EDIT: I have found the issue lies in the ListBoxItem's style in resources. If I comment it, it works fine. But what is wrong with it? I want to override the default style of ListBoxItem with the blue selection, etc.

I have a ListBox with the item template containing a Border. The border has triggers to make the delete button only appear on mouse hover. Now what happens in DeleteCommand command is that I delete the data for which this particular item was appearing in the list box. Consequently, the border disappears but the MouseLeave trigger still gets called and I get the following exception:

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)
...

This is a sample 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>

This is the sample code-behind:

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 );
    }
  }
}
user1004959
  • 627
  • 6
  • 16

2 Answers2

1

While overwriting template you must have overridden system value which would have been responsible to remove storyboard on item deletion.

Why do you need to override it just to give margin to ContentPresenter? Generally avoid overwriting template for such small things unless you want to give it complete different look.

You can give the margin on ListBoxItem itself via Style setters.

<Style TargetType="{x:Type ListBoxItem}">
    <Setter Property="Margin" Value="0,0,0,4" />
</Style>
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • I have not overridden the default style just to apply the margin. I don't want the default selection style. You get the blue selection on an item if it is selected which is I want to avoid and I'll provide my own selection style. – user1004959 Apr 10 '14 at 07:31
  • 1
    I meant default template. Have you tried overwriting `SystemColors.HighlightBrushKey` in case you want another highlight color like this - ``? – Rohit Vats Apr 10 '14 at 07:34
  • It worked. I hope all default behaviors of `ListBoxItem` will be easily configurable. But this is still a workaround. I want to know how can I give my own template of `ListBoxItem` and not get that exception. – user1004959 Apr 10 '14 at 07:52
  • For that you have to dig into source code using reflector to see what's happening under the wraps and what code remove the storyBoard on item deletion. – Rohit Vats Apr 10 '14 at 08:06
  • You mean I'd need to dig into .NET framework code? How do you do that? BTW, it worked in sample but in actual app I'm using third-party controls library and they have defined their own default styles in their DLL and the above mentioned brush is not working. – user1004959 Apr 10 '14 at 08:31
  • Yup i meant to dig into .NET framework code. You can do that using various tools available like `Dot Net Reflector`, `Jetbeans`, `DotPeek` etc. – Rohit Vats Apr 10 '14 at 08:34
  • Hmmm... so doing something as simple as having a delete icon only available on item hover is not so simple in WPF. :) – user1004959 Apr 11 '14 at 06:05
0

The storyboard animation retains a reference to the item until the animation is complete.

You should do the following:

  1. Move storyboards into resources, enabling you to access them from code behind.
  2. Create a field to reference the MyItem to be deleted.
  3. Set that field when "Delete" button is clicked
  4. Attach a StoryBoard.Completed event handler to the "fadeOut" storyboard, that removes the MyItem to be deleted from your collection, once the storyboard animation is done.

Here is a solution that works:

Code-behind:

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>
KVarmark
  • 226
  • 3
  • 4
  • This will only delete the item when mouse has left the border since it is only then the `fadeOutStoryBoard_Completed` will be invoked. – user1004959 Apr 09 '14 at 09:24
  • Yeah, I see... I solve one problem, but create another. The solution lies somewhere between using a [conditional event trigger](http://stackoverflow.com/questions/2764415/how-to-give-the-condition-for-eventtrigger/2823333#2823333) and my answer, where you would set up a conditional in the code-behind that only "begins" the storyboard if that condition is met (ie the item has not been deleted). Hope this helps. – KVarmark Apr 09 '14 at 10:55
  • Another point to ponder is that why does it work if I remove the `ListBoxItem`'s style. – user1004959 Apr 09 '14 at 11:37