Databinding to enumeration of a static class - wpf

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.

Related

WPF: unique style for each combo box item bound to dictionary

A ComboBox bound to the dictionary (of Enum, String). Selected value path is dictionary's Key.
Is it possible to set individual style for each combo box item in XAML?
In the following I use a custom enum called Cards, that has constants Skull, Hearts and others for demonstration purposes. You can simply use your enum type instead.
Item Container Style Using Data Triggers
You could create an items container style with triggers for each enum value.
<Style x:Key="EnumComboBoxItemStyle" TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Key}" Value="{x:Static local:Cards.Skull}">
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding="{Binding Key}" Value="{x:Static local:Cards.Hearts}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
<ComboBox ItemContainerStyle="{StaticResource EnumComboBoxItemStyle}" ...>
Item Container Style Selector
Another option is to create a style selector if you have multiple distinct styles like this:
<Style x:Key="SkullComboBoxItemStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Foreground" Value="Green"/>
</Style>
<Style x:Key="HeartsComboBoxItemStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Foreground" Value="Red"/>
</Style>
<!-- ...other styles. -->
The style selector determines the style based on the enum value.
public class CardsKeyStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
if (container is FrameworkElement element && item is KeyValuePair<Cards, string> keyValuePair)
{
switch (keyValuePair.Key)
{
case Cards.Skull:
return element.FindResource("SkullComboBoxItemStyle") as Style;
case Cards.Hearts:
return element.FindResource("HeartsComboBoxItemStyle") as Style;
// ...other cases.
}
}
return null;
}
}
You can assign the style selector to your combo box and it will choose the right style.
<ComboBox ...>
<ComboBox.ItemContainerStyleSelector>
<local:CardsKeyStyleSelector/>
</ComboBox.ItemContainerStyleSelector>
</ComboBox>
My solution is further. First, define enumerable type and dictionary, associated with it:
Public Enum MyEnum As Integer
OneValue = 0
OtherValue = 1
End Enum
Public ReadOnly Property MyDict As Dictionary(Of MyEnum, String)
Get
Return New Dictionary(Of MyEnum, String) From {
{MyEnum.OneValue, "First value text"},
{MyEnum.OtherValue, "Other value text"}
}
End Get
End Property
Next, in XAML use it in this manner:
<Style TargetType="Ellipse" x:Key="ellipseStyle">
<Setter Property="Height" Value="10" />
<Setter Property="Width" Value="10" />
<Style.Triggers>
<DataTrigger Binding="{Binding Key}" Value="0"><!-- "0" - one of the dictionary key -->
<Setter Property="Fill" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Key}" Value="1"><!-- "1" - one of the dictionary key -->
<Setter Property="Fill" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type ComboBox}" x:Key="cmb_osn_rez">
<Setter Property="ItemsSource" Value="{Binding MyDict}" />
<Setter Property="SelectedValuePath" Value="Key" />
<Setter Property="ItemsControl.ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Style="{StaticResource ellipseStyle}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>

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>

How to add new setter programatically in existing ItemContainerStyle in WPF?

I have item container style for list view like below :
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Padding" Value="0"></Setter>
<Setter Property="Margin" Value="0,0,0,-1"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=EventType}" Value="2">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
Now I need to add new setter for context menu programatically based on item data. How can I do it ?
Please guide me.....
Thanks
I have to display Images dynamically and show/hide depends on state of checkboxes using Style and DataTrigger.
<Image Source="/WpfApplication;component/Imgs/img1_1.png">
<Image.Style>
<Style>
<Setter Property="Image.Visibility" Value="Collapsed"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=isDisplayingImgSet1, Path=IsChecked}" Value="True">
<Setter Property="Image.Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
After spec modification i have to create checkboxes dynamically, then to set Style.
At beginning, I have the same error message.
Image img = new Image();
//..
img.Style.Setters.Add(setter);
img.Style.Triggers.Add(trigger);
//..After a 'SetterBaseCollection' is in use (sealed), it cannot be modified.
The solution is to create a style object and Affect it to Image.Style
//Visibility
DataTrigger trigger = new DataTrigger();
trigger.Binding = new Binding
{
ElementName = "isDisplayingImgSet"+NumSet,
Path = new PropertyPath(CheckBox.IsCheckedProperty)
};
trigger.Value = "True";
trigger.Setters.Add(new Setter(ContentControl.VisibilityProperty, Visibility.Visible));
Setter setter = new Setter(ContentControl.VisibilityProperty, Visibility.Collapsed);
Style style = new Style(typeof(Image));
style.Setters.Add(setter);
style.Triggers.Add(trigger);
img.Style = style;
Hope that helping you
PS : It's my first post
I have implemented logic to add setter dynamically in existing style like below :
listview1.ItemContainerStyle.Setters.Add(new Setter(Control.ContextMenuProperty,GetItemContextMenu(txtName.Text)));
But it gives me following error :
"After a 'SetterBaseCollection' is in use (sealed), it cannot be modified."
I think it is not possible to add new setter in sealed style. So I have got another temporary solution like assign context menu to whole listview rather than it's item on preview mouse right click event. So context menu will be same for all items then managed in coding based on data of selected item.
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Padding" Value="0"></Setter>
<Setter Property="Margin" Value="0,0,0,-1"></Setter>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=EventType}" Value="2">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White"></Setter>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
If you create a new Style() and "re-add" the the setters, you can get around this issue if you are using Telerik.
private static void AddColumnSetter(GridViewColumn gridColumn, SetterBase setter)
{
var setters = new SetterBaseCollection();
if (gridColumn.CellStyle != null)
{
foreach (SetterBase setterBase in gridColumn.CellStyle.Setters)
{
setters.Add(setterBase);
}
}
setters.Add(setter);
gridColumn.CellStyle = new Style();
foreach (var setr in setters)
{
gridColumn.CellStyle.Setters.Add(setr);
}
}

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.

Resources