Style DataTrigger not updating Custom Control Dependency Property - wpf

I've defined a custom control that allows me to draw a series of arcs that result in a segmented circle. In this control I've defined a dependency property that allows me to set the number of segments to draw, i.e.,
public int SegmentCount
{
get => (int) GetValue( SegmentCountProperty );
set => SetValue( SegmentCountProperty, value );
}
public static readonly DependencyProperty SegmentCountProperty =
DependencyProperty.Register( nameof(SegmentCount), typeof( int ), typeof( MyCustomControl ), new PropertyMetadata( 1 ) );
I want to set this property in xaml according to a data trigger defined in a style as follows
<Style x:Key="MyCustomControlStyle" TargetType="local:MyCustomControl">
<Setter Property="SegmentCount" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="SegmentCount" Value="4"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsActive}" Value="False">
<Setter Property="SegmentCount" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
However, the "False" DataTrigger that attempts to set the SegmentCount property back to 0 doesn't appear to update the view. If I set the Stroke property as well (which i don't want to do) like this
<Style x:Key="MyCustomControlStyle" TargetType="local:MyCustomControl">
<Setter Property="SegmentCount" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="SegmentCount" Value="4"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsActive}" Value="False">
<Setter Property="SegmentCount" Value="0"/>
<Setter Property="Stroke" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
It seems to kick things along and I get the expected view, albeit with the wrong colour now. Is there a reason why my Dependency property is not updating the view in this scenario? Do I have to tell the framework it has changed similar to a RaiseNotifyProperty changed event?

There is no PropertyChangedCallback registered for the SegmentCount property.
It probably just does not trigger rendering. Try to set Framework​Property​Metadata​Options.AffectsRender:
public static readonly DependencyProperty SegmentCountProperty =
DependencyProperty.Register(
nameof(SegmentCount), typeof(int), typeof(MyCustomControl),
new FrameworkPropertyMetadata(
1, FrameworkPropertyMetadataOptions.AffectsRender));

Related

Databinding to enumeration of a static class

Chaps,
I have a datagrid and am colouring the rows as follows.
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}" >
<Style.Triggers>
<Trigger Property="DataGridCell.IsSelected" Value="True">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Background" Value="White" />
</Trigger>
<DataTrigger Binding="{Binding ErrorType}" Value="TerminalError">
<Setter Property ="Foreground" Value="Purple"/>
</DataTrigger>
<DataTrigger Binding="{Binding ErrorType}" Value="CriticalError">
<Setter Property ="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
Currently the colours are hard-coded and I need to change this. I have a singleton class that holds colours for different states and colours may be accessed in the following way:
Color returnedColour = ColourSchemes.Instance.GetColour (CriticalError)
So in the xaml, where I have Value="Red" etc, I wish to source the name from the globally accessible ColourSchemes object instead. Would very much appreciate any words of wisdom.
At first, make sure to use Brush instead of Color what you assign a value to a property like Foreground or Background.
Then you may add an indexer property to your ColourSchemes class, which takes the enum value as key:
public enum ErrorType
{
TerminalError, CriticalError
}
public class ColourSchemes
{
private readonly Dictionary<ErrorType, Brush> brushes =
new Dictionary<ErrorType, Brush>
{
{ ErrorType.TerminalError, Brushes.Orange },
{ ErrorType.CriticalError, Brushes.Red }
};
public Brush this[ErrorType value]
{
get { return brushes[value]; }
}
public static ColourSchemes Instance { get; } = new ColourSchemes();
}
Now you may bind a property like this:
Background="{Binding Source={x:Static local:ColourSchemes.Instance}, Path=[CriticalError]}">
Or in a Setter:
<Setter Property="Background"
Value="{Binding Source={x:Static local:ColourSchemes.Instance}, Path=[CriticalError]}"/>
You may however want to take a look at Dynamic Resources.

Grid Hide/Show animation

I've got some User Control, which is grid with transparent background, on top of that there's another grid that is suppose to change its width when triggers are fired (ultimately it'll be a in/out animation). Purpose was to simply use something different than Visibility that was instantly hiding entire control without any animations Here's the property it's based on:
public static readonly DependencyProperty IsOpenedProperty = DependencyProperty.Register ("IsOpened", typeof (Boolean), typeof (SidePanel), new PropertyMetadata (false));
public bool IsOpened {
get {
if (GetValue (IsOpenedProperty) != null)
return (bool)GetValue (IsOpenedProperty);
else
return false;
}
set {
SetValue (IsOpenedProperty, value);
Console.WriteLine(value);
}
}
And the Grid itself:
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Width" Value="50"/>
<Setter Property="Background" Value="Gray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsOpened}" Value="True">
<Setter Property="Width" Value="350"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsOpened}" Value="False">
<Setter Property="Width" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding ElementName=Side, Path=Width}"/>
</Grid>
Console gives me corect output but triggers don't fire at all.
Just checked one more thing and when I set trigger for x:Null value then it's fired all the time, how is it possible as Get shouldn't ever return null value here?
Alright I just gave up on dependency property here, created private variable to hold the value and added data context with INotifyPropertyChanged interface implementation and now it works.

How can I fall back to a high contrast color in WPF?

I have some XAML which sets the foreground color directly:
<Style x:Key="HomeHeaderText" TargetType="TextBlock">
<Setter Property="FontSize" Value="24" />
<Setter Property="FontFamily" Value="Segoe Light UI" />
<Setter Property="Foreground" Value="#FF606060" />
<Setter Property="Margin" Value="0,50,0,30" />
</Style>
I would like to detect in the style whether or not the system is in high contrast mode, and fall back to one of the system colors if so.
How can one do this using styles?
I tried setting this using a trigger, but this results in XamlParseException at runtime:
<Style x:Key="HomeHeaderText" TargetType="TextBlock">
<Setter Property="FontSize" Value="24" />
<Setter Property="FontFamily" Value="Segoe Light UI" />
<Setter Property="Foreground" Value="#FF606060" />
<Setter Property="Margin" Value="0,50,0,30" />
<Style.Triggers>
<DataTrigger Binding="{x:Static SystemParameters.HighContrast}" Value="True">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
</DataTrigger>
</Style.Triggers>
</Style>
The problem with your attempt is that DataTrigger.Binding wants a binding, but you gave it a direct value. You can solve this by setting the binding source:
{Binding Source={x:Static SystemParameters.HighContrast}}
However, this will not be dynamic -- the style would not update if someone toggles high-contrast while the application is running. Ideally it would be nice to have something like this:
{Binding Source={x:Static SystemParameters}, Path=HighContrast}
But unfortunately that isn't possible since it's a static property. So, binding to the HighContrastKey resource is the better option. Instead of using Tag, you could bind this to an attached property. Come to think of it, Microsoft probably should have implemented SystemParameters as attached properties in the first place. Try something like this:
public static class SystemParameterProperties {
public static readonly DependencyProperty HighContrastProperty =
DependencyProperty.RegisterAttached("HighContrast", typeof(bool), typeof(SystemParameterProperties),
new FrameworkPropertyMetadata() {Inherits = true});
public static bool GetHighContrast(DependencyObject obj) {
return (bool)obj.GetValue(HighContrastProperty);
}
public static void SetHighContrast(DependencyObject obj, bool value) {
obj.SetValue(HighContrastProperty, value);
}
}
I used Inherits = true on the property so that we can just set it on the outermost container and let it be accessible everywhere, i.e.:
<Window ...
xmlns:attachedProperties="..."
attachedProperties:SystemParameterProperties.HighContrast="{DynamicResource ResourceKey={x:Static Member=SystemParameters.HighContrastKey}}">
...
</Window>
Finally, your trigger would be:
<Trigger Property="attachedProperties:SystemParameterProperties.HighContrast" Value="True">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
</Trigger>
I'd have solved leveraging an helper:
public class HighContrastHelper
: DependencyObject
{
#region Singleton pattern
private HighContrastHelper()
{
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
}
private static HighContrastHelper _instance;
public static HighContrastHelper Instance
{
get
{
if (_instance == null)
_instance = new HighContrastHelper();
return _instance;
}
}
#endregion
void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(e.PropertyName);
if (e.PropertyName == "HighContrast")
{
HighContrastHelper.Instance.IsHighContrast = SystemParameters.HighContrast;
}
}
#region DP IsHighContrast
public static readonly DependencyProperty IsHighContrastProperty = DependencyProperty.Register(
"IsHighContrast",
typeof(bool),
typeof(HighContrastHelper),
new PropertyMetadata(
false
));
public bool IsHighContrast
{
get { return (bool)GetValue(IsHighContrastProperty); }
private set { SetValue(IsHighContrastProperty, value); }
}
#endregion
}
Afterward, the usage in your code is straightforward:
<Style x:Key="HomeHeaderText" TargetType="TextBlock">
<Setter Property="FontSize" Value="24" />
<Setter Property="FontFamily" Value="Segoe Light UI" />
<Setter Property="Foreground" Value="#FF606060" />
<Setter Property="Margin" Value="0,50,0,30" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsHighContrast, Source={x:Static local:HighContrastHelper.Instance}}" Value="True">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
</DataTrigger>
</Style.Triggers>
</Style>
Hope it helps.
You can bind it to Tag property of textblock and use that in DataTrigger like below:
<Style x:Key="MyTextBoxStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Tag" Value="{DynamicResource {x:Static SystemParameters.HighContrastKey}}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag , RelativeSource= {x:Static RelativeSource.Self}}" Value="True">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
</DataTrigger>
</Style.Triggers>
</Style>

Using custom dependency properties as DataTrigger in WPF

I have a custom dependency property that I would like to use as a data trigger. Here is the code behind:
public static readonly DependencyProperty BioinsulatorScannedProperty =
DependencyProperty.Register(
"BioinsulatorScanned",
typeof(bool),
typeof(DisposablesDisplay),
new FrameworkPropertyMetadata(false));
public bool BioinsulatorScanned
{
get
{
return (bool)GetValue(BioinsulatorScannedProperty);
}
set
{
SetValue(BioinsulatorScannedProperty, value);
}
}
I have created a style and control template. My goal is to change the color of some text when the dependency prop is set to true...
<Style x:Key="TreatEye" TargetType="Label">
<Setter Property="Foreground" Value="#d1d1d1" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Canvas>
<TextBlock x:Name="bioinsulatorText"
Canvas.Left="21" Canvas.Top="33"
Text="Bioinsulator" />
<TextBlock Canvas.Left="21" Canvas.Top="70"
Text="KXL Kit" />
</Canvas>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding BioinsulatorScanned}"
Value="True">
<Setter TargetName="bioinsulatorText"
Property="Foreground" Value="Black" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Despite successfully setting the dependency prop to true programmatically, This trigger condition never fires. This is a real pain to debug!
Thanks in advance.
In this case I am switching the visibility of a button using a datatrigger based on a dependency property FirstLevelProperty.
public static readonly DependencyProperty FirstLevelProperty = DependencyProperty.Register("FirstLevel", typeof(string), typeof(MyWindowClass));
public string FirstLevel
{
get
{
return this.GetValue(FirstLevelProperty).ToString();
}
set
{
this.SetValue(FirstLevelProperty, value);
}
}
You can reference the dependency property FirstLevel(Property) contained (in this case) in a window by using the RelativeSource binding. Also you should set the default setting in the style, that will be overridden by the datatrigger.
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=FirstLevel,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"
Value="SomeValue">
<Setter Property="Visibility"
Value="Hidden" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Visible" />
</Style>
</Button.Style>
It looks like your dependency property is defined inside a DisposableDisplay object that you created. In order for the binding specified to work, an instance of that DisposableDisplay object must be set as the DataContext of the control (label in this case) or any of its ancestors.

'View cannot be shared by more than one ListView' Wpf Error

What is wrong with this code ? It throws the Exception: "View cannot be shared by more than one ListView"
<ListView
ItemsSource="{Binding}"
SelectionMode="Extended">
<ListView.Style>
<Style TargetType="ListView">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyControl, Path=IsCompany}" Value="True">
<Setter Property="View" Value="{StaticResource GridViewCompanies}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=MyControl, Path=IsCompany}" Value="False">
<Setter Property="View" Value="{StaticResource GridViewPeople}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=MyControl, Path=IsCompany}" Value="{x:Null}">
<Setter Property="View" Value="{StaticResource GridViewBoth}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
public bool? IsCompany
{
get { return (bool?)GetValue(IsCompanyProperty); }
set { SetValue(IsCompanyProperty, value); }
}
public static readonly DependencyProperty IsCompanyProperty =
DependencyProperty.Register("IsCompany", typeof(bool?), typeof(MyControl), new UIPropertyMetadata(null));
EDITED:
I've tried to set the View in code behind and it works. What's the problem with XAML then?
if() ..
MyListView.View = Resources["GridViewCompanies"] as GridView;
The error is because the GridView is being applied to more than one ListView. Is your ListView Style being applied to more than one control? I took a look in Reflector, and it looks like this scenario should work for a single control.
What exactly are you trying to accomplish? I imagine you just want to show different columns. Maybe you can create an attached behavior to generate columns for the GridView depending on the value of the property you are binding to.

Resources