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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29974236/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:06:57  来源:igfitidea点击:

Binding WPF control visibility using multiple variables from the ViewModel

c#wpfxaml

提问by canice

I'm writing a WPF user control which displays a dynamically generated TabControl with multiple pages, each page in turn contains a list of dynamically generated controls (TextBox, Checkbox etc).

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

I want to set the visibility of the TextBox, CheckBox controls based on whether the user has permission to view them or not. This permission is a combination of a value on each controls ViewModel ('VisiblyBy') and also a property of the overall UserControl ViewModel ('UserRole'). I'm just getting started on WPF but the standard method seems to be to use a ValueConvertor - however I don't understand how I could write one which would combine/access the different properties (VisiblyBy and UserRole) as they exist at different levels in my ViewModel hierary.

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

Here is part of the XAML where I bind to the 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>

I would also need to extend the number of variables that control visibility in the future as it would also depend where in the application it is used from.

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

回答by Ayyappan Subramanian

You can try IMultiValueConverter and use Multibinding.

您可以尝试 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();
    }
}

In the Convert(...)method, the order of the different inputs in the valuesarray is the same order as in the MultiBinding.Bindingscollection.

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

In this example values[0]contains the Roleproperty and values[1]will be OtherPropbecause that is the order they got inserted in XAML

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

回答by ΩmegaMan

I would simply create a property on the ViewModel (or wherever you get the data) called IsAuthorizedand bind to that property. For it will return whether the user is authorized or not after calling the other properties you mention and process that authorization logic to return a visible state (no need for a converter).

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

Note that we have Roleand IsOtherproperties actually also call PropertyChangeon our IsAuthorizedprop. Which when either of those change, the Xaml is duly notified and changes accordingly.

请注意,我们拥有RoleIsOther属性实际上也调用PropertyChange了我们的IsAuthorized道具。当其中任何一个发生变化时,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");
       }
}

People seem to think that one has to only bind to a specific property(ies), but why make your life harder when a simply call to PropertyChanged with a composite property will do the job.

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

回答by canice

Just for completeness, here is the MultiValueConverter I ended up using - note I need add in better error checking on the object array but the idea works.

为了完整起见,这是我最终使用的 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;
    }
}

And this is the corresponding XAML:

这是相应的 XAML:

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

I need to apply it to multiple DataTemplates so I guess the way to do that is through a style.

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

回答by ΩmegaMan

What if we transferred the Inversion of Control method to check security/visibility away from the Converter and on to the actual object?

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

To do that we would need to have the entity instance report its visibility without the need of a converterbut still use the VM to provide authorizationin a IOCdependency injected method in a two part system.

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



Let me explain by code:

我用代码解释一下:

Using the motif of access levels

使用访问级别的主题

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

The VM will still contain the current Security level (ultimately based on the actual users login role?) along with a method to report if an accessing instance has the right to access/be shown for the current level. This will be defined in an interface which the VM has to adhere to named IAuthorize.

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

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

The IAmIAuthorized(Am-I-Authorized) interface will be required of every entity to be shown and is the second part of the two-part security. Notice the Dependency Injection function DetermineAuthorizationFuncwhich will eventually be supplied from the VM via Dependency Injection:

IAmIAuthorizedAM-I-授权)接口将需要每一个实体的被示出并是两部分的安全性的第二部分。注意依赖注入函数DetermineAuthorizationFunc,它最终会通过依赖注入从虚拟机提供:

public interface IAmIAuthorized
{
   SecurityLevels Level { get; }

   Visibility IsVisible { get; }

   bool IsAuthorized { get; }

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

Now the entity object if trusted can derive IAmIAuthorziedand report visibility via the DetermineAuthorizationFuncor we can create a wrapper or a derived class with the interface. In any case, every entity to be shownhas to have the above interface in one form or another and call DetermineAuthorizationFunc.

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

Our Xaml becomes easier for we know that the entities' IsVisibleis doing its job and returning a Visibilitybased on the current VM state with its pre-req state.

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

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

With that we have moved the IOC away from a Conversion method and into the VM.

有了这个,我们已经将 IOC 从转换方法移到 VM 中。

Here is an actual implementation of the OP's VariableDataItemclass in this situation:

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

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

With code from the VM

使用来自 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   }
};

And the IOC dependency injected method:

以及IOC依赖注入方法:

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


Under that system if the current state is userLevelour list will only show Alpha (which is public) and Beta (which is User) but not Gamma which is admin only:

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

enter image description here

在此处输入图片说明

Admin can see all:

管理员可以看到所有:

enter image description here

在此处输入图片说明