【问题标题】:WPF bind IsEnabled to method on view modelWPF 将 IsEnabled 绑定到视图模型上的方法
【发布时间】:2016-08-02 03:50:20
【问题描述】:

我正在开发一个 WPF 应用程序,该应用程序使用声明来控制用户可以做什么和不可以做什么。要求是禁用用户无权访问的控件。在我们的基础视图模型中,我们有以下方法:

HasClaim(字符串名称);

我想在视图中做这样的事情:

我知道我可以使用 ObjectDataProvider,但我不想为每个声明创建一个。

【问题讨论】:

  • 你是如何创建按钮的...我们真的需要查看更多代码...
  • 您只能绑定到属性,因此解决方案很简单:创建一个调用HasClaim("NAME") 的属性并绑定到该属性。
  • @mike z:如何从视图中调用带有参数的属性? ... :-) 我认为这就是 Monty 所描述的转换器。
  • @Fruchtzwerg 将视图中的其他参数绑定到属性。所以视图模型有public string Name { get; set; } public string HasNameClaim { get { return HasClaim(Name); } }。显然,您希望实施 INPC 以使更新正常工作,但我认为您明白了。
  • 是的,我建议了转换器然后删除了评论....希望看到更多代码以演示如何使用转换器,而不是简单地将建议扔在那里....跨度>

标签: c# wpf data-binding


【解决方案1】:

如果您经常在视图中这样做,请考虑使用标记扩展。很可能您不需要来自视图或视图模型的任何信息来检查用户是否拥有正确的声明,通常此类信息是在登录时获取的,并且不依赖于具体视图。

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;
    }
}

【讨论】:

  • 太棒了!这就是我要找的,谢谢。
  • 一个问题。是否可以重新执行此操作?我问的原因是因为在这个应用程序中,主窗口是在身份验证之前创建的,所以一切都会返回 false。
  • 用关于如何做到这一点的建议更新了我的答案。
【解决方案2】:

您可以使用 MultiValueConverter 并将 Button 本身及其 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();
    }
}

【讨论】:

  • 我希望我早点看到这个答案。如果可以的话,我会给它超过 +1。
猜你喜欢
  • 2023-03-06
  • 2012-09-29
  • 2011-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-25
相关资源
最近更新 更多