W moim modelu widoku przechowuję obiekt NewMyItem
jako DataContext
kontrolki odpowiedzialnej za dodanie nowego elementu na liście. Ilekroć wykonywany jest AddCommand
, resetuję ten obiekt, aby był gotowy do dodania kolejnego przedmiotu.Wyzwalanie wyzwalacza ComboBox dla starego DataContext przy zmianie DataContext
Problem mam obliczu tutaj jest to, że tak szybko, jak obiekt zostanie zresetowany wewnątrz metody Add
, liście rozwijanej w SelectionChanged
spust jest niepotrzebnie podniesiony po właśnie dodanego elementu. Nie powinien zostać zwolniony na pierwszym miejscu, ale nawet jeśli jest on zwalniany, dlaczego zostaje zwolniony za poprzednie DataContext
?
Jak tego uniknąć, ponieważ muszę wprowadzić jakąś logikę biznesową w poleceniu wyzwalacza, którego nie stać mnie na dwukrotne uruchomienie?
To prosta próba wykazania problem mam zachodu:
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>
Code-tył:
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);
}
}
}
Jeśli umieścisz punkt przerwania wewnątrz metody ChangeType
zauważysz, że niepotrzebnie uruchamia się dla dodanego elementu, gdy linia NewMyItem = new MyItem();
jest wykonywana wewnątrz metody .
To rozwiązanie nie działa, gdy używasz klawiszy strzałek na klawiaturze do zmiany zaznaczenia. –