ComboBox在更改DataContext时触发旧DataContext的触发

时间:2014-04-11 06:14:50

标签: c# .net wpf triggers

所以我在我的视图模型中保留一个对象NewMyItem作为控件的DataContext,负责在列表中添加一个新项目。每当执行AddCommand时,我都会重置该对象,以便添加另一个项目。

我在这里遇到的问题是,只要在Add方法内重置了对象,就会为刚刚添加的项目<不必要地引发组合框的SelectionChanged触发器/ strong>即可。它不应该在第一时间被解雇,但即使它被解雇了,为什么它被解雇了之前的DataContext

如何避免这种情况,因为我需要在触发器的命令中放置一些业务逻辑,我不能运行两次?

这是一个简单的示例,展示了我面临的问题:

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:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:WpfApplication2"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">

    <Window.Resources>
        <local:ChangeTypeConverter x:Key="changeTypeConverter" />

        <local:MyItems x:Key="myItems">
            <local:MyItem Name="Item 1" Type="1" />
            <local:MyItem Name="Item 2" Type="2" />
            <local:MyItem Name="Item 3" Type="3" />
        </local:MyItems>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" DataContext="{Binding DataContext.NewMyItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <TextBox Grid.Column="0" Width="100" Text="{Binding Name, Mode=TwoWay}" />
            <ComboBox Grid.Column="1" Margin="10,0,0,0" Width="40" SelectedValue="{Binding Type, Mode=OneWay}"
                      ItemsSource="{Binding DataContext.Types, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SelectionChanged">
                        <i:InvokeCommandAction Command="{Binding DataContext.ChangeTypeCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}">
                            <i:InvokeCommandAction.CommandParameter>
                                <MultiBinding Converter="{StaticResource changeTypeConverter}">
                                    <Binding />
                                    <Binding Path="SelectedValue" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBox}}" />
                                </MultiBinding>
                            </i:InvokeCommandAction.CommandParameter>
                        </i:InvokeCommandAction>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </ComboBox>

            <Button Grid.Column="2" Margin="10,0,0,0"
                    Command="{Binding DataContext.AddCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}">Add</Button>
        </Grid>

        <ListBox Grid.Row="1" ItemsSource="{StaticResource myItems}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Grid.Column="0" Width="100" Text="{Binding Name}" Foreground="Black" />
                        <TextBlock Grid.Column="1" Margin="10,0,0,0" Text="{Binding Type, StringFormat='Type {0}'}" Foreground="Black" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

代码隐藏:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
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 AddCommand { get; private set; }
    public ICommand ChangeTypeCommand { get; private set; }
    public IEnumerable<int> Types { get; private set; }

    public static readonly System.Windows.DependencyProperty NewMyItemProperty = System.Windows.DependencyProperty.Register( "NewMyItem", typeof( MyItem ), typeof( MainWindow ) );
    public MyItem NewMyItem { get { return (MyItem) GetValue( NewMyItemProperty ); } protected set { SetValue( NewMyItemProperty, value ); } }

    public MainWindow()
    {
      InitializeComponent();
      Types = new List<int> { 1, 2, 3 };
      NewMyItem = new MyItem();
      AddCommand = new MyCommand( Add );
      ChangeTypeCommand = new MyCommand<Tuple<MyItem, int>>( ChangeType );
    }

    private void Add()
    {
      MyItems myItems = Resources[ "myItems" ] as MyItems;
      myItems.Add( NewMyItem );
      NewMyItem = new MyItem();
    }

    private void ChangeType( Tuple<MyItem, int> tuple )
    {
      MyItem myItem = tuple.Item1;
      int type = tuple.Item2;

      myItem.Type = type;

      // TODO : some business checks
      // if(myItem.Type == 1)
      // if(myItem.Type == 2)
      // ...
    }
  }

  public class ChangeTypeConverter : IMultiValueConverter
  {
    public object Convert( object[] values, Type targetType, object parameter, CultureInfo culture )
    {
      if( values != null && values.Length > 1 && values[ 0 ] is MyItem && values[ 1 ] is int )
        return new Tuple<MyItem, int>( (MyItem) values[ 0 ], (int) values[ 1 ] );

      return values;
    }

    public object[] ConvertBack( object value, Type[] targetTypes, object parameter, CultureInfo culture )
    {
      throw new NotSupportedException();
    }
  }

  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 static readonly DependencyProperty TypeProperty = DependencyProperty.Register( "Type", typeof( int ), typeof( MyItem ) );
    public int Type { get { return (int) GetValue( TypeProperty ); } set { SetValue( TypeProperty, value ); } }
  }

  public class MyItems : ObservableCollection<MyItem>
  {

  }

  public class MyCommand : ICommand
  {
    private readonly Action executeMethod = null;
    private readonly Func<bool> canExecuteMethod = null;

    public MyCommand( Action execute )
      : this( execute, null )
    {
    }

    public MyCommand( Action execute, Func<bool> 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 ? canExecuteMethod() : true;
    }

    public void Execute( object parameter )
    {
      if( executeMethod != null )
        executeMethod();
    }
  }

  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 );
    }
  }
}

如果在ChangeType方法中放置断点,当NewMyItem = new MyItem();方法中执行行Add时,您会注意到刚刚添加的项不必要地运行。 / p>

3 个答案:

答案 0 :(得分:2)

您可以使用ComboBox.DropDownClosed事件:

,而不是使用ComboBox.SelectionChanged事件
  

ComboBox的下拉列表关闭时发生。

示例:

<ComboBox Name="MyComboBox" Grid.Column="1" Margin="10,0,0,0" Width="40" SelectedValue="{Binding Type, Mode=OneWay}"                     
            ItemsSource="{Binding DataContext.Types, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}">

    <i:Interaction.Triggers>
        <i:EventTrigger EventName="DropDownClosed"
                        SourceObject="{Binding ElementName=MyComboBox}">

            <i:InvokeCommandAction Command="{Binding DataContext.ChangeTypeCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}">
                <i:InvokeCommandAction.CommandParameter>
                    <MultiBinding Converter="{StaticResource changeTypeConverter}">
                        <Binding />
                        <Binding Path="SelectedValue" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBox}}" />
                    </MultiBinding>
                </i:InvokeCommandAction.CommandParameter>
            </i:InvokeCommandAction>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ComboBox>

在这种情况下ChangeType命令只会被调用一次。

答案 1 :(得分:1)

由于组合框的datacontext是一个对象,因此在ADD命令中,您将通过新对象实例重新初始化组合框,因此它的所选项也会被重置。

为了获得最新的选定项目(用户选择)或以前选择的项目(默认),SelectionChangedEventArgs中有一些属性,如e.AddedItems,e.RemovedItems。

对于此类警告,可以找到here的一些有用的讨论。

答案 2 :(得分:1)

这是有道理的 - 您正在更改数据上下文,并且组合框的SelectedValue已绑定到它。我没有使用选择更改事件,而是使用对Type属性的双向绑定:

<ComboBox SelectedValue="{Binding Type}" />

然后在属性设置器中运行ChangeType逻辑(顺便说一下,您可能不希望使用DependencyObject作为数据类。而是实现INotifyPropertyChanged

public int Type
{
    get { return _type; }
    set
    { 
        _type = value;
        OnPropertyChanged("Type");
        ChangeType(value);
    }
}