[英]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
属性并绑定到该属性。 因为它会在调用您提到的其他属性后返回用户是否被授权,并处理该授权逻辑以返回可见状态(不需要转换器)。
请注意,我们有Role
和IsOther
属性实际上也在我们的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);
}
每个要显示的实体都需要IAmIAuthorized
( Am-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.