You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用System.Reactive控制复杂场景下WPF命令的可用性

实现方案

前置依赖

需要安装如下NuGet包:

  • System.Reactive:Rx核心库
  • 如果选择更简便的ReactiveUI实现,额外安装ReactiveUI、ReactiveUI.WPF

简便实现(基于ReactiveUI)

ReactiveUI的WhenAnyValue天然支持嵌套属性监听、自动空值处理,完全匹配需求。
首先让MyViewModel继承ReactiveObject,然后按如下写法实现:

合并条件写法

public class MyViewModel : ReactiveObject
{
    public MyViewModel()
    {
        // 监听所有依赖属性的变更,自动处理空值
        var canExecute = this.WhenAnyValue(
                vm => vm.ActiveDocument.Highlighting.Type,
                vm => vm.ActiveDocument.IsReadOnly,
                vm => vm.License.Kind,
                vm => vm.License.TrialDay,
                (type, isReadOnly, licenseKind, trialDay) => 
                    type == Highlighting.Xml && 
                    !isReadOnly && 
                    (licenseKind == LicenseKind.Full || trialDay < 30)
            )
            .DistinctUntilChanged() // 仅当状态变更时才触发更新
            .StartWith(false); // 初始默认值为false

        MyCommand = ReactiveCommand.Create(DoSomething, canExecute);
    }

    public ICommand MyCommand { get; } 
    // 其他所需属性
}

拆分条件写法(便于复用)

public class MyViewModel : ReactiveObject
{
    public MyViewModel()
    {
        // 拆分单个条件,可多处复用
        var highlightingIsXml = this.WhenAnyValue(vm => vm.ActiveDocument.Highlighting.Type)
            .Select(t => t == Highlighting.Xml)
            .StartWith(false);
        
        var documentNotReadonly = this.WhenAnyValue(vm => vm.ActiveDocument.IsReadOnly)
            .Select(isReadOnly => !isReadOnly)
            .StartWith(false);
        
        var appIsLicensed = this.WhenAnyValue(vm => vm.License.Kind, vm => vm.License.TrialDay)
            .Select(pair => pair.Item1 == LicenseKind.Full || pair.Item2 < 30)
            .StartWith(false);

        // 组合所有条件
        var canExecute = Observable.CombineLatest(
                highlightingIsXml,
                documentNotReadonly,
                appIsLicensed,
                (isXml, notReadonly, licensed) => isXml && notReadonly && licensed
            )
            .DistinctUntilChanged();

        MyCommand = ReactiveCommand.Create(DoSomething, canExecute);
    }

    public ICommand MyCommand { get; } 
    // 其他所需属性
}

纯System.Reactive实现(不依赖ReactiveUI)

如果不想引入ReactiveUI,可以自行实现属性变更的监听扩展方法:

// 基础扩展方法,将INotifyPropertyChanged属性变更转为可观测序列
public static class RxExtensions
{
    public static IObservable<TProperty> ObserveProperty<T, TProperty>(
        this T source, 
        Expression<Func<T, TProperty>> propertyExpr) 
        where T : INotifyPropertyChanged
    {
        var getter = propertyExpr.Compile();
        var memberExpr = (MemberExpression)propertyExpr.Body;
        var propertyName = memberExpr.Member.Name;

        return Observable.FromEventPattern<PropertyChangedEventArgs>(
                h => source.PropertyChanged += h, 
                h => source.PropertyChanged -= h)
            .Where(e => e.EventArgs.PropertyName == propertyName || string.IsNullOrEmpty(e.EventArgs.PropertyName))
            .Select(_ => getter(source))
            .StartWith(getter(source));
    }
}

嵌套属性的监听可以通过Switch操作符实现自动切换:

public class MyViewModel : INotifyPropertyChanged
{
    public MyViewModel()
    {
        // 监听ActiveDocument.Highlighting.Type的全链路变更
        var typeObs = this.ObserveProperty(vm => vm.ActiveDocument)
            .Select(doc => doc == null 
                ? Observable.Return(default(Highlighting)) 
                : doc.ObserveProperty(d => d.Highlighting)
                    .Select(h => h?.Type ?? default))
            .Switch();

        // 监听ActiveDocument.IsReadOnly变更
        var isReadOnlyObs = this.ObserveProperty(vm => vm.ActiveDocument)
            .Select(doc => doc == null 
                ? Observable.Return(true) 
                : doc.ObserveProperty(d => d.IsReadOnly))
            .Switch();

        // 监听授权相关属性变更
        var licenseObs = this.ObserveProperty(vm => vm.License)
            .Select(lic => lic == null 
                ? Observable.Return(false) 
                : lic.ObserveProperty(l => l.Kind)
                    .CombineLatest(lic.ObserveProperty(l => l.TrialDay), 
                        (kind, day) => kind == LicenseKind.Full || day < 30))
            .Switch();

        // 组合所有条件
        var canExecute = Observable.CombineLatest(
                typeObs,
                isReadOnlyObs,
                licenseObs,
                (type, isReadOnly, licensed) => type == Highlighting.Xml && !isReadOnly && licensed
            )
            .DistinctUntilChanged()
            .StartWith(false);

        // 自定义接收IObservable<bool>的Command实现即可
        MyCommand = new CustomCommand(DoSomething, canExecute);
    }

    public ICommand MyCommand { get; } 
    // 其他所需属性、INotifyPropertyChanged实现
}

能力适配说明

以上实现完全覆盖原LambdaCondition的核心能力:

  • 全链路变更监听:自动监听访问路径上所有实现INotifyPropertyChanged的实例的属性变更,实例替换时自动解绑旧实例、绑定新实例
  • 空值自动处理:访问链任意节点为null时自动返回预设默认值,无需手动编写多层判空逻辑
  • 按需刷新:DistinctUntilChanged保证仅当命令可执行状态实际发生变化时才触发通知,避免UI无效重绘

内容的提问来源于stack exchange,提问作者Spook

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 05:24:03