views:

741

answers:

1

Yes, I know it sounds weird, but it doesn't, the question is why, and if there's a work around. It works with everything, even when you hit PrintScreen or Pause keys, CanExecute fires. So after doing a drag drop, in order to make it fire, you have to do "something" else, like a mouse click, focus, hit a key, anything. That'll make the event fire, and allow Execute to happen. Anyway, here's my code, I know it's long, but it'll help you help me.

I found this bug in our large main project, so I simplified it to this little app to isolate the problem.

XAML:

<Window x:Class="DragNDropCommands.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="485" SizeToContent="Width" Loaded="Window_Loaded">
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.New" CanExecute="NewCanExecute" Executed="NewExecuted" />
        <CommandBinding Command="ApplicationCommands.Save" CanExecute="SaveCanExecute" Executed="SaveExecuted" />
        <CommandBinding Command="ApplicationCommands.Undo" CanExecute="UndoCanExecute" Executed="UndoExecuted" />
        <CommandBinding Command="ApplicationCommands.Redo" CanExecute="RedoCanExecute" Executed="RedoExecuted" />
    </Window.CommandBindings>
    <Grid Margin="8">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <Button Command="ApplicationCommands.New" Grid.Row="0" Grid.Column="0" FontWeight="Bold" Content="New" Width="80" Margin="8"></Button>
        <Button Command="ApplicationCommands.Save" Grid.Row="0" Grid.Column="1" FontWeight="Bold" Content="Save" Width="80" Margin="8"></Button>
        <Button Command="ApplicationCommands.Undo" Grid.Row="0" Grid.Column="2" FontWeight="Bold" Content="Undo" Width="80" Margin="8"></Button>
        <Button Command="ApplicationCommands.Redo" Grid.Row="0" Grid.Column="3" FontWeight="Bold" Content="Redo" Width="80" Margin="8"></Button>

        <CheckBox Grid.Row="1" Grid.Column="0" Margin="8" IsChecked="{Binding Path=AllowNew, Mode=TwoWay}">Allow New</CheckBox>
        <CheckBox Grid.Row="1" Grid.Column="1" Margin="8" IsChecked="{Binding Path=AllowSave}">Allow Save</CheckBox>
        <CheckBox Grid.Row="1" Grid.Column="2" Margin="8" IsChecked="{Binding Path=AllowUndo}">Allow Undo</CheckBox>
        <CheckBox Grid.Row="1" Grid.Column="3" Margin="8" IsChecked="{Binding Path=AllowRedo}">Allow Redo</CheckBox>

        <Label x:Name="labelDrag" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" BorderBrush="Black" BorderThickness="1" MouseDown="Label_MouseDown"
               Background="LightBlue" HorizontalContentAlignment="Center" Margin="8">Drag this label...</Label>
        <Label x:Name="labelDropNew" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Black" BorderThickness="1" Drop="labelDropNew_Drop"
               Background="LightGray" HorizontalContentAlignment="Center" Margin="8" AllowDrop="True">...here to toggle AllowNew</Label>
        <Label x:Name="labelDropSave" Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" BorderBrush="Black" BorderThickness="1" Drop="labelDropSave_Drop"
               Background="LightGray" HorizontalContentAlignment="Center" Margin="8" AllowDrop="True">...here to toggle AllowSave</Label>

        <ListBox x:Name="listViewLog" Grid.Row="4" Grid.ColumnSpan="4" Margin="8" Width="500">
        </ListBox>

        <Button Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2" Margin="8" Click="Button_Click">Clear list</Button>
    </Grid>
</Window>

C#:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace DragNDropCommands
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private CommandControl commandControl = new CommandControl();
        private int canExecuteCount = 1;

        public Window1()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = commandControl;
        }

        private void NewCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (this.commandControl == null || listViewLog == null)
                return;

            e.CanExecute = this.commandControl.AllowNew;

            listViewLog.Items.Add
            (
                String.Format
                (
                    "{0} - NewCanExecute: {1} - commandControl.AllowNew: {2}",
                    canExecuteCount++, e.CanExecute, commandControl.AllowNew
                )
            );
        }

        private void NewExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("New executed");
        }

        private void SaveCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (this.commandControl == null || listViewLog == null)
                return;

            e.CanExecute = this.commandControl.AllowSave;

            listViewLog.Items.Add
            (
                String.Format
                (
                    "{0} - SaveCanExecute: {1} - commandControl.AllowSave: {2}",
                    canExecuteCount++, e.CanExecute, commandControl.AllowSave
                )
            );
        }

        private void SaveExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Save executed");
        }

        private void UndoCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (this.commandControl == null || listViewLog == null)
                return;

            e.CanExecute = this.commandControl.AllowUndo;

            listViewLog.Items.Add
            (
                String.Format
                (
                    "{0} - UndoCanExecute: {1} - commandControl.AllowUndo: {2}",
                    canExecuteCount++, e.CanExecute, commandControl.AllowUndo
                )
            );
        }

        private void UndoExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Undo executed");
        }

        private void RedoCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (this.commandControl == null || listViewLog == null)
                return;

            e.CanExecute = this.commandControl.AllowRedo;

            listViewLog.Items.Add
            (
                String.Format
                (
                    "{0} - RedoCanExecute: {1} - commandControl.AllowRedo: {2}",
                    canExecuteCount++, e.CanExecute, commandControl.AllowRedo
                )
            );
        }

        private void RedoExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Redo executed");
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            listViewLog.Items.Clear();
        }

        private void Label_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Label label = (Label)sender;

            if(e.LeftButton == MouseButtonState.Pressed)
                DragDrop.DoDragDrop(label, label, DragDropEffects.Move);
        }

        private void labelDropNew_Drop(object sender, DragEventArgs e)
        {
            this.commandControl.AllowNew = !this.commandControl.AllowNew;
        }

        private void labelDropSave_Drop(object sender, DragEventArgs e)
        {
            this.commandControl.AllowSave = !this.commandControl.AllowSave;
        }
    }

    public class CommandControl : DependencyObject
    {
        public bool AllowNew
        {
            get { return (bool)GetValue(AllowNewProperty); }
            set { SetValue(AllowNewProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AllowNew.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AllowNewProperty =
            DependencyProperty.Register("AllowNew", typeof(bool), typeof(CommandControl), new UIPropertyMetadata(false));

        public bool AllowSave
        {
            get { return (bool)GetValue(AllowSaveProperty); }
            set { SetValue(AllowSaveProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AllowSave.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AllowSaveProperty =
            DependencyProperty.Register("AllowSave", typeof(bool), typeof(CommandControl), new UIPropertyMetadata(false));

        public bool AllowUndo
        {
            get { return (bool)GetValue(AllowUndoProperty); }
            set { SetValue(AllowUndoProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AllowUndo.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AllowUndoProperty =
            DependencyProperty.Register("AllowUndo", typeof(bool), typeof(CommandControl), new UIPropertyMetadata(false));

        public bool AllowRedo
        {
            get { return (bool)GetValue(AllowRedoProperty); }
            set { SetValue(AllowRedoProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AllowRedo.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AllowRedoProperty =
            DependencyProperty.Register("AllowRedo", typeof(bool), typeof(CommandControl), new UIPropertyMetadata(false));
    }
}

You should be able to just copy paste and do a few name changes (files, namespaces) to get it running. I'd really love your help since this has been driving me nuts, and now that I finally discover the reason for the bug, I don't know what to do about it.

Any suggestion is really apreciatted.

Thanks in advance.

+4  A: 

Just a quick suggestion:

You could use CommandManager.InvalidateRequerySuggested() in the drag and drop event handler to force the reexecution of CanExecute(). (Link)

J W
Wow what an elegant solution. Thank you so much!
Carlo
one of these things you can look for forever, if it had not been for you ;-) thanks!
Dabblernl