wpf - datatriggers repeated use - wpf

I have the current DataTrigger:
<DataTrigger Binding="{Binding HeaderType}" Value="1">
<Setter Property="BorderThickness" Value="5"/></DataTrigger>
I want to do the same with values 2-100
Do I have to copy the Data Trigger 99 times or maybe there's a better way ?

Add a property to your view model:
public bool HasImportantHeader // or something...
{
get { return HeaderType >=1 && HeaderType <= 100; }
}
Use that property in the data trigger:
<DataTrigger Binding="{Binding HasImportantHeader}" Value="True">
<Setter Property="BorderThickness" Value="5"/>
</DataTrigger>
Generally, I like to keep my XAML as simple as possible, put all the logic in the view model, and avoid using Converters unless they are absolutely necessary.
Let's say you add another view, where you want to use bold text to indicate the header type is between 1 and 100. Just re-use the HasImportantHeader property, something like:
<DataTrigger Binding="{Binding HasImportantHeader}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
Later, you may decide that all header types up to 200 should have thick border and bold text. It'll be a simple matter of changing the implementation of the HasImportantHeader property.

I've used this in similar situations
<DataTrigger Binding="{Binding HeaderType,
Converter={StaticResource RangeConverter},
ConverterParameter=1-100}"
Value="True">
<Setter Property="BorderThickness" Value="5"/>
</DataTrigger>
And in the converter we return true or false depending on the ranges
public class RangeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string[] ranges = parameter.ToString().Split(new char[]{'-'});
int headerType = (int)value;
if (headerType >= System.Convert.ToInt32(ranges[0]) &&
headerType <= System.Convert.ToInt32(ranges[1]))
{
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}

You need to use a converter for that purpose.You can add a converter on your DataTrigger.
The Converter will allow you to pass in the value, and return true or false.
<DataTrigger
Binding="{Binding HeaderType, Converter={StaticResource RengeConvertor}}"
Value="true"
>
<Setter Property="BorderThickness" Value="5" />
</DataTrigger>
and your converter should look something like
public class RengeConvertor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int data = (int)value;
if (data >= 2 && data <= 100)
return true;
else
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You may also find this interesting http://zamjad.wordpress.com/2010/07/29/range-converter/

Related

How Do I Bind to Key in Dictionary only if it exists

I have a case where I dont know if a key exists in a dictionary, but if it does. I want to bind to it.
In this case, its a colour, so I use a converter to convert the colour and bind. This works great but only if the key exists!
I cant think of a way to setup a trigger, which checks if the key exists before I execute the binding?
Note in the code below, fieldColour may or may not exist. I would like to setup a data trigger which I can do, but I dont know how this is going to be possible.
<ToggleButton.Style>
<Style>
<Setter Property="ToggleButton.Background" Value="{Binding Path=Schema.KeyValues[fieldColour], Converter={converters:Converter_StringToColour}}" />
</Style>
</ToggleButton.Style>
I eventually worked out the best way. You can pass it to a converter and let that decide if the parameter is null:
C#
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null) return null;
if (value == null) return null;
string key = (parameter as string).ToLower();
Dictionary<string, string> dict = value as Dictionary<string, string>;
if (!dict.ContainsKey(key)) return null;
string colour = dict[key];
var converter = TypeDescriptor.GetConverter(typeof(Color));
if (converter.IsValid(colour))
{
//Easter Egg.
if (colour == "Magenta")
{
ErrorConsoleViewModel.Instance.LogWarning("Magenta? Really? Are you trying to blind me!");
}
Color newCol = (Color)converter.ConvertFromString(colour);
return new SolidColorBrush(newCol);
}
ErrorConsoleViewModel.Instance.LogWarning("Colour " + colour + " not found. See C# Colour Table: http://www.dotnetperls.com/color-table");
return null;
}
XAML
<Style>
<Setter Property="ToggleButton.Background" Value="{Binding Path=Schema.KeyValues, Converter={converters:Converter_DictionaryStringToColour}, ConverterParameter='fieldColour'}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Schema.KeyValues, Converter={converters:Converter_DictionaryStringToColour}, ConverterParameter='fieldColour'}" Value="{x:Null}">
<Setter Property="ToggleButton.Background" Value="#A9C7F0" />
</DataTrigger>
</Style.Triggers>
</Style>

Dynamic Style Binding with IValueConverter

I am trying to set a style, which is defined in the App.xaml, dynamically when loading a user control and it's just not applying the style for some reason (i.e. there is no error occurring, it's just not applying the style).
I'm sure it's because I've defined the binding wrong, but I'm unable to figure out what I need to do differently to get it to work.
App.xaml Style
The style I'm after is the RunningTitleBlock and it's comprised of a couple other styles that I've included in the below code sample.
<Style TargetType="Label">
<Setter Property="Margin" Value="4"/>
</Style>
<Style TargetType="Label"
BasedOn="{StaticResource {x:Type Label}}"
x:Key="HeaderBlock">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<Style TargetType="Label"
BasedOn="{StaticResource ResourceKey=HeaderBlock}"
x:Key="TitleBlock">
<Setter Property="Foreground" Value="Black"/>
</Style>
<Style TargetType="Label"
BasedOn="{StaticResource ResourceKey=TitleBlock}"
x:Key="RunningTitleBlock">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0.0, 0.5"
EndPoint="1.0, 0.5">
<GradientStop Color="White" Offset="0.0"/>
<GradientStop Color="Green" Offset="1.0"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
Binding on the user control
I'm trying to get the Binding to bind to a value returned from a value converter.
Style="{DynamicResource ResourceKey={Binding Path=MonitoringType, Converter={StaticResource TSConverter}}}"
Code
MonitoringTypes Enum
public enum MonitoringTypes
{
Running,
Failed,
Paused,
Favorites,
}
User Control
Here what I'm trying to do is concatenate the string value of the MonitoringTypes enum value that's passed in with some well known text to build a style name that exists in the App.xaml. The value converter is being called and returning the correct value, but for some reason the style isn't applying.
/// <summary>
/// Interaction logic for MonitorWorkflow.xaml
/// </summary>
public partial class MonitorWorkflow : UserControl
{
public MonitorWorkflow(MonitoringTypes monitoringType)
{
InitializeComponent();
this.DataContext = new MonitorWorkflowViewModel { MonitoringType = monitoringType };
}
}
public class MonitorWorkflowViewModel
{
public MonitoringTypes MonitoringType { get; set; }
}
public class TitleStyleValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var type = (MonitoringTypes)value;
return string.Format("{0}TitleBlock", Enum.GetName(typeof(MonitoringTypes), type));
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Enum.Parse(typeof(MonitoringTypes), value.ToString().Substring(0, value.ToString().IndexOf("TitleBlock")));
}
}
My suggestion would be to skip the DynamicResource statement and using the Converter provide the Style directly.
Style="{Binding Path=MonitoringType, Converter={StaticResource TSConverter}}"
In TSConverter, you can return a Style rather than a string. Kind of like this:
public class TitleStyleValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
var type = (MonitoringTypes)value;
var styleToReturn = FindResource(
string.Format("{0}TitleBlock",
Enum.GetName(typeof(MonitoringTypes), type)));
if (styleToReturn != null)
return (Style)styleToReturn;
else
return null;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
// not sure if you need this anymore...
return Enum.Parse(typeof(MonitoringTypes), value.ToString().Substring(0,
value.ToString().IndexOf("TitleBlock")));
}
}
This is what I did but with the following code instead. I actually just answered my own question while you answered it as well. Good timing!
public class TitleStyleValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var type = (MonitoringTypes)value;
return App.Current.Resources[string.Format("{0}TitleBlock", Enum.GetName(typeof(MonitoringTypes), type))];
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Enum.Parse(typeof(MonitoringTypes), value.ToString().Substring(0, value.ToString().IndexOf("TitleBlock")));
}
}
public static Style DayModeButton = null;
void loadStyle()
{
Uri uri1 = new Uri("/Resources/ButtonStyle.xaml", UriKind.Relative);
ResourceDictionary resDict1 = Application.LoadComponent(uri1) as ResourceDictionary;
foreach (object obj in resDict1.Values) //Set explicit reference
if (obj is Style) DayModeButton = (Style)obj;
}
[ValueConversion(typeof(object), typeof(Style))]
public class GetStyleConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return DayModeButton ;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return 0;
}
}

Colouring a hierarchical XamDataGrid

I'm using a XamDataGrid (Infragistics-control) to display some hierarchical data. The objects that I can have up to 10 levels and I need to be able to give each level a specific background-color. I use the AssigningFieldLayoutToItem-event to get the "level" of the item and it would be best to assign the background/style here as well, I suppose.
I have tried specifying a DataRecordCellArea-style and even a CellValuePresenter-style but I can't get any of these to work with the FieldLayouts.
Another solution is to write a FieldLayout for each level, but this would create a lot of unnecessary XAML-code.
Any suggestions as to what I should do?
If you have a different FieldLayout for each level, you could use a single style targeting the DataRecordPresenter with a converter to set the background.
XAML:
<local:BackgroundConverter x:Key="BackgroundConverter"/>
<Style TargetType="{x:Type igDP:DataRecordPresenter}">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=FieldLayout.Key, Converter={StaticResource BackgroundConverter}}"/>
</Style>
Converter:
public class BackgroundConverter:IValueConverter
{
public BackgroundConverter()
{
this.Brushes = new Dictionary<string, Brush>();
}
public Dictionary<string, Brush> Brushes {get;set;}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string)
{
string key = value.ToString();
if (this.Brushes.ContainsKey(key))
return this.Brushes[value.ToString()];
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The following will set the colors to use for fields with Key1 and Key2:
BackgroundConverter backgroundConverter = this.Resources["BackgroundConverter"] as BackgroundConverter;
backgroundConverter.Brushes.Add("Key1", Brushes.Green);
backgroundConverter.Brushes.Add("Key2", Brushes.Yellow);
If you are reusing the same FieldLayout for multiple fields, then you could use the InitializeRecord event and change the style to bind to the Tag of the DataRecord like this:
XAML:
<Style TargetType="{x:Type igDP:DataRecordPresenter}">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Record.Tag}"/>
</Style>
C#:
void XamDataGrid1_InitializeRecord(object sender, Infragistics.Windows.DataPresenter.Events.InitializeRecordEventArgs e)
{
if (!e.ReInitialize)
{
// Set the tag to the desired brush.
e.Record.Tag = Brushes.Blue;
}
}
Note that I didn't add the conditional logic for determining the brush to use and that still needs to be done for different levels to have different backgrounds.

WPF Datagrid right align numeric columns

I'm trying to create a style for the DataGrid cells in my application. I thoight that maybe there is a way to right align the numeric cells in DataGrid using a DataTrigger. Is this even possible?
My style for my DataGrid is this:
<Style TargetType="{x:Type DataGrid}">
<Setter Property="AlternatingRowBackground">
<Setter.Value>
<SolidColorBrush Color="#CCCCCC" />
</Setter.Value>
</Setter>
<Setter Property="CanUserAddRows" Value="False" />
<Setter Property="CanUserDeleteRows" Value="False" />
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0" Color="#CCCCCC" />
<GradientStop Offset="1" Color="#FFFFFF" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="RowHeaderWidth" Value="0" />
<Setter Property="CellStyle">
<Setter.Value>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#7777FF" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>
I'm thinking of maybe adding some kind of trigger on the CellStyle to detect if the content is numeric (int, double, decimal,...) and style the cell accordingly. Is this possible?
Update
Thinking about this I've tried several things, but it didn't work. I tried using a DataTrigger defined as:
<DataTrigger Binding="{Binding Converter={StaticResource IsNumericConverter}}" Value="True">
<Setter Property="HorizontalContentAlignment" Value="Right" />
</DataTrigger>
Where IsNumericConverter is:
public class IsNumericConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is int || (value is decimal || value is double);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
But when I set a breakpoint on the converter I get that the value is of the type of the whole row, not each individual cell...
One way to do this is to set up the triggers in code-behind. The following method applies a trigger to a DataGridColumn. This trigger uses your IsNumericConverter to determine whether to do the right-alignment:
public static void AddTriggerToColumnStyle(DataGridColumn column)
{
var boundColumn = column as DataGridBoundColumn;
if (boundColumn != null && boundColumn.Binding is Binding)
{
string bindingPropertyPath = (boundColumn.Binding as Binding).Path.Path;
column.CellStyle = new Style()
{
BasedOn = column.CellStyle,
TargetType = typeof(DataGridCell),
Triggers =
{
new DataTrigger
{
Binding = new Binding(bindingPropertyPath) { Converter = new IsNumericConverter() },
Value = true,
Setters =
{
new Setter
{
Property = TextBlock.TextAlignmentProperty,
Value = TextAlignment.Right
}
}
}
}
};
}
}
This method should be safe to call after InitializeComponent() in the code-behind's constructor.
This approach will work if the column has a mixture of numeric and non-numeric data. If the columns contain data of one type only, then this approach will still work, but the triggers created will either never fire or stay fired permanently.
Of course, the above method doesn't have to be repeated in the code-behind for each control that uses DataGrids. You could move it to a static utility class and have all your code-behind classes call this method. You could even add a static utility method that loops through the columns in a DataGrid you pass it and calls the above method for each column.
UPDATE: I modified the above method to allow it deduce the binding property-path. That makes it easier to use. I've also made the method public and static so that it's clear it can be moved to a static utility class.
I did think of another approach, that would run through all of the columns in a DataGrid, inspect the bindings, deduce the type of property bound to and apply the right-alignment to the column. This would avoid creating triggers that never fire or stay permanently fired, and would perhaps fit the case where each column contains data of one type only a bit better. However, this approach would still involve code-behind, it would need to be passed the type that each row would be bound to, and would get complicated if the binding property-paths involved anything more than a single property name.
I'm afraid I can't see a solution to this problem that uses only Styles and avoids code-behind.
I know this is three years old, but I was able to get this done without adding any code to the codebehind and I figured I would share how. using this article as a model: link
It uses multibinding as a way of around your original problem of passing a row to your converter.
add this to your datagrid:
CellStyle="{StaticResource CellRightAlignStyle }"
then add this style to your xaml, you must add it as a resource:
<Window.Resources>
<Style x:Key="CellRightAlignStyle" TargetType="{x:Type DataGridCell}">
<Setter Property="HorizontalAlignment">
<Setter.Value>
<MultiBinding
Converter="{converters:IsNumericConverter}" >
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding Path="Row" Mode="OneWay"/>
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Then the converter:
public object Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (values[1] is DataRow)
{
//change the text alignment left or right.
var cell = (DataGridCell)values[0];
var row = (DataRow)values[1];
var columnName = cell.Column.SortMemberPath;
if (row[columnName] is int || (row[columnName] is decimal || row[columnName] is double))
return System.Windows.HorizontalAlignment.Right;
}
return System.Windows.HorizontalAlignment.Left;
}
public override object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return null;
}
that is all now your cells that contain numeric data should be right aligned!
I use converter for creating a fixed-length string and monospaced font for view.
Converter:
public class AlignRightConverter : IValueConverter // samples: _10 F2_14 C2_16
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
if (parameter == null) throw new Exception("Format is missing");
var parameterStr = "" + parameter;
if (!parameterStr.Contains('_')) throw new Exception("Unknown format");
var parameterParts = parameterStr.Split('_');
var format = parameterParts[0];
var lengthStr = parameterParts[1];
var length = int.TryParse(lengthStr, out int l) ? l : 12;
if (value == null) return new String(' ', length);
var type = value.GetType();
String targetStr;
if (type == typeof(Double) && format.Length > 0) targetStr = ((Double)value).ToString(format, CultureInfo.GetCultureInfo("ru-ru"));
else
if (type == typeof(Single) && format.Length > 0) targetStr = ((Single)value).ToString(format, CultureInfo.GetCultureInfo("ru-ru"));
else
if (type == typeof(Int32) && format.Length > 0) targetStr = ((Int32)value).ToString(format, CultureInfo.GetCultureInfo("ru-ru"));
else
targetStr = value.ToString();
if (targetStr.Length >= length) return targetStr;
else
return new String(' ', length - targetStr.Length) + targetStr;
}
catch (Exception exception)
{
exception.Log($"Failed convert to string value {value} with parameters {parameter}");
return "" + value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
View:
<DataGridTextColumn Header="Цена покупки" Binding="{Binding Data.CostBuy.Value, Converter={StaticResource AlignRightConverter}, ConverterParameter=C2_16}" FontFamily="Courier New" />
Result:

How to trigger on bound item in WPF?

I want to activate a trigger if the bound items property ID is equal to a property in my custom control.
But i can't use bindings in my triggers!
How would i do this?
This should work:
<DataTrigger
Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{x:Static local:EqualityConverter.Instance}">
<Binding
Path="BoundProperty" />
<Binding
ElementName="MockCustomControl"
Path="Text" />
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter
TargetName=" ... "
Property=" ... "
Value=" ... " />
...
...
</DataTrigger.Setters>
</DataTrigger>
And the converter may be something similar to this:
public class EqualityConverter : IMultiValueConverter
{
public static readonly EqualityConverter Instance = new EqualityConverter();
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values == null || values.Length == 0) return false;
for (int i = 1; i < values.Length; i++)
if (!values[i].Equals(values[0])) return false;
return true;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Note that I've used a generic convrter that can campare n values, but you can use a simple one as per your needs.
Hope this helps.
DataTrigger's value property doesn't take bindings?
<DataTrigger Binding="{Binding BoundPropertyId}" Value="{Binding ElementName=Mine, Path=Property}" />
If that doesn't work, you might need to hook onto an event and process it yourself in the code-behind.

Resources