Access logical tree from inside a converter - wpf

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.

Related

In WPF XAML, is it possible to bind a ViewModel property to a DependencyProperty defined in a Converter without using `x:Name`? [duplicate]

This question already has answers here:
Binding ConverterParameter
(3 answers)
Closed 27 days ago.
In the following xaml sample source, I am trying to bind the A property in the SampleViewModel to the B property, which is a DependencyProperty in the SampleConverter.
However, when I do this, I get a XAML bind error and the Data Context is displayed as null.
I know it is possible to get the Data Context using x:Name, but is it possible to get the Data Context without using x:Name?
<Window
x:Class="WpfApp1.BindPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Window.DataContext>
<local:SampleViewModel />
</Window.DataContext>
<StackPanel>
<StackPanel.Height>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Mode=Self}">
<Binding.Converter>
<local:SampleConverter B="{Binding A}" />
</Binding.Converter>
</Binding>
</StackPanel.Height>
</StackPanel>
</Window>
I should mention that with RelativeSource I could not get other than myself (in this case, other than the SampleConverter).
That isn't how you use converters.
I don't know what local:SampleConverter is exactly but it has a property B
B="{Binding A}"
The binding provides the value, you may also bind a command parameter
Here's an example
<Image Name="GridImage"
Visibility="{Binding AppSettings.ShowGrid
, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
This is going to set the Visibility property, the binding is to AppSetting.ShowGrid which is in the datacontext rather than converter. The BooleanToVibilityConverter is taking a bool from ShowGrid and converts it to a Visibility.
If you wanted to bind multiple properties then you can use a multiconverter with a multibinding.
<MultiBinding Converter="{ui:AllMultiplyMultiConverter}" StringFormat="{}{0:n0}">
<Binding Path="Value" ElementName="turnTime"/>
<Binding Path="MoveRate"/>
</MultiBinding>
A multiconverter receives an array object[] for those values.
Since markup extension and ivalueconverter are not dependency objects you would need to reference the parent object to use a dependency property.
You could add dependency properties to your window and reference them in a markup extension that's also a valueconverter.
Consider this value converter that's also a markup extension.
public class AddConverter : MarkupExtension, IValueConverter
{
public double ValueToAdd { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double bound = (Double)value;
return bound + ValueToAdd;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
ValueToAdd is just a property, you can't bind it.
You can get a reference in that ProvideValue to the parent dependency object. Hence window or usercontrol.
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
That target is your control and you can grab dependency properties off that. You could (say) set ValueToAdd in there and use it in the converter. You could cast the datacontext of the targetobject and read values off properties directly.
This is a very complicated approach. I've never had the need for this myself and I would recommend multibinding and multiconverter instead.

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!

How to set a StaticResource Binding in a window Title

I want to put a IValueConverter on a binding to the title of a window, so that changes when the active project changes. The problem is that the value converter is a static resource, which is only loaded a few lines later:
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject"
Height="600" Width="800" VerticalAlignment="Stretch"
Title="{Binding ActiveProject, Converter={StaticResource windowTitleConverter}}, UpdateSourceTrigger=PropertyChanged">
<Window.Resources>
<local:MainWindowTitleConverter x:Key="windowTitleConverter"/>
</Window.Resources>
<!-- Rest of the design -->
</Window>
And then the definition of the converter:
public class MainWindowTitleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return "Programme"; else return "Programme: " + (value as string);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This crashes, presumably because the StaticResource hasn't been loaded yet (I can't think of any other reason), cause without the converter it works fine. However, I can't change the order. I tried to put it in a <Window.Title> tag, but anything that I put within that tag gives a compilation error. What is the proper way of doing this?
Just use the more verbose definitions
xmlns:System="clr-namespace:System;assembly=mscorlib"
...
<Window.Resources>
<local:MainWindowTitleConverter x:Key="windowTitleConverter"/>
...
</Window.Resources>
<Window.Title>
<Binding Path="ActiveProject">
<Binding.Converter>
<StaticResource ResourceKey="windowTitleConverter" />
</Binding.Converter>
</Binding>
</Window.Title>
I can't test this at the moment, but it should work.
The proper way would be to put the converter in your app.xaml.

WPF Label adapt FontSize to it's Width and Height

I need to develop a Label control in WPF, on .NET 3.5 and VisualStudio 2010, in which the FontSize will automatically make the text fill the control area.
I don't know if I should create a CustomControl inheriting from Label or if I should create a UserControl which contains a Label control.
I've seen an example here using a ValueConverter, but I'm not understanding its behavior, here: change font size dynamically.
Can anyone give me a clue about that?
Update:
I found the solutiion using the DoubleConverter from the example I've posted before:
The soultion is using a ValueConverter, which I extracted from the example, but added the NumerFormat IFormatProvider to correctly parse "0.1" value, I found that at Decimal d1 = Decimal.Parse("0.1"); // = 1?!?:
[ValueConversion(typeof(object), typeof(double))]
public class DoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
double dblValue = (double)value;
double scale = Double.Parse(((string)parameter), System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
return dblValue * scale;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then, you have to instantiate in XAML the DoubleConverter, and specify the binding in the FonSize Property:
<UserControl x:Class="<Namespace>.LabelAutoFontSize"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:me="clr-namespace:<Namespace>"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="278">
<UserControl.Resources>
<me:DoubleConverter x:Key="doubleConverter" />
</UserControl.Resources>
<Grid>
<Label
x:Name="lbl"
FontSize="{
Binding Path=Width,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Converter={StaticResource doubleConverter},
ConverterParameter=0.116}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Content="LabelAutoFontSize"
d:LayoutOverrides="Width"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center" />
</Grid>
</UserControl>
An important point is that the value for ConverterParameter depends absolutely from the font assigned. Each font may need a different value and you have to "play around" to get the correct value to fit exactly.
<Viewbox>
<TextBlock>asd</TextBlock>
</Viewbox>
Also does the job.

Change other's property

Right now, I'm learning WPF. Can we change another WPF object's property when an WPF object's property is changed?
Below is simplified scenario.
I have a Window with a TextBox named m_Text and a ToggleButton named m_Button. I want to change the m_Text.Background property if m_Button is pressed, that is m_Button.IsChecked = true. I think it's possible using a Trigger but I don't know how to do it.
P.S. If possible, I want to do it only in XAML.
WPF makes this really easy - you can databind the TextBox's Background property directly to the IsChecked property on the ToggleButton. Of course, you will need to convert the IsChecked (boolean) to be a Brush, but WPF allows you to specify a Converter object right in the binding...
In code, you create an object that implements IValueConverter, and implement the Convert method, like
public class BoolToBrushConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool isChecked = (bool)value;
string[] colours = parameter.ToString().Split(':');
if (isChecked)
return new BrushConverter().ConvertFromString(colours[0]);
return new BrushConverter().ConvertFromString(colours[1]);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
then in xaml you need to add the namespace containing this class, declare an instance of the converter as a resource within the window, then use it in the Binding... it should look something like this:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<local:BoolToBrushConverter x:Key="boolToBrushConverter" />
</Window.Resources>
<StackPanel Height="250">
<ToggleButton Name="toggleButton" Height="32" Content="Green" />
<TextBox
Background="{Binding ElementName=toggleButton, Path=IsChecked, Converter={StaticResource boolToBrushConverter}, ConverterParameter=Green:White}" />
</StackPanel>
</Window>
UPDATE: As per Ivan's excellent suggestion - have updated to show how you can pass parameters through to the Converter from the XAML...

Resources