How to position tooltip bottom center - wpf

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>

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.

How to set a DynamicResource as PriorityBinding?

I have a multilanguage application which contains two dictionary en.xaml and it.xaml, suppose that I have the following situation:
I need to display a default text in a TextBlock such as: Contact saved 0 in memory using a DynamicResource key that contains the text above.
Unfortunately xaml doesn't allow to use DynamicResource in a StringFormat or as FallbackValue so I used this code:
<TextBlock Tag="{DynamicResource defaultContactStr}">
<TextBlock.Text>
<PriorityBinding>
<Binding Path="ContactSaved" />
<Binding Path="Tag" RelativeSource="{RelativeSource Mode=Self}" />
</PriorityBinding>
</TextBlock.Text>
this will display only 0 which is the default value of the proeprty ContactSaved, but I need to display: Contact saved 0 in memory, or if the value change: Contact saved 5 in memory etc...
how can I manage this situation?
Using a StringFormat works only if the format (the translation in your case) is known at compile-time:
<TextBlock>
<TextBlock.Text>
<Binding Path="ContactSaved" StringFormat="{}Contact saved {0} in memory" />
</TextBlock.Text>
</TextBlock>
Othwerwise you may handle this using a converter and a MultiBinding:
public class CustomConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string resource = values[0] as string;
string contactSaved = values[1].ToString();
return string.Format(resource, contactSaved);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<TextBlock Tag="{DynamicResource defaultContactStr}">
<TextBlock.Text>
<MultiBinding>
<MultiBinding.Converter>
<local:CustomConverter />
</MultiBinding.Converter>
<Binding Path="Tag" RelativeSource="{RelativeSource Mode=Self}" />
<Binding Path="ContactSaved" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
en.xaml:
<system:String x:Key="defaultContactStr">Contact saved {0} in memory</system:String>
Remember that XAML is nothing but a markup language.

WPF MultiBinding Ellipse Fill

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.

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>

Using Multi Binding in telerik Datagrid

How do I pass multiple parameters in command parameters.
here is what I am trying to do:
I want to send Is checked or not (I can do this by introducing a Boolean field to the object bound. But I dont want to do that) and I want to send the selected data object for that row.
<telerik:GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewDataControl}}, Path=DataContext.LineItemSelection}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource CommandConverter}">
<Binding Path="IsChecked" />
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
</DataTemplate>
</telerik:GridViewColumn.CellTemplate>
UPDATE:
I added a class called selection Item. To see what the converter is getting.
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
SelectionItem si = new SelectionItem();
foreach (var value in values)
{
Type t = value.GetType();
if (t.FullName == "System.Boolean")
si.IsSelected = (bool) value;
else
{
si.SelectedCustomer = value as Customer;
}
}
return si;
}
The type of the second parameter is the checkbox itself if I use
<Binding RelativeSource="{RelativeSource Self}"/>
Here I want the data item that is bound to that row(in this case Customer).
I even tried using
<Binding RelativeSource= "{RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewColumn}}" Path="DataContext" />
But this is also coming in as null. why is this?
Try out simple binding by passing in converter current binding source, then cast to underlying object and based on IsChecked return a value.
<DataTemplate>
<CheckBox Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewDataControl}}, Path=DataContext.LineItemSelection}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource CommandConverter}">
<Binding Path="IsChecked" />
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
</DataTemplate>
// CommandConverter should simply return the values
public object Convert(...)
{
return values;
}
Now in command handler you would be able access parameters:
public void OnLineItemSelection(object parameter)
{
object[] parameters = (object[])parameter;
bool isChecked = (double)parameters[0];
var instance = (TYPENAME)parameters[1];
}
using
<CheckBox Name="chkSelection" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:GridViewDataControl}}, Path=DataContext.LineItemSelection}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource CommandConverter}">
<Binding Path="IsChecked" ElementName="chkSelection" />
<Binding />
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
did the trick

Resources