I have create a DataTemplate for my Component object. I added DataTrigger to determine if the component should be visible or not. Essentially we have filters and the code checks those filters to determine if the component should be Visible or Collapse. The issue I have is that I want the trigger to set the visibility to "Collapse" or "Visible" of the parent container, i.e a ListBoxItem. The code works but sets it at the Border instead.
The template starts like this:
<DataTemplate DataType="{x:Type local:Component}">
<Border .....
I am providing the code for my Trigger and I'll explain what I tried below without success.
<DataTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource TrueWhenComponentIsVisible}">
<Binding Path="Type" />
<Binding Path="Dependency"/>
<Binding Path="SelectedType" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
<Binding Path="SelectedDepencency" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource TrueWhenComponentIsVisible}">
<Binding Path="Type" />
<Binding Path="Dependency"/>
<Binding Path="SelectedType" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
<Binding Path="SelectedDepencency" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger.Setters>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
For the Setter Property
<Setter Property="Visibility" Value="Collapsed"></Setter>
I attempted to use the binding to get the listboxitem like this:
<Setter Property="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}, Path=Visibility}" Value="Collapsed"></Setter>
I get this error when I try to run it, so I assume I can't use binding there at all and need a different approach?
A 'Binding' cannot be set on the 'Property' property of type 'Setter'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject
Worked for me.
Don't know why it did not work for you?
<ListBox x:Name="lb" ItemsSource="{Binding}" DisplayMemberPath="Text">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="{Binding Path=Vis}" />
</Style>
</ListBox.Resources>
</ListBox>
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
List<TextVis> TextViss = new List<TextVis>();
TextVis tv1 = new TextVis();
tv1.Text = "tv1";
tv1.Vis = System.Windows.Visibility.Hidden;
TextViss.Add(tv1);
TextVis tv2 = new TextVis();
tv2.Text = "tv2";
tv2.Vis = System.Windows.Visibility.Visible;
TextViss.Add(tv2);
lb.ItemsSource = TextViss;
}
public class TextVis
{
public string Text { get; set; }
public Visibility Vis { get; set; }
}
}
I want the trigger to set the visibility to "Collapse" or "Visible" of
the parent container, i.e a ListBoxItem
Change the ItemContainerStyle, like this:
<ListBox ...>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource TrueWhenComponentIsVisible}">
<Binding Path="Type" />
<Binding Path="Dependency"/>
<Binding Path="SelectedType" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
<Binding Path="SelectedDepencency" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
BTW, you should really create a proper ViewModel and move all this logic to the ViewModel level instead of so much MultiBinding and Converter-based stuff.
Related
I have a custom button control which has a dependency property "IsRequired"
public static readonly DependencyProperty IsRequiredProperty = DependencyProperty.RegisterAttached(nameof(IsRequired), typeof(bool), typeof(RequiredButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
public bool IsRequired
{
get { return (bool)GetValue(IsRequiredProperty); }
set { SetValue(IsRequiredProperty, value); }
}
And implements an interface
public interface IRequiredControl
{
bool IsEnabled { get; }
bool IsRequired { get; }
}
And I have a converter that uses this interface
<sharedConverters:IsRequiredToImageConverter x:Key="IsRequiredToImageConverter"
DisabledImage="{StaticResource DisabledDrawing}"
NormalImage="{StaticResource NormalDrawing}"
RequiredImage="{StaticResource IsRequiredDrawing}" />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IRequiredControl requiredControl)
{
return !requiredControl.IsEnabled ? DisabledImage : requiredControl.IsRequired ? RequiredImage : NormalImage;
}
return DependencyProperty.UnsetValue;
}
I use the converter and bind the image source to the control, as seen below.
<DataTemplate x:Key="RightSideAddTemplate">
<sharedControls:RequiredButton x:Name="addButton"
Command="{x:Static commands:AddCommand}"
IsRequired="{Binding IsButtonRequired}">
<Image Source="{Binding ElementName=addButton, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IsRequiredToImageConverter}}" />
</sharedControls:RequiredButton>
</DataTemplate>
The issue occurs here where the converter is never called when the "IsButtonRequired" viewmodel property is changed. When I set the "IsRequired" of the "AddButton" explicitly to true it works correctly. How can I make the converter update on a change to the "IsRequired" property?
Note I have another solution working where I use a multivalue converter, but I would prefer to get element binding solution working, because it requires much less xaml.
<Image Style="{StaticResource AddImageButtonStyle}">
<Image.Source>
<MultiBinding Converter="{StaticResource IsRequiredToImageMultiConverter}">
<MultiBinding.Bindings>
<Binding ElementName="addButton" Path="IsEnabled" />
<Binding Path="IsButtonRequired" />
</MultiBinding.Bindings>
</MultiBinding>
</Image.Source>
</Image>
The Binding expression
<Image Source="{Binding ElementName=addButton,
Converter={StaticResource IsRequiredToImageConverter}}" />
binds directly to the RequiredButton control, and is not supposed to be triggered when a property of the control changes.
The proper way to implement this is a MultiBinding on both the IsEnabled and IsRequired properties of the control:
<MultiBinding Converter="{StaticResource IsRequiredToImageMultiConverter}">
<MultiBinding.Bindings>
<Binding ElementName="addButton" Path="IsEnabled"/>
<Binding ElementName="addButton" Path="IsRequired"/>
</MultiBinding.Bindings>
</MultiBinding>
The multi-value converter would have to test two boolean values.
Alternatively to using a MultiBinding, a Style with a set of MultiTriggers would also work:
<Style TargetType="sharedControls:RequiredButton">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource DisabledDrawing}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="true" />
<Condition Property="IsRequired" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource NormalDrawing}"/>
</Setter.Value>
</Setter>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="true" />
<Condition Property="IsRequired" Value="true" />
</MultiTrigger.Conditions>
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource IsRequiredDrawing}"/>
</Setter.Value>
</Setter>
</MultiTrigger>
</Style.Triggers>
</Style>
I have a datagrid and I want to set the width of a column according to some values, so I am trying to use a multibinding in this way:
<DataGridTextColumn.Width>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.MyProperty" />
<Binding Source="0"/>
</MultiBinding>
</DataGridTextColumn.Width>
The converter is fired, but the width is not changed according to de value it returns.
However, I have a similiar converter to set the visibility and it works as expected:
<DataGridTextColumn.Visibility>
<MultiBinding Converter="{StaticResource MyConverterVisibilityMultiValueConverter}">
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.NombreProveedor" />
<Binding Source="0"/>
</MultiBinding>
</DataGridTextColumn.Visibility>
Why does it work with the visibivilty but not with the width?
Thanks.
EDIT:
I have tried setting the width of the textblock inside the column in this way:
Value converter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//I have tried both ways, DataGridLength and return 20
//return new DataGridLength(20, DataGridLengthUnitType.SizeToHeader);
return 20;
}
Xaml: Option 1, setting the width directly, it works
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Width" Value="20"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
Xaml: option 2: trying with value converter, it doesn't work
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Width">
<Setter.Value>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}" >
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.Property1" />
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.Property2" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
The converter is rised and it returns 20, but the column doesn't take this value.
Ensure that the converter returns a DataGridLength value:
return new DataGridLength(100, DataGridLengthUnitType.Pixel);
Due to some shortcomings with CodedUI, it's difficult to select items in the DataGrid. One work around that I've found is to override the ItemContainerStyle like so:
<DataGrid.ItemContainerStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="AutomationProperties.AutomationId">
<Setter.Value>
<MultiBinding StringFormat="ArisingID_{0}">
<Binding Path="(DataGridRow.Tag)" RelativeSource="{RelativeSource Mode=Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="AutomationProperties.Name">
<Setter.Value>
<MultiBinding StringFormat="ArisingID_{0}">
<Binding Path="(DataGridRow.Tag)" RelativeSource="{RelativeSource Mode=Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ItemContainerStyle>
and then populate each Row.Tag with a unique ID in the code behind:
private void MyDataGrid_OnLoadingRow(object sender, DataGridRowEventArgs e)
{
var item = e.Row.Item as IMyViewModel;
e.Row.Tag = item.UniqueSeqId;
}
However, one issue is that this overrides some of the "default style" of the data grid row - each cell on the data grid seems to be separately selectable, rather than it behaving as just one row.
What's the best way to incorporate the default styles along with these modifications?
There is a BasedOn property that will set your style, and then add the extra setters you've added:
<DataGrid.ItemContainerStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource DataGridRowStyle}">
<Setter Property="AutomationProperties.AutomationId">
<Setter.Value>
<MultiBinding StringFormat="ArisingID_{0}">
<Binding Path="(DataGridRow.Tag)" RelativeSource="{RelativeSource Mode=Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="AutomationProperties.Name">
<Setter.Value>
<MultiBinding StringFormat="ArisingID_{0}">
<Binding Path="(DataGridRow.Tag)" RelativeSource="{RelativeSource Mode=Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
I have a WPF DataGrid that is configured to only allow a single cell selection, i.e.:-
SelectionMode="Single"
SelectionUnit="Cell"
What I'm trying to do is change the background of the row header of whichever row contains the currently selected cell. I've come up with the following so far, but it isn't working.
Here is the XAML style, which binds the background property to a multi-value converter. The converter is bound to the header's DataGridRow and the SelectedCells property of the DataGrid:-
<Style TargetType="{x:Type DataGridRowHeader}">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource dataGridHeaderBackgroundConverter}" Mode="OneWay">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridRow}}" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}"
Path="SelectedCells"
Mode="OneWay"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
The multi-converter's Convert method looks like this (I've removed null checking code to keep it concise):-
var row = values[0] as DataGridRow;
var selectedCells = values[1] as IList<DataGridCellInfo>;
var selectedCell = selectedCells[0];
return selectedCell.Item == row.Item ? Colors.Red : Colors.LightGray;
The method only seems to get called when the DataGrid is initially rendered (when there are no selections). It doesn't get called after a cell has been selected, so what am I missing?
You can update your style as follows and write the EqualityConverter which would be a MultiValueConverter to be used to bind the DataGrid's CurrentCell and RowHeader context in the DataTrigger. So this trigger will be fired everytime you select the cell on your DataGrid.
<Style TargetType="{x:Type DataGridRowHeader}">
<Style.Triggers>
<DataTrigger Value="true">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityConverter}">
<Binding/>
<Binding Path="CurrentCell" RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
and in converter's Convert method:
if(values[0] == ((DataGridCellInfo)values[1]).Item)
{
return true;
}
return false;
Tested it.. worked well
Nitin's solution works perfectly, but during updating data in my data grid, this warning is raised:
System.Windows.Data Warning: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.DataGrid', AncestorLevel='1''. BindingExpression:Path=CurrentCell; DataItem=null; target element is 'DataGridRowHeader' (Name=''); target property is 'NoTarget' (type 'Object')
Any solution to get rid of this warning?
Anyway here is another working approach...
<DataGrid.RowHeaderStyle>
<Style TargetType="DataGridRowHeader">
<Setter Property="Background" Value="black" />
<Style.Triggers>
<Trigger Property="IsRowSelected" Value="True">
<Setter Property="Background" Value="white" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowHeaderStyle>
I have a DataGrid, in which I want to change the background of a row according to the values of the ItemSource, so I need to pass the current item, but I don't know how.
I am doing that:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource myDataGridBackgroundMultiValueConverter}">
<MultiBinding.Bindings>
<Binding ElementName="ucPrincipal" Path="DataContext.MyProperty01FromDataContext"/>
<Binding ElementName="ucPrincipal" Path="DataContext.MyProperty02FromDataContext"/>
<Binding ElementName="ucPrincipal" Path="DataContext.MyProperty03FromDataContext"/>
<Binding ElementName="dgdMyGrid" Path="CurrentItem"/>
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
The DataGrid has as ItemsSource a collection of MyDataClass (that has many properties) in the ViewModel. I need to pass to the converter the MyDataClass with the information of the row.
CurrentItem does not work, because I always receive null.
All the other parameters are ok.
DataGrid has no CurrentItem property, only CollectionViews have, DataGrid has a SelectedItem. You should see a binding error because of this as well.
If by current item you mean the data item to which the styled row belongs that would be the DataContext of the current row, which can be targeted via <Binding />.
Well, finnally, I get the way to pass the dataContext of the row to the converter. I do the following:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Visibility" Value="{Binding ChangeTracker.State, Converter={StaticResource visibilidadFilaBorradaConverter}}"/>
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource ucTareasMatenimientoDataGridBackgroundMultiValueConverter}">
<MultiBinding.Bindings>
<Binding ElementName="ucPrincipal" Path="DataContext.Property01"/>
<Binding Path="DataContext" RelativeSource="{RelativeSource Mode=Self}" />
<Binding ElementName="ucPrincipal" Path="DataContext.Property02"/>
<Binding ElementName="ucPrincipal" Path="DataContext.Property03"/>
<Binding ElementName="ucPrincipal" Path="DataContext.Property04"/>
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
In the second parameter for the multivalue converter, I use the RelativeSource, to be able to pass the datContext of the row. In this way, I can compare its information with other properties of the principal dataContext of the control.