3

我正在制作一个小型 MVVM 应用程序。它应该采用一个完整的类Func<string>,并显示一个按钮列表,每个按钮执行一个包含 a 的命令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 没有。

4

3 回答 3

2

是使用 MVVMLight 的 RelayCommand 吗?

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

如果您传入一个未植根于其他地方的匿名函数,那么它可能会在 GC 运行时被清理。

大多数时候这不是问题,因为 func 是对 VM 的回调,并且 VM 本身植根于 DataContext、ViewModelLocator 或其他地方。但是,如果您正在创建未植根的 Func,这可能是一个问题。

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

于 2013-03-22T11:53:17.863 回答
0

您从命令中调用命令是对的吗?

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

这不是递归的吗?

您可以尝试从表达式中删除调用吗?你为什么从内部调用它?

于 2013-03-22T07:04:17.687 回答
0
Command = new RelayCommand(() => { Log.Insert(0, "-------------\n" + CommandName  + "\n"  + (Command.Invoke())); })

改成:

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

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

于 2013-03-22T05:40:45.133 回答