WPF ToolTip does not update
Asked Answered
G

6

14

Assuming I have a simple class that represents a staff member

class Staff
{
    public string FirstName { get; set; }
    public string FamilyName { get; set; }
    public int SecondsAlive { get; set; }        
}

and I have a DataTemplate for staff

<DataTemplate DataType={x:Type Staff}>
    <StackPanel Orientation="Horizontal">
        <TextBlock Text={Binding FirstName}/>
        <TextBlock Text=" ">
        <TextBlock Text={Binding FamilyName}/>
        <StackPanel.ToolTip>
            <TextBlock Text={Binding SecondsAlive}/>
        </StackPanel.ToolTip>
     </StackPanel>
</DataTemplate>

I then show a whole bunch of staff in a ListBox

myListBox.ItemsSource = GetAllStaff();

Pretty standard stuff. The problem I have is that the tooltip which shows the number of seconds that someone has been alive does not get updated. When you first mouse over a staff member then it works fine but from then on it keeps that value for ever. I could implement INotifyPropertyChanged to get around this but it seems like overkill to do this for every staff member whenever SecondsAlive changes. Say I have 400 staff in the list then I have to raise 400 events even though the user might never look at another tooltip. What I would like is to make the tooltip request the SecondsAlive property ever time it is shown. Is that possible?

Please note that this is just an example and I don't need to know how many seconds my staff have been alive :-) But I have the same issue that I need to raise an even around 400 times just for a tooltip which someone probably won't look at.

Greenroom answered 11/7, 2011 at 3:4 Comment(5)
Did you actually test that situation (raising 400 events) and did the event go of 400 times when implementing INotifyPropertyChanged? If so you could use the virtualizing stackpanel so not all Staff objects are instantiated (and thus bound)Cabezon
In my case each staff member is represented by a small box on the screen and all of them are visible all of the time.Greenroom
Raising the 400 events is what I am doing now. We can't debug bindings currently (next version apparently) so I can't tell what the results is.Greenroom
Speed is not necessarily an issue. Remember that my SecondsAlive is just an example and I am talking about the general case of tooltips. It could be an issue if there is a large number of updates each raising 400 events. Part of the problem is the amount of code that I need to write. For example, in one place in my app I have a staff object with approx 30 properties and a I have added a tooltip property that is based on many of these 30 properties. For every property on staff I need to raise a propertychanged event for the tooltip also. This seems like a lot of trouble to me.Greenroom
If I could just get the tooltip to call the binding each time it would make everything a lot simpler in a LOT of places within the app. I did some testing and it looks like a new tooltip object is created in some cases but in other cases it is reused. The wierd thing is that it must be caching the results somewhere outside the tooltip object because even when a new tooltip object is created it keeps the old values (if property changed is not raised). I just need to get the tooltip to discard this cache.Greenroom
G
42

OMG!!! I have finally found the solution to this problem!!! This has been bugging me for months. I'm not surprised no one answered this because the code I typed out at the top actually DIDN'T show the problem I was trying to reproduce, in fact it showed the solution. The answer is that if you define your tooltip like this

    <StackPanel.ToolTip>
        <TextBlock Text="{Binding SecondsAlive}"/>
    </StackPanel.ToolTip>

Then everything works just fine and dandy and there is no need to raise a propertyChanged event on "SecondsAlive". The framework will call the SecondsAlive property every time the tooltip is shown. The problem comes when you define your tooltip like this:

    <StackPanel.ToolTip>
        <ToolTip>
            <TextBlock Text="{Binding SecondsAlive}"/>
        </ToolTip>
    </StackPanel.ToolTip>

Having the extra tooltip tag in there makes sense, surely you need to create a tooltip object to assign it to the tooltip property but this is incorrect. What you are assigning to the tooltip property is actually the content of the tooltip. I was assuming you needed to give it controls such as textblock and image to display but you can pass in anything and it will display the content just like a content control. Seeing it inherits from content control this makes sense :-) It all seems obvious once you know :-)

Thanks everyone for looking at this.

PS. I found an additional problem in that the next logical step in simplifying code is to just assign text straight to the tooltip like this (assuming your tooltip is plain text):

 <TextBlock Text="{Binding Path=StaffName}" ToolTip="{Binding Path=StaffToolTip}"/>

This also causes the original problem I was having. This makes sense because the results of the property StaffToolTip get assigned to the tooltip property and never get called again. However, it doesn't quite make sense why then assigning a TextBlock to the tooltip property actually solves the problem.

Greenroom answered 6/9, 2011 at 0:14 Comment(5)
I just found that taking out the <ToolTip> tags causes a bug that can be seen at these 2 links. So it looks like i'm back to my original method and have to raise property changed everywhere. Bummer. social.msdn.microsoft.com/Forums/en-US/wpf/thread/… and here connect.microsoft.com/VisualStudio/feedback/details/496959/…Greenroom
That makes sense. I also bumped in the same problem. I thought that there's a problem with my viewmodel. Thanks.Turnedon
I did this for a Slider.ToolTip element and it works fine it seems, I don't see the bug you mention. I'm using .NET 4 but the links say the bug isn't fixed there, so I don't know why I'm not seeing it.Nogood
Removing <ToolTip Placement="Bottom"> allowed my tooltip to update. Now I am looking for a way to set the placement. ( And I am sure I will find it using the usual searches. )Prevent
This worked well, I'm using Visual Studio 2015+Update 3. I never would have discovered the solution without this hint!!Agrestic
P
3

Although this is an old question.

In this case:

    <StackPanel.ToolTip>
        <ToolTip>
            <TextBlock Text="{Binding SecondsAlive}"/>
        </ToolTip>
    </StackPanel.ToolTip>

The ToolTip control is hosted by an isolate HWND (a.k.a, a native window). It should have its own DataContext, the correct binding expresion should be like:

    <StackPanel.ToolTip>
        <ToolTip 
            DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
            <TextBlock Text="{Binding SecondsAlive}"/>
        </ToolTip>
    </StackPanel.ToolTip>
Prussianism answered 18/9, 2021 at 15:56 Comment(0)
T
1

In this particular case there is a cool trick you can use

Seconds Alive Now = Seconds Alive originally + Elapsed Time

You can bind to the Elapsed Time property and specify a converter that adds the initial value to it. That way you only need to raise 1 event and the tooltips would all be updated.

Edit: You can add the ElapsedTime property (with INotifyPropertyChanged) to many places -- one logical place could be to the collection that is storing your Staff objects

Edit: You would also need to bind each tooltip to the shared ElapsedTime property rather than the SecondsAlive property

Thiel answered 11/7, 2011 at 4:28 Comment(4)
Hi Pickles, thanks for the answer. Unfortunately this was just an example I used and I don't actually need to know the number of seconds someone has been alive. I just want this for general purpose situation. Although ..... if your sample code works then it must trick the tooltip into doing what I want to do (call the property every time it is shown). Maybe if I just have a converter that does nothing it will solve the problem. Will give it a try in sec.Greenroom
My idea didn't work. Same issue, you still need a propertychanged eventGreenroom
I don't quite get where ElapsedTime comes from in you example. From window the controls are hosted on?Greenroom
Thanks for the reply but unfortunately this doesn't really answer the question. The SecondsAlive was only an example so in order to get this to work I would need to create a fake property that all tooltips were based on and raise a propertychanged on that property. It seems like a bit of a hack to me. All I'm trying to do is avoid writing repetitive code that calls PropertyChanged("ToolTip") every time any property on any object in the app is updated. If I have 50 objects with 30 properties each then I need to raise this event in 1500 locations. Seems unnecessary to me.Greenroom
M
0

It's worth noting that the ToolTip appears to check your object that it's bound to for equality before reloading itself with the new data.

In my case I did an override of

public override bool Equals(object obj) 

and

public override int GetHashCode()

on a class with properties

public class MultipleNameObject { string Name, string[] OtherNames};

Unfortunatley I only did a string.Compare() on the Name property of the MultipleNameObject for equality purposes. The tool tip was supposed to display OtherNames in a ItemsControl, but was not updating if Name was equal on the previous MultipleNameObject that the mouse hovered been over on the grid, even if the OtherNames were different.

[edit] Running with debug enabled confirms that the GetHashCode() override of my object was being used by the ToolTip to decide whether to grab the new data back. Fixing that to take the string[] OtherNames into account fixed the problem.

Melise answered 8/3, 2016 at 21:58 Comment(2)
In terms of the original question, I guess that could mean that the tool tip probably won't update when the object changes because it will consider it to be Equal to itself even if some of the internals have changed unless OP overrides (correctly) the compareTo an Equals of the bound object. This is conjecture, but it's what worked for me .Melise
I did try this, but no luck. The answer from @Greenroom worked. The key is to override the template for whatever you are working on, then carefully use the correct XAML to avoid taking a once-off copy of the content.Agrestic
A
0

This example shows how to add a tooltip to a grid that recalculates the tooltip on demand, when the user hovers over a cell in the grid.

This is useful if you have a huge grid with 10,000 items, and you want to update the grid continuously - but you don't want to update the tooltips continuously, as this is time consuming.

This example is for Infragistics, but the principle applies equally to other fine libraries such as DevExpress.

Xaml

<ig:TemplateColumn Key="ColumnKey">
    <ig:TemplateColumn.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="Header"></TextBlock>
        </DataTemplate>
    </ig:TemplateColumn.HeaderTemplate>
    <ig:TemplateColumn.ItemTemplate>
        <DataTemplate>
            <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <StackPanel.ToolTip>
                    <TextBlock Text="{Binding ToolTip}"/>
                </StackPanel.ToolTip>
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseEnter" >
                        <i:InvokeCommandAction Command="{Binding ToolTipHoverRefreshCommand}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </StackPanel>
        </DataTemplate>
    </ig:TemplateColumn.ItemTemplate>
</ig:TemplateColumn>

ViewModel

public ICommand ToolTipHoverRefreshCommand => new DelegateCommand(() =>
{
    this.OnPropertyChanged(this.ToolTip);
});  

public string ToolTip
{
    get
    {
        return DateTime.Now.ToString();
    }
    set
    {
        this.OnPropertyChanged(nameof(this.ToolTip));
    }
}
Agrestic answered 18/11, 2016 at 14:15 Comment(0)
S
0

I was having the same problem of it not updating. I found the solution to be adding the controls to a ToolTip template:

<DataTemplate DataType={x:Type Staff}>
    <StackPanel Orientation="Horizontal">
        <TextBlock Text={Binding FirstName}/>
        <TextBlock Text=" ">
        <TextBlock Text={Binding FamilyName}/>
        <StackPanel.ToolTip>
            <Tooltip>
                <ToolTip.Template>
                    <ControlTemlate>
                        <TextBlock Text={Binding SecondsAlive}/>
                    </ControlTemplate>
                </ToolTip.Template>
            </Tooltip> 
        </StackPanel.ToolTip>
     </StackPanel>
</DataTemplate>

I don't quite understand why this is needed or why this makes it behave differently but it fixed the problem.

Soliz answered 17/11, 2020 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.