2014-04-11 15 views
5

Quindi, nel mio modello di vista, manterrò un oggetto come DataContext del controllo responsabile dell'aggiunta di un nuovo elemento nell'elenco. Ogni volta che viene eseguito il AddCommand, ho resettato quell'oggetto in modo che sia pronto per l'aggiunta di un altro elemento.Attivazione di ComboBox per il vecchio DataContext sul cambiamento DataContext

Il problema che sto affrontando è che non appena l'oggetto viene ripristinato all'interno del metodo Add, SelectionChanged grilletto del combo-box è inutilmente sollevato per la oggetto appena aggiunto. Non dovrebbe essere licenziato in primo luogo, ma anche se è stato licenziato, perché è stato licenziato per il precedente DataContext?

Come evitare ciò poiché è necessario inserire una logica di business nel comando del trigger che non posso permettermi di eseguire due volte?

Questo è un esempio semplice dimostrazione del problema che sto affrontando:

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> 

Codice-behind:

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

Se si mette un punto di interruzione all'interno del metodo ChangeType , noterai che viene eseguito inutilmente per l'elemento appena aggiunto quando la linea NewMyItem = new MyItem(); viene eseguita all'interno del metodo Add.

risposta

2

Invece di utilizzare l'evento ComboBox.SelectionChanged, è possibile utilizzare l'evento ComboBox.DropDownClosed:

Si verifica quando l'elenco a discesa del ComboBox chiude.

Esempio:

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

In questo ChangeType comando caso verrà chiamato solo una volta.

+1

Questa soluzione non funzionerà quando si utilizzano i tasti freccia della tastiera per modificare la selezione. –

1

Poiché il datacontinente della tua casella combinata è un oggetto, nel comando ADD si reinizializza la casella combinata in base alla nuova istanza dell'oggetto, quindi anche l'elemento selezionato viene ripristinato.

Per ottenere l'ultimo elemento selezionato (selezionato dall'utente) o l'elemento selezionato in precedenza (predefinito), ci sono alcune proprietà in SelectionChangedEventArgs come e.AddedItems, e.RemovedItems.

alcune discussioni utili possono essere trovate here per tali avvertimenti.

+0

Sì, l'elemento selezionato viene ripristinato ma penso che abbiate perso il punto che il 'CommandParameter' è associato a' DataContext', quindi devo ottenere il nuovo valore nell'argomento del metodo execute. Il parametro – user1004959

+0

è associato a una proprietà che verrà gestita in modo vincolante dal motore di associazione tra il datacontext di origine e destinazione. Non sono sicuro che otterrete gli ultimi valori datacontext nel metodo ChangeType() fino al prossimo evento SelectionChanged. Possiamo verificare la possibilità di salvare l'ultimo oggetto da e.AddedItems per uso futuro? –

1

Questo ha senso: si sta modificando il contesto dei dati e ad esso è associato il numero SelectedValue della casella combinata. Invece di utilizzare l'evento di modifica selezione, mi piacerebbe utilizzare un doppio senso di legame al Type immobile:

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

Quindi eseguire il ChangeType logica nel setter di proprietà (a proposito, probabilmente non si desidera utilizzare DependencyObject s come le classi di dati. Implementare invece INotifyPropertyChanged:

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