Max Aquila
Max Aquila

Reputation: 21

WPF - custom ToolTip with MultiBinding and different DataContexts

I want to make a UserControl in WPF (C# - MVVM) with a custom two-lines ToolTip.

In the View I have a ListBox with an ItemSource and a custom ItemTemplate where to set the previous ToolTip that at runtime shows only the first line while the second one is an empty string. Indeed the problem is the second line of the ToolTip where I use a MultiBinding with a converter; converter that fails in try/catch returning an empty string.

I know that the exception is generated by a value that is null while it should be an int not nullable, but I don't understand why.
EDIT: I was wrong saying null; the problem is that the converter strikes a cast exception because of DependencyProperty UnsetValue, I don't know why.

Here Converter code:

public class FromDecimal_MVConverter : Base_MVConverter
{
    public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        try
        {
            // Default are 2 decimals
            if (values.Length == 2)
            {
                int decimals;
                switch ((int)values[1])
                {
                    case int dec when dec < 0:
                        decimals = 0;
                        break;
                    case int dec when dec > 99:
                        decimals = 99;
                        break;
                    default:
                        decimals = (int)values[1];
                        break;
                }
                return ((decimal)values[0]).ToString("N" + decimals.ToString());
            }
            else
            {                
                return ((decimal)values[0]).ToString("N2");
            }
        }
        catch
        {
            return string.Empty;
        }
    }

    public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Here XAML code:

...
<ListBox ItemsSource="{Binding Values, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <ToolTipService.ToolTip>
                    <StackPanel>
                        <TextBlock Text="{Binding Description}"/>
                        <TextBlock>
                            <TextBlock.Text>
                                <MultiBinding Converter="{cv_ToString:FromDecimal_MVConverter}">
                                    <Binding Path="Value"/>
                                    <Binding Path="Decimals" RelativeSource="{RelativeSource FindAncestor, AncestorType=UserControl}"/>
                                </MultiBinding>
                            </TextBlock.Text>
                        </TextBlock>
                    </StackPanel>
                </ToolTipService.ToolTip>
                <TextBlock Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}">
                    <TextBlock.Text>
                        <MultiBinding Converter="{cv_ToString:FromDecimal_MVConverter}">
                            <Binding Path="Value"/>
                            <Binding Path="Decimals" RelativeSource="{RelativeSource FindAncestor, AncestorType=UserControl}"/>
                        </MultiBinding>
                    </TextBlock.Text>
                </TextBlock>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
...

As you can see Value is a property of an object in ObservableCollection Values. Decimals and Values are properties in code behind associated to their dependency properties.

Here Decimals definition:

public static readonly DependencyProperty DecimalsProperty = DependencyProperty.RegisterAttached("Decimals", typeof(int), typeof(ucMyUserControl), new FrameworkPropertyMetadata(2) { BindsTwoWayByDefault = true });

public int Decimals
{
    get { return (int)GetValue(DecimalsProperty); }
    set { SetValue(DecimalsProperty, value); }
}

I don't understand why for the TextBlock outside the ToolTip it works and why inside the ToolTip not. How can I resolve the problem?

Upvotes: 1

Views: 637

Answers (2)

Max Aquila
Max Aquila

Reputation: 21

How I have solved:
I have read the comment of @Sinatr about BindingProxy and finally I have found how to avoid the problem.

<UserControl.Resources>
    <local:BindingProxy x:Key="BP_Decimals" Data="{Binding Decimals, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"/>
</UserControl.Resources>
...
    <DataTemplate>
        <Grid>
            <Grid.ToolTip>
                <StackPanel>
                    <TextBlock Text="{Binding Description}"/>
                    <TextBlock>
                        <TextBlock.Text>
                            <MultiBinding Converter="{cv_ToString:FromDecimal_MVConverter}">
                                <Binding Path="Value"/>
                                <Binding Path="Data" Source="{StaticResource BP_Decimals}"/>
                            </MultiBinding>
                        </TextBlock.Text>
                    </TextBlock>
                </StackPanel>
            </Grid.ToolTip>
            ...
        </Grid>
    </DataTemplate>
...

In this case the BindingProxy is binded directly to the DependencyProperty Decimalsand not to DataContext.

Upvotes: 0

mm8
mm8

Reputation: 169150

The binding fails because the UserControl is not a visual ancestor of the ToolTip.

You could bind the Tag property of the Grid to the Decimals property and then bind to the Tag property using the PlacementTarget property of the ToolTip:

<DataTemplate>
    <Grid Tag="{Binding Decimals, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}">
        <Grid.ToolTip>
            <ToolTip>
                <StackPanel>
                    <TextBlock Text="{Binding Description}"/>
                    <TextBlock>
                        <TextBlock.Text>
                            <MultiBinding Converter="{cv_ToString:FromDecimal_MVConverter}">
                                <Binding Path="Value"/>
                                <Binding Path="PlacementTarget.Tag" RelativeSource="{RelativeSource FindAncestor, AncestorType=ToolTip}"/>
                            </MultiBinding>
                        </TextBlock.Text>
                    </TextBlock>
                </StackPanel>
            </ToolTip>
        </Grid.ToolTip>
        ...
    </Grid>
</DataTemplate>

Upvotes: 1

Related Questions