I have this following style. My problem is that even tho multibinding is always true, I am observing setter only once when Window is loaded. Whenever I change Window Height this MyMultiValueConverter is being called I can see it in logs, but not the "<Setter>".
<Style x:Key="SeperatorRectangleStyle" TargetType="Rectangle">
<Setter Property="Width" Value="2"/>
<Setter Property="Height" Value="50"/>
<Setter Property="MinHeight" Value="49"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource myTheMultiValueConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}" Path="ActualHeight"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}" Path="Name"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Height" Value="{Binding Converter={StaticResource mySeperatorHeightConverter}, ConverterParameter=DataTrigger}"/>
</DataTrigger>
</Style.Triggers>
</Style>
MyMultiValueConverter is:
class TheMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
foreach (var i in values)
{
Console.WriteLine("TheMultiValueConverter values" + i);
}
Console.WriteLine("");
return true
}
}
MySeperatorHeightConverter is
class SeperatorHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Console.WriteLine("!-!- SeperatorHeightConverter: " + parameter);
return 100;
}
}
The problem in your code is that you Bind something to the Height that does not change. That is why the second converter does not get triggered.
See this code:
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=ActualHeight, Converter={StaticResource mySeperatorHeightConverter}, ConverterParameter=DataTrigger}"/>
This small change makes it so that the SeperatorHeightConverter gets triggered everytime the Window Height changes.
Related
I'm trying to override the output of a label, say it contained "Account" and a client wants account rendered as "Member" (so kind of think of this as a localisation converter?)
My Question; is this possible with "hardcoded" content? or MUST i create a static file containing all label content (with iNotifiyStatic of course)? *for binding?
xaml:
<Label Style="{StaticResource LabelLeft}" Content="Account Name:"></Label>
Resource File: Including all attempts made, from multiple sources heres the most meaningful one.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters ="clr-namespace:Company.Resources.Converters">
<converters:LocationLabelsConverter x:Key="LocationLabelsConverter" />
<Style x:Key="LabelLeft" TargetType="{x:Type Label}" >
<Setter Property="Margin" Value="10 0 0 0"></Setter>
<Setter Property="Height" Value="22"></Setter>
<Setter Property="Padding" Value="0 0 0 0"></Setter>
<Setter Property="VerticalContentAlignment" Value="Center"></Setter>
<!-- Att1 -->
<!--<Setter Property="TextBlock.Text" Value="{Binding RelativeSource={RelativeSource self},
Path=Content,
Converter={StaticResource LocationLabelsConverter}}"></Setter>-->
<!-- Att2 -->
<!--<Setter Property="Content">
<Setter.Value>
<Binding Path="Content" RelativeSource="{RelativeSource self}">
<Binding.Converter>
<converters:LocationLabelsConverter/>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>-->
<!-- Att3 -->
<!--<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource self},
Path=Content,
Converter={StaticResource LocationLabelsConverter}}">
<Setter Property="Content" Value="Test123"/>
</DataTrigger>
</Style.Triggers>-->
</Style>
And here's the converter:
[ValueConversion(typeof(string), typeof(string))]
public sealed class LocationLabelsConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value != null)
{
return "hello sweety";// (string)value; //The goal here in the end is to run it through a method to replace string with correct text.
}
else return null;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (string)value;
}
}
you can apply converter like this:
<Label Content="{Binding Source='Account Name:', Converter={StaticResource LocationLabelsConverter}"/>
I am trying to bind visibility of a styled control to the condition that its tag is equal to the selected index of a TabControl.
I use RelativeSource TemplatedParent but it's not being set, and I suspect it's because I am not setting Template property in Style.
Here is my code:
<Grid x:Name="Telas" Grid.Column="1">
<Grid.Resources>
<vw:TagIsIndexBooleanConverter x:Key="TagIsIndexBooleanConverter"/>
<Style x:Key="SelectedIndexVisibleStyle" TargetType="UserControl">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource TagIsIndexBooleanConverter}">
<Binding Path="Tag" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="SelectedIndex" ElementName="menu"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<vw:TelaColetaView
x:Name="telaColeta"
DataContext="{Binding TelaColetaVM}"
Style="{StaticResource SelectedIndexVisibleStyle}"
Tag="0"/>
<vw:TelaPacientesView
x:Name="telaPacientes"
DataContext="{Binding TelaPacientesVM}"
Style="{StaticResource SelectedIndexVisibleStyle}"
Tag="1"/>
<vw:TelaConfiguraçõesView
x:Name="telaConfigurações"
DataContext="{Binding TelaConfiguraçõesVM}"
Style="{StaticResource SelectedIndexVisibleStyle}"
Tag="4"/>
</Grid>
And converter:
public class TagIsIndexBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Any(v => v == null || v == DependencyProperty.UnsetValue))
return Binding.DoNothing;
var tag = System.Convert.ToInt32(values[0]);
var index = System.Convert.ToInt32(values[1]);
var result = tag == index;
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can't use TemplatedParent in this case because there isn't one; that's meant to used inside a ControlTemplate to specify that the source for the binding is the control that you're applying the template to.
<Binding Path="Tag" RelativeSource="{RelativeSource TemplatedParent}"/>
But this isn't inside a ControlTemplate. Instead, Tag is just a property of the thing you're styling. Ordinarily you'd do a trigger like this for Tag:
<Trigger Property="Tag" Value="0">
...but you need a multibinding to get the SelectedIndex from menu, and it's got to be a MultiDataBinding because you need to specify ElementName.
So you need a binding. To bind to one of your own properties instead of a property of your DataContext, you bind with a RelativeSource of Self:
<Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
OP also found he had to set the TargetType of the Style to "UserControl", for the Tag binding to work.
I have a DataGrid with several Columns. Some of these columns are something like
state|Color1|Color2|Color3|...
I want to do this:
If state==1 => RowForeground = Color1
If state==2 => RowForeground = Color2
If state==3 => RowForeground = Color3
...
The very first solution I can think is to use several Data Triggers:
<DataTrigger Binding="{Binding Path=state}" Value="0">
<Setter Property="Foreground" Value="{Binding Path=color0, Converter={StaticResource str2clrConverter}}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=state}" Value="1">
<Setter Property="Foreground" Value="{Binding Path=color1, Converter={StaticResource str2clrConverter}}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=state}" Value="2">
<Setter Property="Foreground" Value="{Binding Path=color2, Converter={StaticResource str2clrConverter}}"/>
</DataTrigger>
[...]
Is there a better solution?
Multibinding is the way!
Here how I solved:
<Setter Property="Foreground">
<Setter.Value>
<MultiBinding Converter="{StaticResource frgConverter}">
<Binding Path="state"/>
<Binding Path="color1"/>
<Binding Path="color2"/>
<Binding Path="color3"/>
</MultiBinding>
</Setter.Value>
</Setter>
And the Converter:
public class GridForegroundConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int iColor;
int nState = System.Convert.ToInt32(values[0]);
if (nState < 0 || nState > 3)
throw new ArgumentOutOfRangeException("State");
iColor = System.Convert.ToInt32(values[nState + 1]);
byte[] bytes = BitConverter.GetBytes(iColor);
Color color = Color.FromRgb(bytes[2], bytes[1], bytes[0]);
return new SolidColorBrush(color);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I want to add a DataTrigger to my base TextBox style so that it sets the foreground color to a different value if it is inside of a DataGridCell that is selected. Here is what my trigger looks like:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}, Path=IsSelected}"
Value="True">
<Setter Property="Foreground"
Value="White" />
</DataTrigger>
</Style.Triggers>
This works great, except that when my TextBox is not in a DataGrid the Binding fails and writes an exception to the output window. How can I prevent this.
I basically want to say if Parent is a DataGridCell then apply this trigger otherwise ignore it.
In general just only apply the style where applicable. If you want implicit application use nested styles:
<Style TargetType="{x:Type DataGrid}">
<Style.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}, Path=IsSelected}"
Value="True">
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
If you have other parts which you want to apply to all TextBoxes take out those parts in a serarate style and use BasedOn in the style which applies to the TextBoxes inside the DataGrid.
Edit: MultiDataTrigger seems to return right away if a condition is not met so you can avoid binding errors:
<Style TargetType="{x:Type TextBox}">
<Style.Resources>
<vc:HasAncestorOfTypeConverter x:Key="HasAncestorOfTypeConverter" AncestorType="{x:Type DataGridCell}" />
</Style.Resources>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition
Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource HasAncestorOfTypeConverter}}"
Value="True" />
<Condition
Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}, Path=IsSelected}"
Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="Red" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
public class HasAncestorOfTypeConverter : IValueConverter
{
public Type AncestorType { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return false;
DependencyObject current = value as DependencyObject;
while (true)
{
current = VisualTreeHelper.GetParent(current);
if (current == null)
{
return false;
}
if (current.GetType() == AncestorType)
{
return true;
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
This of course causes quite som overhead so it might not be such a good solution, then again if the RelativeSource-binding fails it also had to go up the tree first.
I have a grid of values in a DataGrid. In each cell I have one of 4 values, I want each value to have an associated colour.
In the past all my data triggers have been of the format;
<DataTrigger Binding="{Binding Status}" Value="R">
<Setter Property="xcdg:DataRow.Background" Value="Pink" />
</DataTrigger>
But I need something of the format;
<DataTrigger Binding="{Binding *}" Value="R">
<Setter Property="xcdg:DataRow.Background" Value="Pink" />
</DataTrigger>
So that any cell's value will be checked. The reason I can't use explicit bindings is that the number of columns and their names is dynamic, I use extend CustomTypeDescriptor to expose the columns and rows to enter the grid.
Thanks in advance!
Is it an option to use a ValueConverter in your binding? That way you could check in the converter which value it should return.
<DataTrigger Binding="{Binding Path=Content, RelativeSource={x:Static RelativeSource.Self}}" Value="Failure">
<Setter Property="xcdg:DataRow.Background" Value="Red" />
</DataTrigger>
I needed to use a Relative source.
Further to that what I ended up using was;
<xcdg:DataGridControl.Resources>
<c:ColorConverter x:Key="colorConverter" />
<Style TargetType="{x:Type xcdg:DataCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type xcdg:DataRow}}}" Value="False">
<Setter Property="xcdg:DataCell.Background" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource colorConverter}}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsDirty, RelativeSource={RelativeSource AncestorType={x:Type xcdg:DataRow}}}" Value="True">
<Setter Property="xcdg:DataCell.Background" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource colorConverter}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</xcdg:DataGridControl.Resources>
Where my Colour converter looks a bit like this;
[ValueConversion(typeof(DataCell), typeof(Brush))]
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var cell = (DataCell)value;
var content = (string)cell.Content;
if (content == null || cell.ParentRow.IsSelected)
{
return DependencyProperty.UnsetValue;
}
if (content == "Unknown")
{
return new SolidColorBrush(Colors.LightYellow);
}
...
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
My only problem now is that when I scroll my background colours aren't updated when the DataRows are reused.
I can't find an event to hook into when a DataRow is reused either...