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
.
Questa soluzione non funzionerà quando si utilizzano i tasti freccia della tastiera per modificare la selezione. –