繁体   English   中英

使用ViewModel中的多个变量绑定WPF控件可见性

[英]Binding WPF control visibility using multiple variables from the ViewModel

我正在编写一个WPF用户控件,它显示一个包含多个页面的动态生成的TabControl,每个页面依次包含一个动态生成的控件列表(TextBox,Checkbox等)。

我想根据用户是否有权查看它们来设置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。

<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中插入的顺序

我只是在ViewModel(或获取数据的任何地方)上创建一个名为IsAuthorized属性并绑定到该属性。 因为它会在调用您提到的其他属性后返回用户是否被授权,并处理该授权逻辑以返回可见状态(不需要转换器)。

请注意,我们有RoleIsOther属性实际上也在我们的IsAuthorized prop上调用PropertyChange 当其中任何一个发生变化时,Xaml会得到适当的通知并相应地进行更改。

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来完成工作时,为什么会让你的生活更加艰难。

为了完整性,这里是我最终使用的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>

我需要将它应用于多个DataTemplates,所以我想这样做的方法是通过一种风格。

如果我们转移了Inversion of Control方法来检查转换器的安全性/可见性以及实际对象,该怎么办?

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


让我用代码解释一下:

使用访问级别的主题

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

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

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

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

public interface IAmIAuthorized
{
   SecurityLevels Level { get; }

   Visibility IsVisible { get; }

   bool IsAuthorized { get; }

   Func<IAmIAuthorized, bool> DetermineAuthorizationFunc { get; set; }
}

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

在此输入图像描述

管理员可以看到所有:

在此输入图像描述

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM