views:

930

answers:

4

Hi,

I am trying to display a tooltip for an item generated by an ItemsControl that needs to pull data from conceptually unrelated sources. For example, say I have an Item class as follows:

public class Item
{
    public string ItemDescription { get; set; }
    public string ItemName { get; set; }
}

I can display the Item within an ItemsControl with a tooltip as follows:

<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ItemName}">
                <TextBlock.ToolTip>
                    <ToolTip>
                        <TextBlock Text="{Binding ItemDescription}" />
                    </ToolTip>
                </TextBlock.ToolTip>
            </TextBlock>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

But say I have another property that can be accessed via the DataContext of the ItemsControl. Is there any way to do this from within the tooltip? E.g.,

<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ItemName}">
                <TextBlock.ToolTip>
                    <ToolTip>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <TextBlock Text="{Binding ItemDescription}" />
                            <TextBlock Grid.Row="1" Text="{Bind this to another property of the ItemsControl DataContext}" />
                        </Grid>
                    </ToolTip>
                </TextBlock.ToolTip>
            </TextBlock>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The code for the test Window I used is as follows:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        List<Item> itemList = new List<Item>() {
            new Item() { ItemName = "First Item", ItemDescription = "This is the first item." },
            new Item() { ItemName = "Second Item", ItemDescription = "This is the second item." } 
        };

        this.Items = itemList;
        this.GlobalText = "Something else for the tooltip.";
        this.DataContext = this;
    }

    public string GlobalText { get; private set; }

    public List<Item> Items { get; private set; }
}

So in this example I want to show the value of the GlobalText property (in reality this would be another custom object).

To complicate matters, I am actually using DataTemplates and show two different types of objects within the ItemsControl, but any assistance would be greatly appreciated!

+1  A: 

Second attempt

Ok, the Relative Source Binding doesn't work in this case. It actually works from a data template, you can find many examples of this on the Internets. But here (you were right, David, in your comment) ToolTip is a special beast that is not placed correctly in the VisualTree (it's a property, not a control per se) and it doesn't have access to the proper name scope to use relative binding.

After some more searching I found this article, which describes this effect in details and proposes an implementation of a BindableToolTip.

It might be an overkill, because you have other options -- like using a static property on a class (as in Dabblernl's response) or adding a new instance property to your Item.

First attempt :)

You should consult with the Relative Source Binding types (in this cheat sheet for example):

So your binding will look somehow similar to this:

{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path= GlobalText}
Yacoder
Thanks Yacoder. I tried this but it didn't work - I think it's because the ToolTip popup window is not in the same visual tree as the ItemsControl.
David Chandler
What if you try to bind to AncestorType={x:Type Window} ?
Yacoder
That doesn't work either unfortunately.
David Chandler
Thanks a lot Yacoder, and Arcturus. The BindableToolTip looks interesting but I think I will fall back on my original workaround of adding extra information to the "Item" class.
David Chandler
I believe that's a good solution for a start, later on you can try a special tooltip if you need =)
Yacoder
A: 

After an hour of hair pulling I have come to the conviction that you can't reference another DataContext inside a DataTemplate for a ToolTip. For other Bindings it is perfectly possible as other posters have proven. That's why you can't use the RelativeSource trick either. What you can do is implement a static property on your Item class and reference that:

<Window x:Class="ToolTipSpike.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
    Name="Root"
    xmlns:ToolTipSpike="clr-namespace:ToolTipSpike">
    <Grid>
        <ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding ItemName}"> 
                        <TextBlock.ToolTip>
                            <ToolTip>
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition />
                                        <RowDefinition />
                                    </Grid.RowDefinitions>
                                    <TextBlock Text="{Binding ItemDescription}" />
                                    <TextBlock Grid.Row="1" 
                   Text="{Binding Source={x:Static ToolTipSpike:Item.GlobalText},
                   Path=.}"
                                    />
                                </Grid>
                            </ToolTip>
                        </TextBlock.ToolTip>
                    </TextBlock>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

using System.Collections.Generic;
using System.Windows;

namespace ToolTipSpike
{
    public partial class Window1 : Window
    {

        public List<Item> Items { get; private set; }
        public Window1()
        {
            InitializeComponent();
            var itemList = new List<Item>
                  {
                      new Item { ItemName = "First Item", ItemDescription = "This is the first item." },
                      new Item { ItemName = "Second Item", ItemDescription = "This is the second item." }
                  };
            this.Items = itemList;
            this.DataContext = this;
       }
    }

     public class Item
     {
         static Item()
         {
             GlobalText = "Additional Text";
         }
         public static string GlobalText { get; set; }
         public string ItemName{ get; set;}
         public string ItemDescription{ get; set;}
     }
}
Dabblernl
Thanks, I thought that was the case. I think I will either associate the data with the original item or use an ObjectDataProvider.
David Chandler
You can use a RelativeSource binding! See my answer below
Arcturus
OOP has nothing to do with the ToolTip behavior, but your workaround is still valid, see my answer below.
Yacoder
edited your answer to remove vote..
Arcturus
thanks, but it was wrong about the second datacontext inside datatempaten, I have working code that uses it! A sudden spell of blindness....
Dabblernl
+1  A: 

Almost correct Yacoder, and guessed way wrong there Dabblernl ;)

Your way of thinking is correct and it is possible to reference the DataContext of your ItemsControl

You are missing the DataContext property in path:

{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.GlobalText}

Second attempt ;)

http://blogs.msdn.com/tom%5Fmathews/archive/2006/11/06/binding-a-tooltip-in-xaml.aspx

Here is an article with the same problem. They can reference the DataContext of their Parent control by the PlacementTarget property:

<ToolTip DataContext=”{Binding RelativeSource={RelativeSource Self},Path=PlacementTarget.Parent}”>

If you would place the DataContext on a deeper level, you avoid changing your Item DataContext

A second suggestion (Neil and Adam Smith) was that we could use PlacementTarget in the binding. This is nice, as I am actually inheriting the DataContext already from the page that hosts the DataControl, and this would allow the ToolTip to gain access back to the origial control. As Adam noted, though, you have to be aware of the parent/child structure off your markup:

Arcturus
This would work fine if I wanted to display the information directly in the ItemsControl template, but I don't - I want to display it in the ToolTip. This binding doesn't work at all for the ToolTip.
David Chandler
This doesn't work, I checked it too :)
Yacoder
Yacoder is correct on this one.. Tooltip is a special beast which does not reside in the VisualTree. RelativeSource bindings will not work in this case.
Arcturus
Edited answer after finding an interesting article :)
Arcturus
A: 

This is a case where I think it's conceptually more appropriate to do this in the view model than it is in the view anyway. Expose the tooltip information to the view as a property of the view model item. That lets the view do what it's good at (presenting properties of the item) and the view model do what it's good at (deciding what information should be presented).

Robert Rossney