【问题标题】:Binding WPF control visibility using multiple variables from the ViewModel使用 ViewModel 中的多个变量绑定 WPF 控件可见性
【发布时间】:2015-04-30 17:32:03
【问题描述】:

我正在编写一个 WPF 用户控件,它显示一个动态生成的具有多个页面的 TabControl,每个页面又包含一个动态生成的控件列表(文本框、复选框等)。

我想根据用户是否有权查看它们来设置 TextBox、CheckBox 控件的可见性。此权限是每个控件 ViewModel ('VisiblyBy') 上的值的组合,也是整个 UserControl ViewModel ('UserRole') 的属性。我刚刚开始使用 WPF,但标准方法似乎是使用 ValueConvertor - 但是我不明白如何编写一个组合/访问不同属性(VisiblyBy 和 UserRole)的方法,因为它们存在于不同级别在我的 ViewModel 层次结构中。

这是我绑定到 ViewModel 的 XAML 的一部分:

<TabControl ItemsSource="{Binding VariableData.Pages}" SelectedIndex="0">
<!-- this is the header template-->
   <TabControl.ItemTemplate>
  <DataTemplate>
     <TextBlock Text="{Binding Title}" FontWeight="Bold"/>
  </DataTemplate>
</TabControl.ItemTemplate>
            
<!-- this is the tab content template-->
   <TabControl.ContentTemplate>
  <DataTemplate>
     <StackPanel>
        <ListBox  Grid.IsSharedSizeScope="True" 
                  ItemsSource="{Binding Variables}"
                  ItemTemplateSelector="{StaticResource templateSelector}">
        </ListBox>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
           <Button Content="Cancel" />
               <Button Content="Submit" 
                       Command="{Binding DataContext.CommitChangesCommand, 
                                   RelativeSource={RelativeSource FindAncestor, 
                                   AncestorType={x:Type TabControl}}}" />
        </StackPanel>
    </StackPanel>
</DataTemplate>
  </TabControl.ContentTemplate>
</TabControl>

我还需要在未来扩展控制可见性的变量的数量,因为它还取决于它在应用程序中的使用位置。

【问题讨论】:

  • 你可以试试 IMultiValueConverter 并使用 Multibinding。

标签: c# wpf xaml


【解决方案1】:

您可以尝试 IMultiValueConverter 并使用 Multibinding。

<Window x:Class="ItemsControl_Learning.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ItemsControl_Learning"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <local:VisibilityConverter x:Key="conv" />
</Window.Resources>
<Grid>
    <Button Content="Test">
        <Button.Visibility>
            <MultiBinding Converter="{StaticResource conv}">
                <Binding  Path="Role" />
                <Binding Path="OtherProp" />                   
            </MultiBinding>
        </Button.Visibility>
    </Button>
</Grid>

public partial class MainWindow : Window
{

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainViewModel();
    }       
}

class MainViewModel
{
    private string role;
    public string Role
    {
        get { return role; }
        set { role = value; }
    }

    private string otherProp;
    public string OtherProp
    {
        get { return otherProp; }
        set { otherProp = value; }
    }
    public MainViewModel()
    {
        Role = "Admin";
        OtherProp = "Fail";
    }
}

class VisibilityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values[0].ToString().Equals("Admin") && values[1].ToString().Equals("Pass"))
        {
            return Visibility.Visible;
        }
        else
        {
            return Visibility.Collapsed;
        }
    }

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

Convert(...) 方法中,values 数组中不同输入的顺序与MultiBinding.Bindings 集合中的顺序相同。

在此示例中,values[0] 包含 Role 属性,values[1] 将是 OtherProp,因为这是它们在 XAML 中插入的顺序

【讨论】:

  • 经过数小时的阅读和尝试其他解决方案后,我最终编写了一个 MultiValueConverter,到目前为止效果很好 - 感谢一百万。
  • 我最终提供了两个答案......但我真的明白为什么这是一个很好的解决方案。 +1 来自我。 :-)
【解决方案2】:

绑定到一个复合属性,而不需要转换器。


我会在 ViewModel 上创建一个名为 IsAuthorized 的复合属性,然后绑定到该属性。因为它总是在设置其他属性时返回 当前 状态。

怎么做?

要完成复合属性RoleIsOther 属性还会在 IsAuthorized 属性上调用 PropertyChange;这始终使页面上的状态保持新鲜。

public Visiblity IsAuthorized
{
   get { return  (Role == "Admin" && OtherProp == "True") 
                     ? Visbility.Visible 
                     : Visibility.Hidden; }

}

 // When a value changes in Role or OtherProp fire PropertyChanged for IsAuthorized. 
public string Role
{
   get { return_Role;}
   set { _Role = value; 
         PropertyChanged("Role");
         PropertyChanged("IsAuthorized");
       }
}

public string OtherProp
{
   get { return_OtherProp;}
   set { _OtherProp = value; 
         PropertyChanged("OtherProp");
         PropertyChanged("IsAuthorized");
       }
}

人们似乎认为一个人只需要绑定到一个特定的属性,但是当简单地调用带有复合属性的 PropertyChanged 就可以完成这项工作时,为什么要让你的生活变得更难呢。

【讨论】:

  • 谢谢。我试过了,但我不知道如何访问顶级 ViewModel 和绑定到我试图更改其可见性的控件的实际数据模型的属性。
  • contd... 传递给我的 Convert() 方法的第一个“值”参数已绑定到我的 ViewModel,所以我有一半的数据。我认为我可以使用 ConverterParameter 来访问我的数据模型对象(甚至只是我需要的 VisibleBy 属性 - 但是我无法让它工作,而且我对 ConverterParameter 的了解越多,它似乎并不容易。
  • @canice 我提供了第二个答案;这对你正在做的事情有好处吗……也许吧。至少你有选择。 :-)
【解决方案3】:

为了完整起见,这里是我最终使用的 MultiValueConverter - 请注意,我需要在对象数组上添加更好的错误检查,但这个想法有效。

public class AccessLevelToVisibilityConverter : MarkupExtension, IMultiValueConverter
{
    public object Convert( object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var visibility = Visibility.Hidden;

        var viewModel = (VariableDataViewModel)values[0];
        var item = (VariableDataItem)values[1];

        visibility = viewModel.IsVariableVisible(item);

        return visibility;
    }

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

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

这是对应的 XAML:

<Grid.Visibility>
   <MultiBinding Converter="{p:AccessLevelToVisibilityConverter}" >
      <Binding Path="DataContext",  
               RelativeSource="{RelativeSource 
                                AncestorType=UserControl}" />
      <Binding Path="." />
   </MultiBinding>
</Grid.Visibility>

我需要将它应用到多个 DataTemplate,所以我想这样做的方法是通过样式。

【讨论】:

    【解决方案4】:

    如果我们将检查安全性/可见性的控制反转方法从转换器转移到实际对象上会怎样?

    为此,我们需要让实体实例报告其可见性无需转换器,但仍使用 VM 在IOC 中提供授权两部分系统中的依赖注入方法。


    我用代码解释一下:

    使用访问级别的主题

    public enum SecurityLevels
    {
        publicLevel = 0,
        userLevel,
        adminLevel
    }
    

    VM 仍将包含当前的安全级别(最终基于实际用户的登录角色?)以及一个方法来报告访问实例是否有权访问/显示现在的水平。这将在 VM 必须遵守的名为 IAuthorize 的接口中定义。

    public interface IAuthorize
    {
        SecurityLevels CurrentLevel { get; set; }
        bool GetAuthorization(IAmIAuthorized instance);
    }
    

    IAmIAuthorized (Am-I-Authorized) 接口是每个实体都需要显示的,它是两部分安全性的第二部分。注意依赖注入函数DetermineAuthorizationFunc,它最终将通过依赖注入从VM提供:

    public interface IAmIAuthorized
    {
       SecurityLevels Level { get; }
    
       Visibility IsVisible { get; }
    
       bool IsAuthorized { get; }
    
       Func<IAmIAuthorized, bool> DetermineAuthorizationFunc { get; set; }
    }
    

    现在,如果受信任的实体对象可以派生 IAmIAuthorzied 并通过 DetermineAuthorizationFunc 报告可见性,或者我们可以使用接口创建包装器或派生类。在任何情况下,要显示的每个实体都必须以一种或另一种形式具有上述接口并调用DetermineAuthorizationFunc

    我们的 Xaml 变得更容易,因为我们知道实体的 IsVisible 正在完成它的工作,并根据当前 VM 状态及其 pre-req 状态返回一个 Visibility

     <TextBlock Text="{Binding Name}" Visibility="{Binding IsVisible}" />
    

    因此,我们将 IOC 从转换方法移到了 VM 中。

    这是在这种情况下 OP 的 VariableDataItem 类的实际实现:

    public class VariableDataItem  : IAmIAuthorized
    {
        public string Name { get; set; }
    
        public SecurityLevels Level { get; set; }
    
        public Func<IAmIAuthorized, bool> DetermineAuthorizationFunc { get; set; }
    
        public bool IsAuthorized
        {
            get { return DetermineAuthorizationFunc != null && 
                         DetermineAuthorizationFunc(this); }
        }
    
        public Visibility IsVisible
        {
            get { return IsAuthorized ? Visibility.Visible : Visibility.Hidden; }
        }    
    }
    

    使用来自 VM 的代码

    Holdings = new List<VariableDataItem>()
    {
        new VariableDataItem() {Name = "Alpha", DetermineAuthorizationFunc = GetAuthorization, Level = SecurityLevels.publicLevel  },
        new VariableDataItem() {Name = "Beta",  DetermineAuthorizationFunc = GetAuthorization, Level = SecurityLevels.userLevel    },
        new VariableDataItem() {Name = "Gamma", DetermineAuthorizationFunc = GetAuthorization, Level = SecurityLevels.adminLevel   }
    };
    

    以及IOC依赖注入方法:

    public bool GetAuthorization(IAmIAuthorized instance)
    {
        return (int)instance.Level <= (int)CurrentLevel; 
    }
    

    在该系统下,如果当前状态为 userLevel,我们的列表将仅显示 Alpha(公开)和 Beta(用户),但不显示 Gamma(仅限管理员):

    管理员可以查看所有内容:

    【讨论】:

      猜你喜欢
      • 2018-04-23
      • 1970-01-01
      • 2010-11-27
      • 1970-01-01
      • 2012-12-13
      • 2010-11-27
      • 1970-01-01
      • 2010-11-14
      • 2018-10-13
      相关资源
      最近更新 更多