gpt4 book ai didi

c# - 为什么从 UI 中删除命令源后调用 CanExecute?

转载 作者:太空狗 更新时间:2023-10-29 21:55:07 26 4
gpt4 key购买 nike

我想了解为什么在已从 UI 中删除的命令源上调用 CanExecute。这是一个简化的程序来演示:

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding Txt}"
Command="{Binding Act}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Remove first item" Click="Button_Click" />
</StackPanel>
</Window>

代码隐藏:

public partial class MainWindow : Window
{
public class Foo
{
static int _seq = 0;
int _txt = _seq++;
RelayCommand _act;
public bool Removed = false;

public string Txt { get { return _txt.ToString(); } }

public ICommand Act
{
get
{
if (_act == null) {
_act = new RelayCommand(
param => { },
param => {
if (Removed)
Console.WriteLine("Why is this happening?");
return true;
});
}
return _act;
}
}
}

public ObservableCollection<Foo> Items { get; set; }

public MainWindow()
{
Items = new ObservableCollection<Foo>();
Items.Add(new Foo());
Items.Add(new Foo());
Items.CollectionChanged +=
new NotifyCollectionChangedEventHandler(Items_CollectionChanged);
DataContext = this;
InitializeComponent();
}

void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
foreach (Foo foo in e.OldItems) {
foo.Removed = true;
Console.WriteLine("Removed item marked 'Removed'");
}
}

void Button_Click(object sender, RoutedEventArgs e)
{
Items.RemoveAt(0);
Console.WriteLine("Item removed");
}
}

当我单击“删除第一项”按钮一次时,我得到以下输出:

Removed item marked 'Removed'
Item removed
Why is this happening?
Why is this happening?

“为什么会这样?”每次我点击窗口的某个空白部分时,都会不断打印。

为什么会这样?我可以或应该做什么来防止在已删除的命令源上调用 CanExecute?

注意:可以找到 RelayCommand here .

Michael Edenfield 问题的答案:

Q1: CanExecute 在删除的按钮上调用时的调用堆栈:

WpfApplication1.exe!WpfApplication1.MainWindow.Foo.get_Act.AnonymousMethod__1(object param) Line 30 WpfApplication1.exe!WpfApplication1.RelayCommand.CanExecute(object parameter) Line 41 + 0x1a bytes PresentationFramework.dll!MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource(System.Windows.Input.ICommandSource commandSource) + 0x8a bytes PresentationFramework.dll!System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute() + 0x18 bytes PresentationFramework.dll!System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(object sender, System.EventArgs e) + 0x5 bytes PresentationCore.dll!System.Windows.Input.CommandManager.CallWeakReferenceHandlers(System.Collections.Generic.List handlers) + 0xac bytes PresentationCore.dll!System.Windows.Input.CommandManager.RaiseRequerySuggested(object obj) + 0xf bytes

问题 2:另外,如果您从列表中删除所有按钮(不仅仅是第一个按钮),这种情况是否会继续发生?

是的。

最佳答案

问题是命令源(即按钮)不会取消订阅它所绑定(bind)的命令的 CanExecuteChanged,因此每当 CommandManager.RequerySuggested 触发时, CanExecute 也会在命令源消失很久之后触发。

为了解决这个问题,我在 RelayCommand 上实现了 IDisposable,并添加了必要的代码,这样每当模型对象被移除,也就是从 UI 中移除时,Dispose( ) 被调用在其所有 RelayCommand 上。

这是修改后的 RelayCommand(原来是 here ):

public class RelayCommand : ICommand, IDisposable
{
#region Fields

List<EventHandler> _canExecuteSubscribers = new List<EventHandler>();
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;

#endregion // Fields

#region Constructors

public RelayCommand(Action<object> execute)
: this(execute, null)
{
}

public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");

_execute = execute;
_canExecute = canExecute;
}

#endregion // Constructors

#region ICommand

[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}

public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
_canExecuteSubscribers.Add(value);
}
remove
{
CommandManager.RequerySuggested -= value;
_canExecuteSubscribers.Remove(value);
}
}

public void Execute(object parameter)
{
_execute(parameter);
}

#endregion // ICommand

#region IDisposable

public void Dispose()
{
_canExecuteSubscribers.ForEach(h => CanExecuteChanged -= h);
_canExecuteSubscribers.Clear();
}

#endregion // IDisposable
}

无论我在哪里使用上面的代码,我都会跟踪所有实例化的 RelayCommand,这样我就可以在时机成熟时调用 Dispose():

Dictionary<string, RelayCommand> _relayCommands 
= new Dictionary<string, RelayCommand>();

public ICommand SomeCmd
{
get
{
RelayCommand command;
string commandName = "SomeCmd";
if (_relayCommands.TryGetValue(commandName, out command))
return command;
command = new RelayCommand(
param => {},
param => true);
return _relayCommands[commandName] = command;
}
}

void Dispose()
{
foreach (string commandName in _relayCommands.Keys)
_relayCommands[commandName].Dispose();
_relayCommands.Clear();
}

关于c# - 为什么从 UI 中删除命令源后调用 CanExecute?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10280763/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com