how to pass an integer as ConverterParameter? - wpf

I am trying to bind to an integer property:
<RadioButton Content="None"
IsChecked="{Binding MyProperty,
Converter={StaticResource IntToBoolConverter},
ConverterParameter=0}" />
and my converter is:
[ValueConversion(typeof(int), typeof(bool))]
public class IntToBoolConverter : IValueConverter
{
public object Convert(object value, Type t, object parameter, CultureInfo culture)
{
return value.Equals(parameter);
}
public object ConvertBack(object value, Type t, object parameter, CultureInfo culture)
{
return value.Equals(false) ? DependencyProperty.UnsetValue : parameter;
}
}
the problem is that when my converter is called the parameter is string. i need it to be an integer. of course i can parse the string, but do i have to?
thanks for any help
konstantin

Here ya go!
<RadioButton Content="None"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<RadioButton.IsChecked>
<Binding Path="MyProperty"
Converter="{StaticResource IntToBoolConverter}">
<Binding.ConverterParameter>
<sys:Int32>0</sys:Int32>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
The trick is to include the namespace for the basic system types and then to write at least the ConverterParameter binding in element form.

For completeness, one more possible solution (perhaps with less typing):
<Window
xmlns:sys="clr-namespace:System;assembly=mscorlib" ...>
<Window.Resources>
<sys:Int32 x:Key="IntZero">0</sys:Int32>
</Window.Resources>
<RadioButton Content="None"
IsChecked="{Binding MyProperty,
Converter={StaticResource IntToBoolConverter},
ConverterParameter={StaticResource IntZero}}" />
(Of course, Window can be replaced with UserControl, and IntZero may be defined closer to the place of actual usage.)

Not sure why WPF folks tend to be disinclined towards using MarkupExtension. It is the perfect solution for many problems including the issue mentioned here.
public sealed class Int32Extension : MarkupExtension
{
public Int32Extension(int value) { this.Value = value; }
public int Value { get; set; }
public override Object ProvideValue(IServiceProvider sp) { return Value; }
};
If this markup extension is defined in XAML namespace 'local:', then the original poster's example becomes:
<RadioButton Content="None"
IsChecked="{Binding MyProperty,
Converter={StaticResource IntToBoolConverter},
ConverterParameter={local:Int32 0}}" />
This works because the markup extension parser can see the strong type of the constructor argument and convert accordingly, whereas Binding's ConverterParameter argument is (less-informatively) Object-typed.

Don't use value.Equals. Use:
Convert.ToInt32(value) == Convert.ToInt32(parameter)

It would be nice to somehow express the type information for the ConverterValue in XAML, but I don't think it is possible as of now. So I guess you have to parse the Converter Object to your expected type by some custom logic. I don't see another way.

Related

Access logical tree from inside a converter

I need to access the Logical Tree inside a converter. Actually this is inside a UserControl, which is (I think) only relevant insofar as this UserControl can live multiple times in my application.
I found a solution, but a rather crude one and my question is: Is there a better, more elegant solution.
Here is what I did. I added an arbitrary control ("Anchor") as a property to my converter. With that control I access the logical tree. In the example I grab the Tag property from the enclosing Grid and convert the value accordingly.
public class SomeConverter : IValueConverter
{
public System.Windows.Controls.Control Anchor { get; set; }
public object Convert(object value, Type t, object parameter, CultureInfo culture)
{
return toUpper() ? value.ToString().ToUpper() : value;
}
public object ConvertBack(object value, Type t, object parameter, CultureInfo culture)
{
return value;
}
private bool toUpper()
{
string tag = (Anchor.Parent as Grid).Tag as string;
return ! String.IsNullOrEmpty(tag);
}
}
So far so good. The real ugly part is how I assign the control to the property. I create an empty ContentControl and assign it to the converter definition. In order for the ContontControl to be in the logical tree I also need to instantiate is somewhere, which I do with Visibility=hidden. Here's the XAML:
<Window x:Class="WpfApp4__Various_Tests_.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp4__Various_Tests_"
Title="MainWindow" Height="100" Width="300">
<Window.Resources>
<ContentControl x:Key="anchor" Visibility="Hidden"/>
</Window.Resources>
<Grid Tag="toUpper">
<Grid.Resources>
<local:SomeConverter x:Key="SomeConverter" Anchor="{StaticResource anchor}"/>
</Grid.Resources>
<StaticResource ResourceKey="anchor" />
<TextBox
x:Name="textBox"
Text="{Binding SomeProperty, Converter={StaticResource SomeConverter}}"
/>
</Grid>
You can try using MultiBinding with IMultiValueConverter. There you can pass the target element using a binding.

Manage the targetType of a Binding in a MultiBinding

So, I have a multi-binding with a converter that takes in some values and finds the max of them. The problem is that one of the bindings utilises a converter that expects a double target type, while the binding has an object target type. I was wondering if there was any way to modify the target type of a binding in any way.
Below is an approximation of my xaml:
<TextBlock>
<TextBlock.Width>
<MultiBinding Converter="{StaticResource _maxValueConverter}">
<Binding Source="{StaticResource _constantZeroValue}"/>
<Binding Path="ActualWidth"
ElementName="_previousTextBlock"
Converter="{StaticResource _requiresDoubleTargetConverter}"/>
</MultiBinding>
</TextBlock.Width>
</TextBlock>
So basically if there is any way to tell the second binding that it is outputting to a double value, that'd be great.
Minimal Verifiable Complete Example:
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApplication1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<StackPanel.Resources>
<sys:Double x:Key="constantZero">0</sys:Double>
<local:RequiresDoubleTargetConverter x:Key="requiresDoubleTargetConverter" />
<local:MaxValueConverter x:Key="maxValueConverter" />
</StackPanel.Resources>
<Border x:Name="topBorder"
BorderThickness="1"
BorderBrush="Black"
HorizontalAlignment="Left">
<TextBlock x:Name="topTextBlock"
Background="Aqua"
Text="{Binding TopText}" />
</Border>
<Border BorderThickness="1"
BorderBrush="Black"
MinWidth="100"
HorizontalAlignment="Left">
<TextBlock Background="ForestGreen"
Text="{Binding BottomText}"
TextWrapping="Wrap"
MinWidth="100">
<TextBlock.Width>
<MultiBinding Converter="{StaticResource maxValueConverter}">
<MultiBinding.Bindings>
<Binding Path="ActualWidth" ElementName="topTextBlock" Converter="{StaticResource requiresDoubleTargetConverter}" />
<Binding Source="{StaticResource constantZero}" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Width>
</TextBlock>
</Border>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string TopText
{
get { return "Hello World!"; }
}
public string BottomText
{
get { return "hi earth."; }
}
public MainWindow()
{
InitializeComponent();
}
}
public class RequiresDoubleTargetConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// I am looking for a way to manually ensure that "targetType == typeof(double)" evaluates to true.
if (targetType != typeof(double))
{
return null;
}
else
{
// Actual converter performs this calculation.
return (double)value - 14;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Irrelevant method for our purposes.
throw new NotImplementedException();
}
}
public class MaxValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double max = double.NegativeInfinity;
foreach (object value in values)
{
if (value is double)
{
max = Math.Max((double)value, max);
}
else
{
Debug.Fail("All values must be doubles");
}
}
return max;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
// Irrelevant method for our purposes.
throw new NotImplementedException();
}
}
}
This was created using Visual Studio 2015, and is verified to show the erroneous behaviour. What I am trying to determine is if it is possible to manually set the targetType of the RequiresDoubleTargetConverter from the xaml.
Bindings operate on object, as far as the type system is concerned. If you want a specific type, you'll need to ensure that yourself.
However you could use the target type passed to the converter to determine which type is required, and modify the converter's return value accordingly.
So is there a way to set the target type of the converter manually, or am I stuck with it being object?
You are stuck with it being object as the signature of the Convert method is always the same, i.e. it accepts an object[] of values and nothing else.
You will have to cast values[1] to a double in your Convert method (because the method will always and only be passed values of type object):
double d = (double)values[1];
I've come up with a way around this, but it only works if you have access to the converter you're using on the MultiBinding itself (or you can add one.) as well as a little extra effort if it also uses a ConverterParameter, TargetNullValue and/or a StringFormat.
The trick is when you add the child Binding to the MultiBinding, you remove the Converter, ConverterParameter, TargetNullValue and StringFormat values from that child binding and store them somewhere that's accessible by the Convert method for the MultiBinding's converter. (We use a wrapper MarkupExtension to simulate the MultiBinding so we have access to everything before they're actually applied as once they are, you can't change them.)
Then in the MultiBinding's Convert method, you're now getting the raw, not-yet-converted/formatted/coalesced value from the child binding, but you also have the ultimate target that you need (double in this example) as it was handed to the Convert method for the MultiBinding that you're in.
With that information, you then call the child converter's Convert method manually, passing in the not-yet-converted value, the targetType (passed in to you), and the childConverterParameter.
You take the result of that call, and if null, return the TargetNullValue from the child binding.
If it's not null and both targetType is a string and you have a String Format, finally format the results.
Here's pseudo-code (i.e. off the top of my head. Prolly lots of syntax errors, etc. For the actual code, you can see me using it in my DynamicResourceBinding class that I have up on StackOverflow here.)
// Convert function for the MultiBinding
private object Convert(object[] values, Type targetType, object parameter, Culture culture){
var rawChildBindingResult = values[0]; // assuming it's in the first position
var convertedChildBindingResult = childConverter(rawChildBindingResult, targetType, childConverterParameter, culture);
if(convertedChildBindingResult == null)
convertedChildBindingResult = childTargetNullValue;
else if(targetType == typeof(string) && childStringFormat != null)
convertedChildBindingResult = string.Format(childStringFormat, convertedChildBindingResult);
// Now do whatever you would with the 'convertedChildBindingResult' as if things were normal
}
Again, take a look at the link to see it in context.
Hope this helps!

Convert Enum to string inside TextBlock text

I have simple Enum:
public enum StatusMessage
{
Cancel,
Done,
[Description("In process...")]
InProcess,
[Description("We have delay...")]
Delay,
Waiting
}
And GridViewColumn:
My property:
StatusMessage StatusMsg;
XAML:
<GridViewColumn Width="180" Header="Status" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding StatusMsg}" Foreground="{Binding StatusMsg,Converter={my:StatusMessageToColorConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
And i have this EnumToStringConverter:
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string EnumString;
try
{
EnumString = Enum.GetName((value.GetType()), value);
return EnumString;
}
catch
{
return string.Empty;
}
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Now i want to use this Convertor inside my TextBlock :
<TextBlock Text="{Binding StatusMsg, Converter={my:EnumToStringConverter}}" Foreground="{Binding StatusMsg,Converter={my:StatusMessageToColorConverter}}" />
So the problem is that i have this error:
'my:EnumToStringConverter' is used like a markup extension but does
not derive from MarkupExtension.
What is this MarkupExtension ?
You need to declare an instance of the EnumToStringConverter in XAML. It can be a local resource or declared in app.xaml to make it accessible everywhere.
<Window.Resources>
<my:EnumToStringConverter x:Key="DefaultEnumToStringConverter"/>
</Window.Resources>
Then use it like this:
Text="{Binding StatusMsg, Converter={StaticResource DefaultEnumToStringConverter}}"
Note the word "StaticResource" in the converter. That is the markup extension. This one tells WPF to go find the static resource with the key "DefaultEnumToStringConverter". WPF will search up the visual tree of the element looking for a resource with that key. If one isn't found it will check at the application level in app.xaml.
MarkupExtensions are the things at the beginning of an attribute enclosed in the {}, "x", "binding", "static", etc. They are what give WPF the ability to resolve the text attribute in to a useful object instance. You can create your own MarkupExtensions to do some pretty cool things.
In your particular example it is complaining because it is looking for a markup extension named "my", from the inner Converter={my:EnumToStringConverter}.

Expander Header text binding not respecting the string formatter

I have a WPF Expander in my project. I have applied styles to the expander to change the header colors and whatnot. After doing so, my data is still bound to the Header's content area, but it just binds the raw data and not the formatting I have specified. See the examples below.
<Style x:Key="ValkyrieStyleExpander" TargetType="{x:Type Expander}">
<!-- Ommiting property setters for brevity -->
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" FontWeight="Bold"
Foreground="White" VerticalAlignment="Center" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
And here is the actual declaration with the appropriate binding syntax
<Expander Style="{StaticResource ValkyrieStyleExpander}"
Margin="10,10,0,0"
Width="670"
Header="{Binding PolicyNumber, StringFormat=Policy {0}}">
</Expander>
We I run the app, the header should display "Policy 123456", and before I restyled the expander it did so. But now when I run the app, the header just shows "123456". I am still kind of a babe-in-the-woods when it comes to databinding, so I am not sure really what I need to do to get the new style to show the correct data. Hopefully the answer isn't to add it to the ValkyrieStyleExpender's Header Template style, as that would defeat the purpose of having a style (Not all expanders in the project are for displaying a particular policy)
StringFormat usually does not work when using it within the Expander.Header property since the property is not of type string.
You will need to write your own class derived from IFormatter that implements the formatted string you'd actually define in the property. I've researched quite a bit and found no better solution for this issue.
The class may look as follows:
public class SomeClass: IFormattable
{
public string ToString(string format, IFormatProvider formatProvider)
{
if(format == "n")
{
return "This is the formatted string";
}
else
{
return "this is the non-formatted string";
}
}
}
And you would use it in your style this way:
Setter Property="HeaderStringFormat" Value="n" />
If the string format is not working for you, you can easily implement a converter, such as...
#region PolicyConverter (ValueConverter)
public class PolicyConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
return "Policy " + value.ToString();
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
#endregion
...and then declare it in your Xaml...
<Window.Resources>
<pc:PolicyConverter x:Key="PolicyConverter"/>
</Window.Resources>
...and finally reference it in your template. I did not use (or modify) your ValkyrieStyleExpander code. But to verify the correctness of this approach, I used the following declaration as a functional prototype...
<Expander Name="Expander1">
<Expander.HeaderTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ElementName=Expander1,
Path=DataContext.PolicyNumber,
Converter={StaticResource PolicyConverter}}"/>
</DataTemplate>
</Expander.HeaderTemplate>
</Expander>
...and it worked as expected (.net 4.5). If you needed to evacuate the in-lined templating to a global declaration in your Xaml, this would also make a great starting point.

WPF: multiple controls binding to same property

Hello
I'm trying to change several controls' property according to some environment variables and i want to avoid creating a property for each control in the datacontext, so i thought using a converter which sets the property according to control name. Goal is to use one property for all controls:
<Grid.Resources>
<local:NameToStringConverter x:Key="conv" />
</Grid.Resources>
<TextBlock Name="FordPerfect"
Text="{Binding ElementName="FordPerfect" Path=Name, Converter={StaticResource conv}, Mode=OneWay}"/>
<TextBlock Name="Arthur"
Text="{Binding ElementName="Arthur" Path=Name, Converter={StaticResource conv}, Mode=OneWay}"/>
<TextBlock Name="ZaphodBeeblebrox"
Text="{Binding ElementName="ZaphodBeeblebrox" Path=Name, Converter={StaticResource conv}, Mode=OneWay}"/>
and ...
public class NameToStringConverter : IValueConverter
{
public object Convert(
object value, Type targetType,
object parameter, CultureInfo culture)
{
if (MyGlobalEnv.IsFlavor1 && ((string)value).Equals("ZaphodBeeblebrox")) return "42"
if (MyGlobalEnv.IsFlavor2 && ((string)value).Equals("ZaphodBeeblebrox")) return "43"
if (MyGlobalEnv.IsFlavor1 && ((string)value).Equals("Arthur")) return "44"
return "?";
}
public object ConvertBack(
object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
I'm sure there's a better and more elegant way... Any ideas?
The point of oneway databinding is just to decouple UI (XAML) from code (CS). Here, your code and UI are tied so tightly together that trying to do this through databinding is really not buying you anything. You might simplify things by writing a method that takes the data value and applies it correctly to each control - still tightly coupled (bad) but at least the code is condensed and easy to follow (less bad).
What you should probably do though is not rely on the control name but define a ConverterParameter. See the bottom 1/3 of this article http://www.switchonthecode.com/tutorials/wpf-tutorial-binding-converters
You may bind directly to environment variable in your situation :
<Window xmlns:system="clr-namespace:System;assembly=mscorlib" ...>
<TextBlock Text="{Binding Source={x:Static system:Environment.OSVersion}}"/>

Resources