Dynamic Conditional Formatting in WPF
I am working on an engineering program that has all the calculations written in VB.net in a separate project and now we are putting it in a WPF interface.
I am having a problem with changing the formats of strings between units. For example: in Imperial units you have a value of 4,966 pounds and convert that to 22.1 kN. You can see that it is necessary to have a different format between the two as they are in different orders.
Currently, the program has conditional coloring (black for a regular number, red for an error, yellow for a warning), which are configured through the styles in the resource dictionary.
<Style x:Key="GlobalUserEditedTextBox" BasedOn="{StaticResource {x:Type TextBox}}" TargetType="TextBox">
<Setter Property="Foreground" Value="{DynamicResource EditableTextColor}"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style x:Key="GlobalErrorTextBox" BasedOn="{StaticResource {x:Type TextBox}}" TargetType="TextBox">
<Setter Property="Foreground" Value="{DynamicResource ErrorTextColor}"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
In the program, the style is selected using a converter and a MultiBinding. ValueShow.TensionStatusShow
is the enum value coming out of the VB computation code:
<TextBlock HorizontalAlignment="Stretch" TextAlignment="Center" Text="{Binding Path=ValueShow.TensionShow}">
<TextBlock.Style>
<MultiBinding Converter="{StaticResource styleConverter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding Path="ValueShow.TensionStatusShow"/>
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Style>
</TextBlock>
And then the MultiValueConverter:
public class StyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
Style _newStyle;
try
{
if (values[1] == null || values[1] == DependencyProperty.UnsetValue)
return null;
if ((String)values[1] == StatusColor.ErrorValue.ToString())
{
if (values[0].GetType() == typeof(TextBox))
_newStyle = (Style)targetElement.TryFindResource("GlobalErrorTextBox");
else if (values[0].GetType() == typeof(TextBlock))
_newStyle = (Style)targetElement.TryFindResource("GlobalErrorTextBlock");
else
_newStyle = null;
}
else if
{
if (values[0].GetType() == typeof(TextBox))
_newStyle = (Style)targetElement.TryFindResource("GlobalWarningTextBox");
else if (values[0].GetType() == typeof(TextBlock))
_newStyle = (Style)targetElement.TryFindResource("GlobalWarningTextBlock");
else
_newStyle = null;
}
return _newStyle;
}
catch (Exception)
{
if (values[0].GetType() == typeof(TextBox))
return (Style)targetElement.TryFindResource("GlobalUnEditableTextBox");
else if (values[0].GetType() == typeof(TextBlock))
return (Style)targetElement.TryFindResource("GlobalUnEditableTextBlock");
else
return null;
}
}
What I tried:
So the problem is that I want to keep the "formatting" of the string formatting from VB calculation methods as opposed to ValueShow.TensionStatusShow
. We currently have 2 resource dictionaries (Imperial and Metric) that contain strings for unit labels. I tried setting up various string formats there so that it updates when the program changes units.
Imperial resource:
<s:String x:Key="UnitsStringFormatlbfkN">F0</s:String>
<Style TargetType="TextBox" x:Key="GlobalErrorTextBoxlbkNFormatting" BasedOn="{StaticResource GlobalErrorTextBox}">
<Setter Property="Text" Value="{Binding Path=., Mode=TwoWay, StringFormat={StaticResource UnitsStringFormatlbfkN}}" />
</Style>
Metric resource
<s:String x:Key="UnitsStringFormatlbfkN">F1</s:String>
<Style TargetType="TextBox" x:Key="GlobalErrorTextBoxlbkNFormatting" BasedOn="{StaticResource GlobalErrorTextBox}">
<Setter Property="Text" Value="{Binding Path=., Mode=TwoWay, StringFormat={StaticResource UnitsStringFormatlbfkN}}" />
</Style>
Then I would pass lbkNFormatting
as the third parameter in multi-linked mode and add it to the call TryFindResource
. Apparently it didn't work, but it loaded the resource successfully but ignored the string format. I tested by adding background colors to the Metric resource, which loaded fine, but again, the string format was ignored.
I also tried to change the style in the MultiValueConverter by programming the addition of string formatting, but encountering a property IsSealed
that I cannot beat
source to share
Sorry for the quick and short, not complete and indirect answer, but I wanted to throw you an often forgotten solution. I sometimes use it when some binding or style gets too complex and starts to fail and it seems impossible to trace why, or when I see that I could benefit from additional decoupling.
You can rewrite almost all styles, triggers and complex bindings + MultiValueCoverters into the so-called "bound behavior".
See the article for a quick look. Notice the two paths: added properties and additional subitems.
Actually, I like to use the best of both at the same time. Since I just wanted to throw you a note, I have truncated this answer and moved the text in this article .
I know this doesn't answer your question as to why styling and binding don't work, but I still think you might find it useful. Your styles and bindings seem complex enough to be difficult to debug and I can't focus on it: | The problem is that bindings can be very easily broken (disabled, overridden) trying to put the value on the wrong area / level, and even setters from styles and triggers can separate them from targets. I feel like this is what's happening, but I won't have more time to help you trace it in the near future. So ... good luck and I hope someone can give you a better answer.
source to share