简体   繁体   中英

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?

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>

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 Decimals and not to DataContext .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM