3

小さな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
    });
}

ルート化されていない場所でリレーコマンドに追加されたアクションのため。

update自分のリレーコマンドに変更すると役に立ちました。Funcsを応援することはしませんでしたが。

4

3 に答える 3

2

MVVMLightのRelayCommandを使用していますか?

その場合、GCの問題が発生している可能性があります。RelayCommandは、コールバックに対して内部的にWeakReferenceを使用します。

他の場所にルートされていないanon関数を渡す場合は、GCの実行時にクリーンアップされる可能性があります。

funcはVMへのコールバックであり、VM自体はDataContext、ViewModelLocatorなどにルートされているため、ほとんどの場合、これは問題にはなりません。ただし、ルート化されていないFuncを作成している場合は、問題になる可能性があります。

これらのFuncをルート化する1つの方法はList<Func<string>>、ViewModelにを入れ、RelayCommandsを作成すると同時にそれらをリストに追加することです。

于 2013-03-22T11:53:17.863 に答える
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 に答える
0

コマンド内からコマンドを呼び出すのは正しいですか?

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

それは再帰的ではありませんか?

式から呼び出しを削除してみてください。なぜあなたはそれを内側から呼び出すのですか?

于 2013-03-22T07:04:17.687 に答える