WPF Resource Font Size - Relative to Another Resource - wpf

My question is very similar to Wpf custom control template - relative font size ... but I'm trying to set the font size in one resource relative to that of another resource. I implemented the solution posted by Thomas, but I can't figure out how to make the Relative source point to another resource.
<my:MathConverter x:Key="MathConverter" />
<Style x:Key="propertyText">
<Setter Property="Control.Foreground" Value="Gray" />
<Setter Property="Control.FontSize" Value="12" />
<Setter Property="Control.Padding" Value="10,2,2,2" />
</Style>
<Style x:Key="headerText">
<!-- I want this to be the same as propertyText +2 -->
<Setter Property="Control.FontSize" Value="FontSize="{Binding
RelativeSource={RelativeSource AncestorType={x:Type Window}},
Path=FontSize,
Converter={StaticResource MathConverter},
ConverterParameter=2}" />
</Style>
Here is the line I'm having trouble with. I want it to point to propertyText instead:
RelativeSource={RelativeSource AncestorType={x:Type Window}},
For completeness, here is the code for the converter :
public class MathConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
{
return (double)value + double.Parse( parameter.ToString() );
}
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
{
return null;
}
}
Based on Markus Hütter's reply. Here is the XAML for the solution:
<system:Double x:Key="baseFontSize">12</system:Double>
<my:MathConverter x:Key="MathConverter" />
<Style x:Key="propertyText">
<Setter Property="Control.Foreground" Value="Gray" />
<Setter Property="Control.FontSize" Value="{StaticResource ResourceKey=baseFontSize}" />
<Setter Property="Control.Padding" Value="10,2,2,2" />
</Style>
<Style x:Key="headerText">
<!-- I want this to be the same as propertyText +2 -->
<Setter Property="Control.FontSize"
Value="{Binding Source={StaticResource ResourceKey=baseFontSize},
Converter={StaticResource MathConverter},
ConverterParameter=2}" />
</Style>

easiest would be:
create a resource
<system:Double x:Key="propertyTextFontSize">12</system:Double>
and use a StaticReference in the setters both pointing to this resource but the second one with the binding and converter.

Related

WPF converter for labels' content

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}"/>

Is it possible to change how the OpacityMask property targets the control in a style

My idea is to use a WPF default button use an image as the opacity mask and have the foreground brush change its color. I want to still have the background however and this is where the issue is, because no matter how I design the style the background always disappears. Either this isn’t possible because the opacity Mask cannot change its target and just acts on the entire control or I am missing something.
Here is the style i have. I am trying to keep it fairly basic.
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Background" Value="{DynamicResource Mono_Darkest}"/>
<Setter Property="Foreground" Value="{DynamicResource Mono_Light}"/>
<Setter Property="BorderBrush" Value="{StaticResource Button_BorderBrush_Normal}"/>
<Setter Property="BorderThickness" Value="{StaticResource Button_BorderThickness_Normal}"/>
<Setter Property="FontFamily" Value="{StaticResource Button_FontFamily_Normal}"/>
<Setter Property="FontWeight" Value="{StaticResource Button_FontWeight_Normal}"/>
<Setter Property="FontSize" Value="{StaticResource Button_FontSize_Normal}"/>
<Setter Property="HorizontalContentAlignment" Value="{StaticResource Button_HorizontalContentAlignment_Normal}"/>
<Setter Property="VerticalContentAlignment" Value="{StaticResource Button_VerticalContentAlignment_Normal}"/>
<Setter Property="Padding" Value="{StaticResource Button_Padding_Normal}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="Border_Part"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Label OpacityMask="{TemplateBinding OpacityMask}"
Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}" FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}" FontWeight="{TemplateBinding FontWeight}">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=OpacityMask, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}, Converter={StaticResource isNull}}"
Value="false">
<Setter Property="Background"
Value="{Binding Path=Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
What I have is simply nesting a label inside the border. What I want is the template (Button) control's opacity property to only affect the label's opacity property. But that doesn’t seem to be happening. Instead it seems to affect everything like the default button style would.
I have tried Setting OverridesDefaultStyle to True but that has not changed anything.
I know I can already do this other ways. But my goal is to just make a quick button that can use either text or images as a foreground. With the image color coming from the foreground brush. If I cannot get an answer here I am most likely just going to use an Attached Property and bind the labels opacity mask to that instead, but again I would rather just do it entirely in this style.
Any advice is welcome. Thank you.
For Opacity I use a Converter. With this I can bind a SolidColorBrush to Foreground or Background. The Converter gets a Parameter with the target Opacity and returns the opac color.
Converter:
public class ColorToOpacityConverter : IValueConverter
{
// Takes a SolidColorBrush as value and double as parameter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is SolidColorBrush)) return value;
SolidColorBrush scb = new SolidColorBrush(((SolidColorBrush) value).Color);
scb.Opacity = parameter as double? ?? 0.5;
return scb;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
xaml:
<Style.Resources>
<namespace:ColorToOpacityConverter x:Key="ColorToOpacityConverter" />
<system:Double x:Key="TargetOpacity">0.3</system:Double>
</Style.Resources>
<Label Foreground="{Binding Path=Foreground, StaticResource={StaticResource TemplatedParent}, Converter={StaticResource ColorToOpacityConverter}, ConverterParameter={StaticResource TargetOpacity}"
A second approach for bound opacities (not as StaticResource) I use a MultiValueConverter
For bound opacities:
public class ColorToDynOpacityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Any(v => v == DependencyProperty.UnsetValue)) return new SolidColorBrush();
if (!(values[0] is SolidColorBrush) || !(values[1] is double)) {
//Fake for DesignMode-Display
if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) return new SolidColorBrush(Colors.Aqua);
throw new ArgumentException("expect values[0] as SolidColorBrush and values[1] as double.");
}
SolidColorBrush scb = new SolidColorBrush(((SolidColorBrush) values[0]).Color);
scb.Opacity = (double) values[1];
return scb;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
xaml:
<namespace:ColorToDynOpacityConverter x:Key="ColorToDynOpacityConverter" />
<Label>
<Label.Foreground>
<MultiBinding Converter="{StaticResource ColorToDynOpacityConverter}">
<Binding Path="Foreground" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="Opacity" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</Label.Foreground>
</Label>
Hope that helps and working for you.

Access Button's Tag property from ControlTemplate in XAML

In my Window I have a series of six buttons that indicate the six possible states for one of the properties of my ViewModel. The one that's active needs to be highlighted. To do this, I've created the following ControlTemplate for the buttons:
<ControlTemplate x:Key="SnijRichtingTemplate" TargetType="Button">
<Border Name="toggleButton" BorderThickness="1" BorderBrush="{StaticResource KleurRadioCheckOuter}" Background="Transparent" Width="20" Height="20" Cursor="Hand">
<TextBlock Name="text" Foreground="{StaticResource KleurRadioCheckOuter}"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag}"
ToolTip="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag.ToolTip}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityToBooleanConverter}">
<Binding Path="SnijRichting" />
<Binding Path="Tag" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter TargetName="toggleButton" Property="BorderBrush" Value="{StaticResource KleurTekstDonker}" />
<Setter TargetName="text" Property="Foreground" Value="{StaticResource KleurTekstDonker}" />
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="toggleButton" Property="BorderBrush" Value="{StaticResource Kleur2}" />
<Setter TargetName="text" Property="Foreground" Value="{StaticResource Kleur2}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
The template is then used like so:
<Button Grid.Column="0" Template="{StaticResource SnijRichtingTemplate}"
HorizontalAlignment="Right" Click="SnijRichting_Click"
Tag="{StaticResource XLinks}" />
Where the tag is just an instance defined in the XAML:
<wg:SnijRichting x:Key="XLinks" SnijAs="X" Negatief="True" />
The MultibindingConverter is nothing fancy:
public class EqualityToBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Basically, each button has a Tag with the new value. In the click handler, the ViewModel's property is set to the button's Tag. The button state is updated by checking whether the button's Tag is equal to the ViewModel property.
The problem is that this doesn't work. When the EqualityToBooleanConverter is executed, the second value is null. By removing the Path="Tag" bit from the second binding I see that the TemplatedParent is a ContentPresenter rather than the Button I was expecting, which explains why Tag is null. Now of course I could write a ValueConverter to get the correct value using VisualTreeHelper.GetParent to get the ContentPresenter's parent (which returns the desired Button), but surely there must be a way to do this from XAML? The obvious Path="Parent.Tag" doesn't work, since the Parent of the ContentPresenter is apparently a Border.
Does anyone know how to access the button's Tag property from XAML?
Found the problem. Turns out you need {RelativeSource Mode=Self}, not {RelativeSource TemplatedParent}.

stackpanel visibility based on label content not working

I have a stack panel that I want to make visible based on a label's content. Just not sure why it isnt working for me. What's highlighted in bold is what I want to hide. Any suggestion?
<StackPanel Orientation="Horizontal">
<Label Nane="lblCarrier" Content="{Binding Path=Carrier}" />
**<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Content, ElementName=lblCarrier}" Value="">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Label x:Name="lblCarrierGrade" Content="Grade Carrier:" />
<TextBox x:Name="txtCarrierGrade1" />
<TextBox x:Name="txtCarrierGrade2" />
</StackPanel>**
It could be that the Content is null rather than String.Empty.
You could try using TargetNullValue
<DataTrigger Binding="{Binding Content, ElementName=lblCarrier,TargetNullValue=''}" Value="">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
Why not using a converter? Add a class file to you project like this:
class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return string.IsNullOrEmpty(value as string) ? Visibility.Hidden : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In your Window definition add this:
xmlns:myNamespace="clr-namespace:[YourProjectName]"
Then somewhere in the resources add this
<myNamespace:VisibilityConverter x:Key="myConverter"/>
Now you can use it:
<Style TargetType="StackPanel">
<Setter Property="Visibility"
Value="{Binding Content, ElementName=lblCarrier,
Converter = {StaticResources myConverter}}"/>

Hide DataTrigger if RelativeSource doesn't exist

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.

Resources