WPF绑定IsEnabled到视图模型上的方法

5
我正在开发一个使用claims来控制用户权限的WPF应用程序。要求是禁用用户无权访问的控件。在我们的基本视图模型中,我们有以下方法:
HasClaim(string name);
我想在视图中做类似这样的事情:
<button IsEnabled="{Binding HasClaim("NAME")}" />
我知道我可以使用ObjectDataProvider,但我不想为每个claim创建一个。

你是如何创建按钮的...我们需要看更多的代码才能确定。 - Monty
你只能绑定到属性,所以解决方案很简单:创建一个调用 HasClaim("NAME") 的属性并将其绑定。 - Mike Zboray
@mike z:你会如何从视图中调用带参数的属性?... :-) 我认为这就像Monty所描述的转换器。 - Fruchtzwerg
@Fruchtzwerg 将视图中的另一个参数绑定到属性上。因此,视图模型具有 public string Name { get; set; } public string HasNameClaim { get { return HasClaim(Name); } }。显然,您需要实现 INPC 以使更新起作用,但我认为您已经明白了这个想法。 - Mike Zboray
我本来就担心会得到这个答案。我也考虑过使用转换器,但我真的不想创建一堆属性。如果我要走这条路,我可能会在视图的构造函数中启用/禁用控件,因为添加属性、转换器和绑定的开销对于应用程序生命周期内不会改变的东西来说是没有意义的。 - Nick
显示剩余4条评论
2个回答

5
如果您在视图中经常执行此操作,请考虑使用标记扩展。通常情况下,您不需要从视图或视图模型中获取任何信息来检查用户是否具有正确的声明,这些信息通常在登录时获取,并且不依赖于具体的视图。
public class HasClaimExtension : MarkupExtension {
    private readonly string _name;
    public HasClaimExtension(string name) {
        _name = name;
    }

    public override object ProvideValue(IServiceProvider serviceProvider) {
        return HasClaim();
    }

    private bool HasClaim() {
        // check if user has this claim here
        if (_name.ToLowerInvariant() == "admin")
            return true;
        return false;
    }
}

然后只需:
<Button IsEnabled="{local:HasClaim Admin}" Height="20" Width="100"/>

如果您真的需要访问您的视图模型,您仍然可以这样做:

在不太可能的情况下,您确实需要访问您的视图模型,您仍然可以这样做:
public class HasClaimExtension : MarkupExtension {
    private readonly string _name;

    public HasClaimExtension(string name) {
        _name = name;
    }

    public override object ProvideValue(IServiceProvider serviceProvider) {
        var service = (IProvideValueTarget) serviceProvider.GetService(typeof (IProvideValueTarget));
        // this is Button or whatever control you set IsEnabled of
        var target = service.TargetObject as FrameworkElement;
        if (target != null) {
            // grab it's DataContext, that is your view model
            var vm = target.DataContext as MyViewModel;
            if (vm != null) {
                return vm.HasClaim(_name);
            }
        }
        return false;
    }
}

public class MyViewModel {
    public bool HasClaim(string claim) {
        return false;
    }
}

更新以回答您在评论中的问题。您可以像这样做。假设有一个简单的LoginManager类:

public class LoginManager {
    public static LoginManager Instance = new LoginManager();

    private LoginManager() {

    }

    public bool IsLoggedIn { get; private set; }

    public void Login() {
        // do something, then
        IsLoggedIn = true;
        OnLoggedIn?.Invoke();
    }

    public bool HasClaim(string name) {
        if (!IsLoggedIn)
            throw new Exception("Cannot check claim until logged in");
        return true;
    }

    public event Action OnLoggedIn;
}

它有一些指示,用于判断声明是否已经可用,并且还有一个事件来通知当这些声明不可用时它们何时可用。然后在您的标记扩展中,您首先检查声明是否存在。如果是,则立即返回结果。如果没有,则返回false,但订阅当这些声明可用时的事件。之后 - 使用实际值更新目标属性。

public class HasClaimExtension : MarkupExtension {
    private readonly string _name;

    public HasClaimExtension(string name) {
        _name = name;
    }

    public override object ProvideValue(IServiceProvider serviceProvider) {
        if (LoginManager.Instance.IsLoggedIn) {
            return LoginManager.Instance.HasClaim(_name);
        }
        // if not logged in yet
        var service = (IProvideValueTarget) serviceProvider.GetService(typeof (IProvideValueTarget));
        var target = service.TargetObject as FrameworkElement;
        // this is dependency property you want to set, IsEnabled in this case
        var targetProperty = service.TargetProperty as DependencyProperty;
        if (target != null && targetProperty != null) {
            if (targetProperty.PropertyType != typeof (bool)) {
                // not boolean property - throw
                throw new Exception("HasClaim extension should be applied to Boolean properties only");
            }
            // here, subscribe to event after which your claims are available
            LoginManager.Instance.OnLoggedIn += () => {
                // update target property
                if (Application.Current.Dispatcher.CheckAccess())
                    target.SetValue(targetProperty, LoginManager.Instance.HasClaim(_name));
                else {
                    Application.Current.Dispatcher.Invoke(() => {
                        target.SetValue(targetProperty, LoginManager.Instance.HasClaim(_name));
                    });
                }
            };
        }

        return false;
    }
}

3
您可以使用MultiValueConverter,并将按钮本身和其DataContext作为绑定传递:
<Button Name="someName">
    <Button.IsEnabled>
        <MultiBinding Converter={StaticResource HasClaimConverter}>
            <Binding Path="Name" RelativeSource="{RelativeSource Self}"/>
            <Binding/>
        </MultiBinding>
    </Button.IsEnabled>
</Button>

转换器类:

class HasClaimConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var name= values[0] as String;
        var vm = values[1] as YourViewModel;

        return YourViewModel.HasClaim(name);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接