Bind two elements' Visibility to one property - wpf

I have two Menu Item elements - "Undelete" and "Delete" who have complementary visibility: when one is shown, the other one is hidden.
In the code of the ViewModel I have a dependency property FilesSelectedCanBeUndeleted defined as below:
private bool _filesSelectedCanBeUndeleted;
public bool FilesSelectedCanBeUndeleted
{
get
{
return _filesSelectedCanBeUndeleted;
}
set
{
_filesSelectedCanBeUndeleted = value;
OnPropertyChanged("FilesSelectedCanBeUndeleted");
}
}
the XAML for the Undelete button looks like below:
<MenuItem Header="Undelete" Command="{Binding UndeleteCommand }"
Visibility="{Binding Path=FilesSelectedCanBeUndeleted,
Converter={StaticResource BoolToVisConverter}}" >
As you can see the Visibility of the Undelete is bind to the FilesSelectedCanBeUndeleted
property ( with the help of a BooleanToVisibilityConveter).
Now my question is, how can I write the XAML to bind the Visibility of the Delete button to the "NOT" value of the FilesSelectedCanBeUndeleted property?
Thanks,

Here is an example of a custom IValueConverter, that allows you to reverse the visibility logic. Basically, one MenuItem will be visible when your view-model property is true, and the other would be collapsed.
So you'd need to define two instances of the converter like so:
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<local:BooleanToVisibilityConverter x:Key="ReversedBooleanToVisibilityConverter" IsReversed="true" />

You can use apply the datatrigger to you menuitem to avoid another property in your viemodel like this -
<MenuItem Header="Delete"
Command="{Binding DeleteCommand }">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding FilesSelectedCanBeUndeleted}" Value="False">
<Setter Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>

Create new property on your ViewModel and just Negate 'FilesSelectedCanBeUndeleted' and then bind to it.

I did something like this a while ago with a simple negation...
private bool _filesSelectedCanBeUndeleted;
public bool FilesSelectedCanBeUndeleted{
get{
return _filesSelectedCanBeUndeleted;
}
set{
_filesSelectedCanBeUndeleted = value;
OnPropertyChanged("FilesSelectedCanBeUndeleted");
// You have also to notify that the second Prop will change
OnPropertyChanged("FilesSelectedCanBeDeleted");
}}
public bool FilesSelectedCanBeDeleted{
get{
return !FilesSelectedCanBeUndeleted;
}
}
Xaml could look like this then ....
<MenuItem Header="Delete"
Command="{Binding DeleteCommand }"
Visibility="{Binding Path=FilesSelectedCanBeDeleted, Converter={StaticResource BoolToVisConverter}}" >

Related

WPF DataBinding: How to bind "IsEnabled" of a ComboBoxItem if the ComboBox "ItemsSource" Property is used?

Currently I'm creating a custom Style for a ComboBox.
Current State of Styling
The next step should be the IsEnabled state of the ComboBoxItems. Therefore I created a Simple User Class and a UserList ObservableCollection bound to the ComboBox.
public class User
{
public int Id { get; private init; }
public string Name { get; private init; }
public bool IsEnabled { get; private init; }
public User(int id, string name, bool isEnabled = true)
{
Id = id;
Name = name;
IsEnabled = isEnabled;
}
}
<ComboBox
ItemsSource="{Binding UserList}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedUser}"
IsEnabled="{Binding IsComboboxEnabled}"
IsEditable="{Binding IsComboboxEditable}"
/>
To create and test the Disabled Style of the ComboBoxItems I want to Bind the IsEnabled Property of the User to the IsEnabled Property of the ComboBoxItem.
But I can't use a ItemContainerStyle here, because this overrides my custom Style:
<ComboBox
...
>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
So: How can I bind the IsEnabled Property without using a ItemContainerStyle or destroying the custom style I already add to the ComboBox?
If you have a custom ComboBoxItem Style that you don't want to override, then your Style inside the ItemContainerStyle should have a BasedOn, which will basically copy your default style, then add/replace with whatever is contained:
<Style TargetType="ComboBoxItem" BasedOn="YourComboBoxItemStyle">
Otherwise, if you have a ComboBox Style and want to add this then in either your Style in your ResourceDictionary you can add a Style.Resources in the Style that targets the ComboBoxItem:
<Style TargetType="ComboBox" x:Key="MyComboBoxStyle">
<Setter .../>
<Setter .../>
<Style.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</Style.Resources>
</Style>
I hope I understood the question and that my answer is helpful.

Selecting User Control for Data Template based on an Enum

I am working on a WPF app and currently I have an ItemsControl bound up to my View Model ObservableCollection and I have a DataTemplate that uses a UserControl to render the items on canvas. Can you use multiple User Controls and then switch which one is used based on an Enum? Another way to look it is to either create a Button or a TextBox for the item in the ObservableCollection based on an Enum.
You can select the data template for an item using a custom DataTemplateSelector. Assume we have the following:
public enum Kind
{
Button, TextBox,
}
public class Data
{
public Kind Kind { get; set; }
public string Value { get; set; }
}
Your data template selector might then look like this:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate ButtonTemplate { get; set; }
public DataTemplate TextBoxTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Data data = (Data)item;
switch (data.Kind)
{
case Kind.Button:
return ButtonTemplate;
case Kind.TextBox:
return TextBoxTemplate;
}
return base.SelectTemplate(item, container);
}
}
In XAML, declare templates for all the cases you want to cover, in this case buttons and text boxes:
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="ButtonTemplate" DataType="local:Data">
<Button Content="{Binding Value}" />
</DataTemplate>
<DataTemplate x:Key="TextBoxTemplate" DataType="local:Data">
<TextBox Text="{Binding Value}" />
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
Finally, have your ItemsControl create an instance of your custom template selector, initializing its two DataTemplateproperties from the above data templates:
<ItemsControl>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector
ButtonTemplate="{StaticResource ButtonTemplate}"
TextBoxTemplate="{StaticResource TextBoxTemplate}"/>
</ItemsControl.ItemTemplateSelector>
<ItemsControl.Items>
<local:Data Kind="Button" Value="1. Button" />
<local:Data Kind="TextBox" Value="2. TextBox" />
<local:Data Kind="TextBox" Value="3. TextBox" />
<local:Data Kind="Button" Value="4. Button" />
</ItemsControl.Items>
</ItemsControl>
(In real life, set the ItemsSource instead of declaring the items inline, as I did.)
For completeness: To access your C# classes you need to set up the namespace, e.g.,
xmlns:local="clr-namespace:WPF"
Another possible quick solution is to use Data Triggers:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content"
Value="{StaticResource YourDefaultLayout}" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue1}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue2}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
You could also define the template of a whole control using a trigger setter.
I prefer this because there is no need to define all the DataTemplateSelector stuff etc.

How to bind DataGridTemplateColumn.Visibility to a property outside of DataGrid.ItemsSource?

I need to bind the Visibility of a DataGridTemplateColumn to a property outside of the DataGrid.ItemsSource,because i need to bind this column in the all the rows to one property inside the ViewModel,but as far as i know you just can bind that to something inside the ItemsSource or you should use ElementStyle and EditingElementStyle
I've Already tried this code:
<DataGridTemplateColumn Header="post"
Visibility="{Binding DataContext.ProjectPostVisibility
, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MvvmCommonControl:DataGrid}}"/>
And i'm Sure my binding is correct because it works fine when i bind the DataGridCell.Visibility like below:
<DataGridTemplateColumn Header="post">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Visibility" Value="{Binding DataContext.ProjectPostVisibility,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MvvmCommonControl:DataGrid}}"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn >
Your binding is correct, but it won't work with DataGridTemplateColumn directly because it's not in the visual tree. So it's not inherting DataContext.
You need to bind the DataGridTemplateColumn from code behind. Here is a demo that shows a way of doing it.
As mentionned in other answers, the column isn't part of the visual/logical tree and doesn't inherit from FrameworkElement meaning it has no DataContext. That's why your binding doesn't work.
However you can add a dummy (collapsed) FrameworkElement at a level where the DataContext is what you're looking for (so taking your example, it'd be at the DataGrid's level), collapse it and use it as the Source of your Binding with the x:Reference markup extension.
Here's an example :
<FrameworkElement x:Name="Proxy" Visibility="Collapsed"/>
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn Header="post"
Visibility="{Binding DataContext.ProjectPostVisibility, Source={x:Reference Name=Proxy}}"/>
</DataGrid.Columns>
</DataGrid>
Add this setter in the DataGridTemplateColumn.CellStyle and done:
<Setter Property="Visibility" Value="{Binding DataContext.isVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
If you need more help look at my example below.
I want the Remove button to not be visible at the project level. First you have to make sure you have a isVisible property in your view model:
private System.Windows.Visibility _isVisible;
public System.Windows.Visibility isVisible
{
get { return _isVisible; }
set
{
if (_isVisible != value)
{
_isVisible = value;
OnPropertyChanged("isVisible");
}
}
}
Then:
if (isProj == false)
this.model.isVisible = Visibility.Visible;
else
this.model.isVisible = Visibility.Collapsed;
XAML:
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<Button x:Name="btnRemove" Content="X">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="50" />
</Style>
</Button.Style>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="Red"/>
<Setter Property="Visibility" Value="{Binding DataContext.isVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
</Style>
</DataGridTemplateColumn.CellStyle>

Is it possible to bind a property to value of a setter?

Is it possible to bind a property to value of a setter like this
<Setter Property="ToolTip" Value="{Binding updateToolTip}"/>
Yes you can bind provided that updateToolTip is publically \ internally found in the data context of the UI control for which the tooltip setter is targetted.
When you say its not working, do you mean it gives you an error or wrong content? It works for me: (it is the value of the property not the setter though)
XAML
<Button Click="Button_Click">
<Button.Style>
<Style TargetType="Button">
<Setter Property="ToolTip" Value="{Binding MySelectedItem}"/>
</Style>
</Button.Style>
</Button>
<ComboBox ItemsSource="{Binding ListOfValues}" SelectedItem="{Binding MySelectedItem}" Grid.Row="1"/>
Code:
public string MySelectedItem
{
get
{
return _selectedValueString;
}
set
{
_selectedValueString = value;
OnPropertyChanged("MySelectedItem");
}
}

WPF binding based on comparison

There is a relatively simple thing I'm trying to achieve but I'm unsure how to do it. Basically, I have a CLR class as follows:
class SomeClass
{
public SomeEnum Status;
}
public enum SomeEnum { One, Two, Three };
I've got a DataGrid that I'm binding an ObservableCollection<SomeClass> programmatically through the code-behind. In this DataGrid I have a DataGridTemplateColumn containing two buttons, as follows:
<toolkit:DataGridTemplateColumn Header="Actions">
<toolkit:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="ActionOne" />
<Button Content="ActionTwo" />
</StackPanel>
</DataTemplate>
</toolkit:DataGridTemplateColumn.CellTemplate>
</toolkit:DataGridTemplateColumn>
What I want to do is bind the IsEnabled property of these buttons to a comparison based on the value of {Binding Path=Status}. For example, in pseudocode:
ActionOne.IsEnabled = BoundValue.Status != SomeEnum.Two
ActionTwo.IsEnabled = BoundValue.Status == SomeEnum.One || BoundValue.Status == SomeEnum.Two
Is there anyway to do this in XAML? The alternative would be just to write a value converter for each button, but since the content and other details of the button may vary, too, I don't want to end up writing like 6 value converters.
Cheers!
Why not expose additional Properties in SomeClass that performs the comparison logic?
ex:
public bool ActionOneEnabled
{
get { return Status != SomeEnum.Two; }
}
Then you can easily bind the Button's IsEnabled to the appropriate Property.
Don't forget to include an OnPropertyChanged("ActionOneEnabled") in your setter for Status - so that when your Status changes your Properties based on Status are re-evaluated.
You could do this using a DataTrigger in conjunction with a Converter like below. However, Bryan's solution has the benefit of not using multiple converters and it looks like that was one of your concerns so his answer might be better for your scenario.
<Button>
....
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Status, Converter={StaticResource yourConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
Another option would be to not use the DataTrigger and add the binding directly in the IsEnabled property:
<Button
IsEnabled="{Binding Path=Status, Converter={StaticResource yourConverter}}"
...
/>

Resources