命令停止执行

本文关键字:执行 命令 | 更新日期: 2023-09-27 18:05:04

我正在制作一个小型MVVM应用程序。它应该采用一个充满Func<string>的类,并显示一个按钮列表,每个按钮执行一个包含Func<string>的命令,并在另一个列表中显示它们的返回值。

程序起初运行良好,但在随机按下大量按钮后,它只是停止执行命令。UI仍然具有响应性。好像装订坏了。

有太多的类,所以我把整个项目附加在下面的链接中:

http://www.megafileupload.com/en/file/403770/GenericTester-zip.html

相关代码:

namespace AdapterTester.ViewModel
{
  public class MainViewModel : ViewModelBase
  {
    public ObservableCollection<ViewableRelayCommand> CommandsList { get; set; }
    public ObservableCollection<string> Log { get; set; }
    /// <summary>
    /// Initializes a new instance of the MainViewModel class.
    /// </summary>
    public MainViewModel()
    {
      CommandsList = new ObservableCollection<ViewableRelayCommand>();
      Log = new ObservableCollection<string>();
      MapCommands();
    }
    /// <summary>
    /// adds a ViewableRelayCommand to the CommandsList
    /// </summary>
    public void Add(Func<string> iCommand, string CommandName, Func<bool> CanExecute = null)
    {
      CommandsList.Add(new ViewableRelayCommand()
      {
        Command = new RelayCommand(() => { Log.Insert(0, "-------------'n" + CommandName  + "'n"  + (iCommand.Invoke())); }),
        CommandName = CommandName
      });
    }
    /// <summary>
    /// For Each Func<string> in TestFunctions create a ViewableRelayCommand
    /// </summary>
    private void MapCommands()
    {
      var target = new TestFunctions();
      var methods = target.GetType().GetMethods().Where(m => m.DeclaringType == typeof(TestFunctions));
      foreach (var method in methods)
      {
        if( (method.ReturnType == typeof(string)) && (method.GetParameters().Length ==0))
        {
          Func<string> func = (Func<string>)Delegate.CreateDelegate(typeof(Func<string>), target, method);
          Add(func, method.Name);
        }
      }
    }
  }
  public class ViewableRelayCommand : ViewModelBase
  {
    public RelayCommand Command { get; set; }
    /// <summary>
    /// The <see cref="CommandName" /> property's name.
    /// </summary>
    public const string CommandNamePropertyName = "CommandName";
    private string _CommandName = "Hello";
    /// <summary>
    /// Sets and gets the CommandName property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    public string CommandName
    {
      get
      {
        return _CommandName;
      }
      set
      {
        if (_CommandName == value)
        {
          return;
        }
        RaisePropertyChanging(CommandNamePropertyName);
        _CommandName = value;
        RaisePropertyChanged(CommandNamePropertyName);
      }
    }
  }
}

XAML:

<Window x:Class="AdapterTester.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:ignore="http://www.ignore.com"
      mc:Ignorable="d ignore"
      Width="500"
      Height="300"
      Title="MVVM Light Application"
      DataContext="{Binding Main, Source={StaticResource Locator}}">
  
  <Window.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Skins/MainSkin.xaml" />
      </ResourceDictionary.MergedDictionaries>
      <DataTemplate x:Key="myButtonTemplate">
        <Button Content="{Binding Path=CommandName}" Command="{Binding Path=Command}" Margin="3"></Button>
      </DataTemplate>
    </ResourceDictionary>
  </Window.Resources>
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <ListBox Name="CommandsListBox" Grid.Column="0" 
             ItemsSource="{Binding CommandsList}"
             ItemTemplate="{StaticResource myButtonTemplate}">
    </ListBox>
     <ListBox Name="LogListBox" Grid.Column="1" 
                ItemsSource="{Binding Log}"
    </ListBox>
  </Grid>
</Window>

更新:

答案是改变:

 Command = new RelayCommand(() => { Log.Insert(0, "-------------'n" + CommandName  + "'n"  + (iCommand.Invoke())); }),

类似的东西:

List<Action> actions = new List<Action>();
public void Add(Func<string> iCommand, string CommandName, Func<bool> CanExecute = null)
{
    Action act = () => { Log.Insert(0, "-------------'n" + CommandName + "'n" + (iCommand.Invoke())); };
    actions.Add(act);
    CommandsList.Add(new ViewableRelayCommand()
    {
        Command = new RelayCommand(act)
        ,
        CommandName = CommandName
    });
}

因为添加到中继命令的操作没有根。

更新更改为我自己的中继命令有帮助。尽管Funcs没有生根。

命令停止执行

是否使用MVVMLight的RelayCommand?

如果是这样,您可能遇到GC问题。RelayCommand在内部对其回调使用WeakReference。

如果您传递的是一个不植根于其他地方的anon函数,那么当GC运行时,它可能会被清理掉。

大多数情况下,这不是问题,因为func是对VM的回调,VM本身植根于DataContext、ViewModelLocator或其他位置。如果你正在创建没有根的Func,这可能是一个问题。

根除这些Func的一种方法是在ViewModel中有一个List<Func<string>>,并在创建RelayCommands的同时将它们添加到列表中。

您从命令中调用命令,我说得对吗?

 Command = new RelayCommand(() => { Log.Insert(0, "-------------'n" + CommandName  + "'n"  + (Command.Invoke())); })

这不是递归的吗?

你能试着从表达式中删除invoke吗?你为什么要从内部调用它?

Command = new RelayCommand(() => { Log.Insert(0, "-------------'n" + CommandName  + "'n"  + (Command.Invoke())); })

更改为:

Command = new RelayCommand(() => { Log.Insert(0, "-------------'n"); })

它可以工作,但我不知道为什么不能在Lambda表达式中使用参数(Command和CommandName(?