Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding WPF control visibility using multiple variables from the ViewModel

Tags:

c#

wpf

xaml

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).

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.

Here is part of the XAML where I bind to the ViewModel:

<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.

like image 806
canice Avatar asked Apr 30 '15 17:04

canice


2 Answers

You can try IMultiValueConverter and use 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 values array is the same order as in the MultiBinding.Bindings collection.

In this example values[0] contains the Role property and values[1] will be OtherProp because that is the order they got inserted in XAML

like image 75
Ayyappan Subramanian Avatar answered Nov 14 '22 16:11

Ayyappan Subramanian


Bind to a composite property which holds the status instead without the need for a converter.


I would create a composite property on the ViewModel called IsAuthorized and just bind to that property. For it always return the current status when the other properties are set.

How?

To accomplish the composite property the Role and IsOther properties also call PropertyChange on the IsAuthorized property; which always keeps the status fresh on the page.

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.

like image 29
ΩmegaMan Avatar answered Nov 14 '22 17:11

ΩmegaMan