1

通常のプロパティの代わりにネストされたプロパティが使用されている場合、 ICommand.CanExecutes に常に新しい値ではなく以前の値が含まれる理由を理解するのに苦労しました。

問題は以下で説明されていますが、ビューモデルでプロパティを作成し、モデルの対応するプロパティにフックする何らかの形式の「ファサード」パターンを使用する以外に、これを修正する方法を真剣に理解できません。

または、いまいましい CommandManager.RequerySuggested イベントを使用します。これが最適ではない理由は、メニューを数えるだけで 30 を超えるコマンドがビューに表示され、何かが変更されるたびにすべての CanExecute が更新される場合、すべてのメニュー項目/ボタンが更新されるまでに数秒かかるためです。以下の例をコマンド マネージャーと共に 1 つのコマンドとボタンだけで使用しても、ボタン自体を有効/無効にするのに約 500 ミリ秒かかります。

私が考えることができる唯一の理由は、CanExecute が起動される前に CommandParameter バインディングが更新されないことです。それについては何もできないと思います。

前もって感謝します :!

例えば

この基本的なビューモデルがあるとしましょう

public class BasicViewModel : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return name; }
        set {
            this.name = value;
            RaisePropertyChanged("Name");
            Command.RaiseCanExecuteChanged();
        }
    }

    private Project project;

    public Project Project
    {
        get { return project; }
        set {
            if (project != null) project.PropertyChanged -= ChildPropertyChanged;
            if (value != null) value.PropertyChanged += ChildPropertyChanged;

            project = value;
            RaisePropertyChanged("Project");
        }
    }

    private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) {
        Command.RaiseCanExecuteChanged();
    }

    public DelegateCommand<string> Command { get; set; }

    public BasicViewModel()
    {
        this.Project = new Example.Project();
        Command = new DelegateCommand<string>(this.Execute, this.CanExecute);
    }

    private bool CanExecute(string arg) {
        return !string.IsNullOrWhiteSpace(arg);
    }

    private void Execute(string obj) { }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string propertyName = null) {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

そしてこのモデル

public class Project : INotifyPropertyChanged
{
    private string text;

    public string Text
    {
        get { return text; }
        set
        {
            text = value;
            RaisePropertyChanged("Text");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string propertyName = null)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

私の見解では、このテキストボックスとボタンがあります。

<Button Content="Button" CommandParameter="{Binding Path=Project.Text}" Command="{Binding Path=Command}" />
<TextBox Text="{Binding Path=Project.Text, UpdateSourceTrigger=PropertyChanged}" />

テキストボックスに何かを入力するたびに CanExecute が呼び出されますが、パラメーターは常に前の値に設定されます。テキストボックスに 'H' と書くと、パラメータを NULL に設定して CanExecute が起動されます。次に「E」と書きます。テキストボックスに「HE」が含まれ、CanExecute が再び起動します。今回はパラメータを'H'のみに設定。

奇妙な理由で、パラメーターは常に以前の値に設定され、Project.Text を確認すると「HE」に設定されていますが、パラメーターはまだ「H」にのみ設定されています。

コマンドパラメータを

CommandParameter="{Binding Path=Name}"

と Textbox.Text へ

Text={Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"

すべてが完璧に機能します。CanExecute パラメーターには、以前の値ではなく常に最新の値が含まれます。

4

3 に答える 3

0

あなたが話しているファサードパターンは、標準のWPFプラクティスです。あなたがやっている方法の主な問題は、イベントが発生したときに、サブスクライブされたイベント ハンドラーがサブスクライブされた順序で実行されることです。あなたが持っているコード行:

        if (value != null) value.PropertyChanged += ChildPropertyChanged;

これは、「Project」クラスの「PropertyChanged」イベントをサブスクライブします。UIElements も、XAML でのバインディングを通じて、この同じ "PropertyChanged" イベントにサブスクライブされます。つまり、「PropertyChanged」イベントには 2 人のサブスクライバーがいます。

イベントに関することは、それらが順番に発生し、コードで何が起こっているかということです。イベントが「Project.Text」から発生すると、「ChildPropertyChanged」イベントが実行され、「CanExecuteChanged」イベントが発生し、最終的に実行されます。 「CanExecute」関数 (これは、誤ったパラメーターが表示されている場合です)。その後、UIElements は同じイベントによって実行される EventHandlers を取得します。そして、それらの値が更新されます。

問題を引き起こしているのは、サブスクリプションの順序です。これを試して、問題が解決するかどうか教えてください:

public Project Project
{
    get { return project; }
    set {
        if (project != null) project.PropertyChanged -= ChildPropertyChanged;
        project = value;
        RaisePropertyChanged("Project");
        if (project != null) project.PropertyChanged += ChildPropertyChanged;
    }
}
于 2015-02-13T18:12:04.950 に答える
0

これは私がこれを行う方法であり、期待どおりに機能します。ここでの唯一の違いは、DelegateCommand の代わりに RelayCommand を使用していることです。これらは基本的に同じ実装を持っているため、交換可能である必要があります。

ユーザーがテキストを入力してからボタンをクリックすると、RelayCommand の execute メソッドが期待どおりのテキストを取得します - 簡単です。

XAML:

<Grid>

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <TextBox Grid.Column="0"
             Grid.Row="0"
             Text="{Binding Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

    <Button Grid.Column="0"
            Grid.Row="1"
            Content="Test"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Center"
            Command="{Binding Path=TextCommand, Mode=OneWay}" />

</Grid>

ビューモデル:

public sealed class ExampleViewModel : BaseViewModel
{
    private string _text;

    public ExampleViewModel()
    {
       TextCommand = new RelayCommand(TextExecute, CanTextExecute);
    }

    public string Text
    {
        get
        {
            return _text;
        }
        set
        {
            _text = value;
            OnPropertyChanged("Text");
        }
    }

    public ICommand TextCommand { get; private set; }

    private void TextExecute()
    {
        // Do something with _text value...
    }

    private bool CanTextExecute()
    {
        return true;
    }
}
于 2015-02-14T10:56:17.580 に答える