Dynamic Style Binding with IValueConverter - wpf

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;
}
}

Related

How To Setup A Converter To Return Properties From Template

I'm making a style for a custom control that will can be one of two colors.
My properties of my control are: { SolidColorBrush color1, SolidColorBrush color2, bool usingColor1}.
I'm trying to make a converter that will bind to usingColor1 and will return either color1 or color2. I would like to define my converter like this:
<Style.Resources>
<Converters:ValueParameterComparisonConverter x:Key="Color1WhenTrue" ValueWhenEqual="{DataTemplateKey Color1}" ValueWhenNotEqual="{DataTemplateKey Color2}"/>
</Style.Resources>
And use my converter like this:
<Border Background="{TemplateBinding UsingColor1, Converter={StaticResource Color1WhenTrue}}" />
I have implemented this in my code and it does not work. I do not understand what DataTemplateKey does and do not think it is the correct word for this scenario.
How can I setup my converter to return properties from my template?
Edit:
Here is the interesting part of the converter code:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isEqual;
if (value == null)
{
isEqual = null == parameter;
}
else
{
isEqual = value.Equals(parameter);
}
return isEqual ? this.ValueWhenEqual : this.ValueWhenNotEqual;
}
You can get rid of the valueWhenEqual parameters;
<Style.Resources>
<Converters:ValueParameterComparisonConverter x:Key="Color1WhenTrue"/>
</Style.Resources>
Just use a binding rather than a templatebinding;
<Border Background="{Binding UsingColor1, Converter={StaticResource Color1WhenTrue}}" />
Then change your converter to return the color you want when true and another color when false;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var usingColor1 = (bool)value;
return usingColor1 ? new SolidColorBrush(Color.FromRgb(179, 255, 179)) : new SolidColorBrush(Color.FromRgb(255, 100, 0));
}
Obviously you'll need to change the colors used in the example I've given.

Cannot find converter resource

I have a WPF Solution with 2 projects, the first one (ResourcesLibrary) contains common styles and common resources. The other is the WPF application.
I created a style for DataGridRows in the ResourcesLibrary generic.xaml file:
<Style x:Key="DGRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ValidationErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Image Source="/Resources/stop.png"
ToolTip = "{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}},
Path=(Validation.Errors),
Converter={StaticResource errorConverter]}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
I added the .cs converter files to the ResourcesLibrary project:
namespace ResourceLibrary
{
public class ErrorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var errors = value as ReadOnlyObservableCollection<ValidationError>;
if (errors == null)
return "";
return errors.Count > 0 ? errors[0].ErrorContent : "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
And added the reference and the static resource:
xmlns:my="clr-namespace:ResourceLibrary"
<!-- CONVERTERS -->
<my:ErrorConverter x:Key="errorConverter" />
But at runtime when, in the main project, I use the DataGridRow style defined in the ResourcesLibrary I get this error:
{"Cannot find resource named 'errorConverter]'. Resource names are case sensitive."}
Do I need to have another project inside my solution for the converters I will use?
Thanks to #Clemens I removed the extra char ']' in the expression "={StaticResource errorConverter]}" and it all works fine.

Convert and Convertback between double and "+" & " -" String

I would like to create WPF converter that converts double number to "+" or "-" String based on the number Sign(positive or negative)
but I can't handle the converted back method because I don't have the number anymore.
The "+" and "-" signs bounded to a combobox at the xaml side
any ideas???
public class AmountToDebitCreditConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((value == null) || (System.Convert.ToDecimal(value) == 0))
return string.Empty;
return System.Convert.ToDecimal(value) > 0 ? "+" : "-";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
KeyValue kv = value as KeyValue;
if ((String)value == "+")
return 1;
else
return -1;
}
}
Here's my Xaml code
<igDP:UnboundField Name="ActualAdjustmentAmount" Label="PlusMinusKey" Converter={StaticResource signConverter} >
<igDP:Field.Settings>
<igDP:FieldSettings EditorType="{x:Type igEditors:XamComboEditor}" LabelWidth="40" CellWidth="40">
<igDP:FieldSettings.EditorStyle>
<Style TargetType="{x:Type igEditors:XamComboEditor}">
<Setter Property="ItemsSource" Value="{Binding Path=Flags, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
<Setter Property="FlowDirection" Value="LeftToRight"/>
<Setter Property="SelectedItem" Value="{Binding Path=ActualAdjustmentAmount }" />
</Style>
</igDP:FieldSettings.EditorStyle>
</igDP:FieldSettings>
</igDP:Field.Settings>
</igDP:UnboundField>
If the idea is not just to be able to use (and exploit) Converters, then I would rather suggest you to have a specific Sign property in your ViewModel and bind your view showing sign with that property.
It is probably best to do this sort of conversion in a view-model whenever possible.
Nonetheless, you've pinpointed the problem -- you no longer have the number after your conversion. The fact is that you don't need to return a string from your converter -- you can return any object that will return the desired string from its ToString() override, and that object can contain any other data and behaviour that you want.
Here is a converter that should accomplish what you're after, though I haven't actually tested this:
public class SignedDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new DisplaySignedDouble(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var d = value as DisplaySignedDouble;
return (d == null || !d.Value.HasValue)
? 0d
: d.Value.Value;
}
private class DisplaySignedDouble
{
public DisplaySignedDouble(object value)
{
Value = value is double ? (double) value : (double?) null;
}
public double? Value { get; private set; }
public override string ToString()
{
if (!Value.HasValue || Value.Value == 0d)
{
return string.Empty;
}
return Value.Value > 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.

Changing the appearance of collection of items according to a single property in XAML

I have 4 buttons in grid which datacontext is set to an object which has property that indicates what button should be enabled (it's enumerable).
Currenty I have done this in code-behind so that when that specific property changes, it disables all but one depending on the value. It works, but I really don't like to put stuff like this to code-behind. There must be a way to do this in xaml?
I could make own style for all four buttons and then do this with data triggers, but I would prefer more generic approach: use same style for all buttons that somehow applies differently depending on, for example, a button name and value of the property.
Thanks in advance.
You could use a MultiBinding to bind the IsEnabled property to a combination of the control's name and the property from your DataContext, and create a Style to apply it to all buttons in the Grid:
<Grid.Resources>
<local:EqualsConverter x:Key="EqualsConverter"/>
<Style TargetType="Button">
<Setter Property="IsEnabled">
<Setter.Value>
<MultiBinding Converter="{StaticResource EqualsConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Name"/>
<Binding Path="EnabledButtonName"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
And in code:
public class EqualsConverter
: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values.Length == 2 && object.Equals(values[0], values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
You can create an association between the enum you've created and the buttons by having integer references to your enum values and giving those enum values as ConverterParameters for corresponding buttons.
For Eg:
The enum:
public enum myOptions
{
value1 = 1,
value2 = 2,
value3 = 3,
value4 = 4
}
The Binding:
<Button IsEnabled = {Binding Path=myProperty,
Converter = {StaticResource EnumToBoolConverter},
ConverterParameter = 1} />
<Button IsEnabled = {Binding Path=myProperty,
Converter = {StaticResource EnumToBoolConverter},
ConverterParameter = 2} />
And the Converter:
public class EnumToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value == (int)parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}

Resources