WPF MultiBinding Ellipse Fill - wpf

I am not able to get MultiBinding on Ellipse.Fill working correctly. I do have (single) Binding working correctly, plus MultiBinding on Ellipse.Tooltip:
<Ellipse Margin="210,56,0,0" Fill="{Binding InspectorPC, Converter={StaticResource statusButtonConverter}, Mode=OneWay}">
<Ellipse.ToolTip>
<MultiBinding Converter="{StaticResource statusStringConverter}" Mode="OneWay">
<Binding Path="InspectorPC"/>
<Binding Path="InspectorPCPing"/>
<Binding Path="InspectorPCReadHD"/>
</MultiBinding>
</Ellipse.ToolTip>
</Ellipse>
but I would like something like:
<Ellipse Margin="210,56,0,0">
<Ellipse.Fill>
<MultiBinding Converter="{StaticResource statusButtonConverter}" Mode="OneWay">
<Binding Path="InspectorPCPing"/>
<Binding Path="InspectorPCReadHD"/>
</MultiBinding>
</Ellipse.Fill>
<Ellipse.ToolTip>
<MultiBinding Converter="{StaticResource statusStringConverter}" Mode="OneWay">
<Binding Path="InspectorPC"/>
<Binding Path="InspectorPCPing"/>
<Binding Path="InspectorPCReadHD"/>
</MultiBinding>
</Ellipse.ToolTip>
</Ellipse>
(Obviously statusButtonConverter would need to be changed from IValueConverter to IMultiValueConverter, but that is not the issue.)

If this isn't working, it suggests a problem in your statusButtonConverter implementation.
A simple example shows no problem applying a MultiBinding to Ellipse.Fill:
<Window x:Class="WpfTest.FillMultiBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfTest2"
Width="320"
Height="160">
<Window.Resources>
<l:BrushPartsConverter x:Key="brushPartsConverter" />
</Window.Resources>
<Window.DataContext>
<l:FillViewModel />
</Window.DataContext>
<Ellipse>
<Ellipse.Fill>
<!-- Dodger + Blue = DodgerBlue -->
<MultiBinding Converter="{StaticResource brushPartsConverter}" Mode="OneWay">
<Binding Path="Part1" />
<Binding Path="Part2" />
</MultiBinding>
</Ellipse.Fill>
</Ellipse>
</Window>
public class FillViewModel
{
public string Part1 => "Dodger";
public string Part2 => "Blue";
}
public class BrushPartsConverter : IMultiValueConverter
{
private static readonly BrushConverter InnerConverter = new BrushConverter();
public object Convert(object[] values, Type type, object p, CultureInfo c)
{
if (values?.Length == 2)
return InnerConverter.ConvertFrom("" + values[0] + values[1]);
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] types, object p, CultureInfo c)
{
return new[] { DependencyProperty.UnsetValue };
}
}
Post the code for your converter and binding context (view model), and we'll see what we can do.

Related

WPF) Why does the exact same binding work in one place but not another?

So i am at a complete loss why the exact same binding works for one element but not another (on the same control, code for binding is copy and pasted).
I have made a MultiValueConverter that takes in 4 values. values[0] determines which one of the values[1-3] should be returned. (Ternary logic)
This converter works great. I use this to choose which color and image a control should have based on an enum. But, when using the same converter for tooltip to choose between string, then i get a binding error.
The weird thing is that is that when i use the same converter inside a template for choosing which string for the ToolTip, then it works! The exact same code copy and pasted.
When i bind with the ToolTip (not in a template) the value[0] is "{DependencyProperty.UnsetValue}", instead of the enum that i have binded to.
Code inside a UserControl)
<v:ColoredImage Width="20" Height="20" HorizontalAlignment="Right">
<v:ColoredImage.Color> //THIS WORKS
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="ParamStatus" ElementName="pn"/> <-- SAME BINDING
<Binding Source="{StaticResource GreenLight}"/>
<Binding Source="{StaticResource YellowLight}"/>
<Binding Source="{StaticResource RedLight}"/>
</MultiBinding>
</v:ColoredImage.Color>
<v:ColoredImage.Image> // THIS WORKS
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="ParamStatus" ElementName="pn"/> <-- SAME BINDING
<Binding Source="{StaticResource OkIcon}"/>
<Binding Source="{StaticResource WarningIcon}"/>
<Binding Source="{StaticResource ErrorIcon}"/>
</MultiBinding>
</v:ColoredImage.Image>
<v:ColoredImage.ToolTip>
<ToolTip> //THIS PART DOES NOT WORK
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="ParamStatus" ElementName="pn"/> <-- SAME BINDING
<Binding Source="OK"/>
<Binding Source="Not Synced"/>
<Binding Source="ERROR"/>
</MultiBinding>
</ToolTip>
</v:ColoredImage.ToolTip>
</v:ColoredImage>
Code Inside a Style and ControlTemplate (this code work, even though it is the same)
<v:ColoredImage Height="24" Width="24" Margin="65,65,0,0" VerticalAlignment="Center">
<v:ColoredImage.Color>
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="Status" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Source="{StaticResource GreenLight}"/>
<Binding Source="{StaticResource YellowLight}"/>
<Binding Source="{StaticResource RedLight}"/>
</MultiBinding>
</v:ColoredImage.Color>
<v:ColoredImage.Image>
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="Status" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Source="{StaticResource OkIcon}"/>
<Binding Source="{StaticResource UnidentifiedIcon}"/>
<Binding Source="{StaticResource ErrorIcon}"/>
</MultiBinding>
</v:ColoredImage.Image>
<v:ColoredImage.ToolTip>
<ToolTip>
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="Status" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Source="OK"/>
<Binding Source="Unidentified"/>
<Binding Source="ERROR"/>
</MultiBinding>
</ToolTip>
</v:ColoredImage.ToolTip>
</v:ColoredImage>
I could fix this by doing a style/template for my first UserControl. But i feel like i shouldnt have too, and either way i wanna know why the EXACT same code works in one place but not another. I'm completely dumbfounded.
Code for the Converter, this is not where problem occurs, but i figured someone is going to ask me to post it anyway:
public class TernaryConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
int index = (int)values[0];
if (index < 0 || index > 2)
{
return values[1];
}
return values[index+1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("EnumToImageConverter can only be used OneWay.");
}
}
The reason why ElementName="pn" doesn't work in a ToolTip is that a ToolTip resides in its own element tree and there is no element named "pn" in the namescope of this tree.

Add comma to string inside StringFormat in wpf

I have this textblock
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}, {2}, ">
<Binding Path="object.strProp1" />
<Binding Path="object.strProp2" />
<Binding Path="object.strProp3" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Let's assume object is not null and *strProp1* = "strProp1", *strProp2* = "strProp2", and *strProp2* = "strProp2".
The output for this would be something like this:
strProp1, strProp2, strProp3,
What I would like to know is how to remove the ',' whenever object is null or one of the properties is empty. That is, if object is null then the Textblock would just be empty. Or if one of the objects is empty then it will just be empty.
Any recommendations on how to this? Thanks!
Edit: preferably in xaml only :)
I know this is an old question but I was doing a similar thing and came up with this solution. You just need to escape the ',' as you would escape special characters in a string with a '\'.
So your binding would be:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}\, {1}\, {2}\, ">
<Binding Path="object.strProp1" />
<Binding Path="object.strProp2" />
<Binding Path="object.strProp3" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
You have to use a Converter
MultiValueConverter.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace DataBinding
{
public class MultiStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values != null)
{
StringBuilder formattedString = new StringBuilder();
int count = 0;
foreach (var item in values)
{
if (string.IsNullOrEmpty((String)item) == false)
{
if (count == 0)
formattedString.Append(item);
else
formattedString.Append(", " + item);
count++;
}
}
return formattedString.ToString();
}
else
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
}
XAML
<Window x:Class="DataBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataBinding"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MultiStringConverter x:Key="multiStringConverter"/>
</Window.Resources>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource multiStringConverter}">
<Binding Path="object.strProp1" />
<Binding Path="object.strProp2" />
<Binding Path="object.strProp3" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Window>
<TextBlock TextWrapping="Wrap">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Address.FullAddress}" Value="">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
<Run Text="{Binding Address.LineOne}"/>
<Run Text="{Binding
Address.LineTwo,
StringFormat='{}{0}, ',
TargetNullValue=''}"/>
<Run Text="{Binding
Address.City,
StringFormat='{}{0}, ',
TargetNullValue=''}"/>
<Run Text="{Binding Address.StateProvince}"/>
<Run Text="{Binding
Address.Zip,
StringFormat='{}{0}, ',
TargetNullValue=''}"/>
<Run Text="{Binding Address.Country}"/>
</TextBlock>

Applying a (non-multi) ValueConverter the output of a MultiBinding + StringFormat

Is there a way to apply a (single, not multi) ValueConverter to the output of a MultiBinding which uses StringFormat (i.e. after the string has been formatted).
It would be the equivalent of that code, in which I used an intermediary collapsed TextBlock to do the trick :
<StackPanel>
<TextBox x:Name="textBox1">TB1</TextBox>
<TextBox x:Name="textBox2">TB2</TextBox>
<TextBlock x:Name="textBlock" Visibility="Collapsed">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}{1}">
<Binding ElementName="textBox1" Path="Text"/>
<Binding ElementName="textBox2" Path="Text"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding ElementName=textBlock,
Path=Text, Converter={StaticResource SingleValueConverter}}" />
</StackPanel>
Here is a hack that does what you want:
public static class Proxy
{
public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
"Text",
typeof(string),
typeof(Proxy),
new PropertyMetadata(string.Empty));
public static void SetText(this TextBlock element, string value)
{
element.SetValue(TextProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBlock))]
public static string GetText(this TextBlock element)
{
return (string) element.GetValue(TextProperty);
}
}
<StackPanel>
<TextBox x:Name="textBox1">TB1</TextBox>
<TextBox x:Name="textBox2">TB2</TextBox>
<TextBlock Text="{Binding Path=(local:Proxy.Text),
RelativeSource={RelativeSource Self},
Converter={StaticResource SingleValueConverter}}">
<local:Proxy.Text>
<MultiBinding StringFormat="{}{0}{1}">
<Binding ElementName="textBox1" Path="Text" />
<Binding ElementName="textBox2" Path="Text" />
</MultiBinding>
</local:Proxy.Text>
</TextBlock>
</StackPanel>
If you look at the MultiBinding.Converter Property page on MSDN, you will see that you can provide a Converter for a MultiBinding. However, it is not a normal IValueConverter, instead it requires an IMultiValueConverter. It can be used like this:
<TextBlock x:Name="textBlock" Visibility="Collapsed">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}{1}" Converter="{StaticResource Converter}"
ConverterParameter="SomeValue">
<Binding ElementName="textBox1" Path="Text"/>
<Binding ElementName="textBox2" Path="Text"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
An example of an IMultiValueConverter implementation can be found in the linked pages.

Databinding one property on a Dialog from two contols

I have a Window that I'm showing using ShowDialog. One of the values I'm trying to get from the user is size in GB or TB. So I have two controls for this, an IntegerUpDown from the WPF Extended Toolkit and a ComboBox:
<xctk:IntegerUpDown Name="SizeN" Minimum="1" Maximum="1023" Increment="1" Value="100"/>
<ComboBox Name="SizeS" SelectedIndex="0">
<ComboBoxItem>GB</ComboBoxItem>
<ComboBoxItem>TB</ComboBoxItem>
</ComboBox>
I am setting the Dialog's DataContext to itself. I have defined the Capacity property:
public ulong Capacity { get; set; }
public CustomWindow()
{
InitializeComponent();
DataContext = this;
}
I have already created an IMultiValueConverter, PowerConverter, that takes an int and a string and returns ulong. I think the correct MultiBinding is:
<Window.Resources>
<local:PowerConverter x:Key="CapacityConverter" />
</Window.Resources>
<MultiBinding Converter="{StaticResource CapacityConverter}">
<Binding ElementName="SizeN" Path="Value" />
<Binding ElementName="SizeS" Path="SelectedValue" />
</MultiBinding>
I can't figure out how to assign this binding to the Capacity property on the Dialog. I want WPF to automagically set the Capacity property for me. Any ideas?
I had to convert Capacity into a DependencyProperty on CustomWindow, set the SelectedValuePath attribute on the ComboBox, and assign the binding to Capacity in the style.
XAML:
<Window xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="clr-namespace:Xceed.Wpf.Toolkit;assembly=WPFToolkit.Extended"
xmlns:local="clr-namespace:MyProject"
x:Class="MyProject.CustomWindow" Title="CustomWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<local:PowerConverter x:Key="CapacityConverter" />
</Window.Resources>
<Window.Style>
<Style TargetType="{x:Type local:CustomWindow}">
<Setter Property="Capacity">
<Setter.Value>
<MultiBinding Converter="{StaticResource CapacityConverter}"
Mode="TwoWay">
<Binding ElementName="SizeNumber" Path="Value"
Mode="TwoWay" />
<Binding ElementName="SizeSuffix" Path="SelectedValue"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Window.Style>
<StackPanel>
<xctk:IntegerUpDown Name="SizeNumber" Minimum="1" Maximum="1023" Increment="1"
Value="100"/>
<ComboBox Name="SizeSuffix" SelectedIndex="0" SelectedValuePath="Content">
<ComboBoxItem>GB</ComboBoxItem>
<ComboBoxItem>TB</ComboBoxItem>
</ComboBox>
</StackPanel>
</Window>
Code behind:
public partial class CustomWindow : Window
{
public CustomWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty CapacityProperty =
DependencyProperty.Register("Capacity", typeof(ulong), typeof(CustomWindow));
public ulong Capacity
{
get
{
return (ulong)GetValue(CapacityProperty);
}
set
{
SetValue(CapacityProperty, value);
}
}
}

How to position tooltip bottom center

I'm trying to position my tooltip so that it would be on the bottom and center of my target object. I can position it to be just on the bottom by ToolTipService.Postion="Bottom", but how to position it to be also on the center?
I agree, the options available for positioning a ToolTip are a little limited. I think you'll have to combine Placement="Bottom" with HorizontalOffset to get Bottom/Center positioning.
To center the ToolTip relative to the PlacementTarget you can use
(PlacementTarget.ActualWidth / 2.0) - (ToolTip.ActualWidth / 2.0)
Example
<Button Content="Test">
<Button.ToolTip>
<ToolTip Content="ToolTip Text"
Placement="Bottom">
<ToolTip.HorizontalOffset>
<MultiBinding Converter="{StaticResource CenterToolTipConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="PlacementTarget.ActualWidth"/>
<Binding RelativeSource="{RelativeSource Self}" Path="ActualWidth"/>
</MultiBinding>
</ToolTip.HorizontalOffset>
</ToolTip>
</Button.ToolTip>
</Button>
CenterToolTipConverter
public class CenterToolTipConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.FirstOrDefault(v => v == DependencyProperty.UnsetValue) != null)
{
return double.NaN;
}
double placementTargetWidth = (double)values[0];
double toolTipWidth = (double)values[1];
return (placementTargetWidth / 2.0) - (toolTipWidth / 2.0);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
If you need to center several ToolTips you could use a Style like
<Style x:Key="centeredToolTip" TargetType="ToolTip">
<Setter Property="HorizontalOffset">
<Setter.Value>
<MultiBinding Converter="{StaticResource CenterToolTipConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="PlacementTarget.ActualWidth"/>
<Binding RelativeSource="{RelativeSource Self}" Path="ActualWidth"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
<!-- ... -->
<Button Content="Test">
<Button.ToolTip>
<ToolTip Style="{StaticResource centeredToolTip}"
Placement="Bottom"
Content="ToolTip Text"/>
</Button.ToolTip>
</Button>

Resources