【问题标题】:WPF ToolTip does not updateWPF 工具提示不更新
【发布时间】:2011-10-02 11:43:15
【问题描述】:

假设我有一个代表员工的简单类

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

我有一个员工数据模板

<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>

然后我在 ListBox 中显示一大堆员工

myListBox.ItemsSource = GetAllStaff();

相当标准的东西。我遇到的问题是显示某人还活着的秒数的工具提示没有得到更新。当您第一次将鼠标悬停在工作人员上时,它可以正常工作,但从那时起,它会永远保持该值。我可以实施 INotifyPropertyChanged 来解决这个问题,但是每当 SecondsAlive 发生变化时,对每个员工都这样做似乎有点过头了。假设列表中有 400 名员工,那么即使用户可能永远不会查看另一个工具提示,我也必须引发 400 个事件。我想要的是让工具提示每次显示时都请求 SecondsAlive 属性。这可能吗?

请注意,这只是一个示例,我不需要知道我的员工已经活了多少秒 :-) 但我有同样的问题,我需要为工具提示提出 400 次左右可能有人不会看。

【问题讨论】:

  • 您是否真的测试过这种情况(引发 400 个事件),并且在实施 INotifyPropertyChanged 时事件发生了 400 次?如果是这样,您可以使用虚拟化堆栈面板,因此并非所有 Staff 对象都被实例化(并因此绑定)
  • 在我的例子中,每个工作人员都由屏幕上的一个小框表示,并且他们都始终可见。
  • 提高 400 个事件是我现在正在做的事情。我们目前无法调试绑定(显然是下一个版本)所以我不知道结果是什么。
  • 速度不一定是问题。请记住,我的 SecondsAlive 只是一个示例,我说的是工具提示的一般情况。如果有大量更新每次引发 400 个事件,这可能是一个问题。部分问题是我需要编写的代码量。例如,在我的应用程序的一个地方,我有一个包含大约 30 个属性的人员对象,并且我添加了一个基于这 30 个属性中的许多属性的工具提示属性。对于工作人员的每个属性,我还需要为工具提示引发一个 propertychanged 事件。这对我来说似乎很麻烦。
  • 如果我每次都能获得工具提示来调用绑定,这将使应用程序中许多地方的一切变得更加简单。我做了一些测试,它看起来像在某些情况下创建了一个新的工具提示对象,但在其他情况下它被重用了。奇怪的是,它必须将结果缓存在工具提示对象之外的某个地方,因为即使创建了新的工具提示对象,它也会保留旧值(如果未引发属性更改)。我只需要获取工具提示即可丢弃此缓存。

标签: c# .net wpf binding


【解决方案1】:

在这种特殊情况下,您可以使用一个很酷的技巧

现在存活的秒数 = 原来的存活秒数 + 已用时间

您可以绑定到 Elapsed Time 属性并指定一个将初始值添加到该属性的转换器。这样你只需要引发 1 个事件,工具提示就会全部更新。

编辑:您可以将 ElapsedTime 属性(使用 INotifyPropertyChanged)添加到许多地方——一个合乎逻辑的地方可以是存储您的 Staff 对象的集合

编辑:您还需要将每个工具提示绑定到共享的 ElapsedTime 属性而不是 SecondsAlive 属性

【讨论】:

  • 嗨泡菜,感谢您的回答。不幸的是,这只是我使用的一个例子,我实际上并不需要知道某人还活着的秒数。我只是想要这个用于通用情况。虽然.....如果您的示例代码有效,那么它必须欺骗工具提示做我想做的事情(每次显示时调用该属性)。也许如果我只有一个什么都不做的转换器,它将解决问题。过会试一试。
  • 我的想法没有奏效。同样的问题,你仍然需要一个 propertychanged 事件
  • 在您的示例中,我不太明白 ElapsedTime 的来源。从窗口托管控件?
  • 感谢您的回复,但不幸的是,这并不能真正回答问题。 SecondsAlive 只是一个例子,所以为了让它工作,我需要创建一个所有工具提示都基于的假属性,并在该属性上引发一个 propertychanged。对我来说,这似乎有点骇人听闻。我要做的就是避免编写重复的代码,每次更新应用程序中任何对象的任何属性时都会调用 PropertyChanged("ToolTip")。如果我有 50 个对象,每个对象有 30 个属性,那么我需要在 1500 个位置引发此事件。对我来说似乎没有必要。
【解决方案2】:

天哪!!!我终于找到了解决这个问题的方法!!!这一直困扰着我好几个月。我并不惊讶没有人回答这个问题,因为我在顶部输入的代码实际上并没有显示我试图重现的问题,实际上它显示了解决方案。答案是,如果你像这样定义你的工具提示

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

然后一切正常,花花公子,没有必要在“SecondsAlive”上引发 propertyChanged 事件。每次显示工具提示时,框架都会调用 SecondsAlive 属性。当您像这样定义工具提示时,问题就来了:

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

在那里有额外的工具提示标签是有意义的,当然您需要创建一个工具提示对象来将其分配给工具提示属性,但这是不正确的。您分配给 tooltip 属性的内容实际上是 tooltip 的 content。我假设您需要为其提供控件,例如要显示的文本块和图像,但您可以传入任何内容,它会像内容控件一样显示内容。看到它继承自内容控制,这是有道理的 :-) 一旦你知道,这一切似乎都很明显 :-)

感谢大家观看本文。

PS。我发现了一个额外的问题,简化代码的下一个逻辑步骤是直接将文本分配给工具提示,就像这样(假设你的工具提示是纯文本):

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

这也导致了我原来遇到的问题。这是有道理的,因为属性 StaffToolTip 的结果被分配给 tooltip 属性并且永远不会被再次调用。但是,为什么将 TextBlock 分配给 tooltip 属性实际上可以解决问题,这并不完全有道理。

【讨论】:

  • 我刚刚发现取出 标签会导致在这两个链接中可以看到的错误。所以看起来我回到了我原来的方法,不得不提高到处改变的财产。真可惜。 social.msdn.microsoft.com/Forums/en-US/wpf/thread/… 和这里 connect.microsoft.com/VisualStudio/feedback/details/496959/…
  • 这是有道理的。我也遇到了同样的问题。我认为我的视图模型有问题。谢谢。
  • 我为 Slider.ToolTip 元素做了这个,它似乎工作正常,我没有看到你提到的错误。我正在使用 .NET 4,但链接说该错误未修复,所以我不知道为什么我没有看到它。
  • 删除 允许我的工具提示更新。现在我正在寻找一种设置位置的方法。 (而且我相信我会使用通常的搜索找到它。)
  • 这很好用,我使用的是 Visual Studio 2015+Update 3。如果没有这个提示,我永远不会发现解决方案!!
【解决方案3】:

值得注意的是,在使用新数据重新加载自身之前,工具提示似乎会检查您绑定到的对象是否相等。

在我的情况下,我做了一个覆盖

public override bool Equals(object obj) 

public override int GetHashCode()

在具有属性的类上

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

不幸的是,出于相等目的,我只对 MultipleNameObject 的 Name 属性执行了 string.Compare()。工具提示应该在 ItemsControl 中显示 OtherNames,但如果 Name 与鼠标悬停在网格上的先前 MultipleNameObject 上的 Name 相同,则不会更新,即使 OtherNames 不同也是如此。

[编辑] 在启用调试的情况下运行确认工具提示正在使用 GetHashCode() 覆盖我的对象来决定是否获取新数据。修复它以考虑 string[] OtherNames 解决了问题。

【讨论】:

  • 就原始问题而言,我想这可能意味着当对象更改时工具提示可能不会更新,因为它会认为它等于自身,即使某些内部有除非 OP 覆盖(正确)绑定对象的 compareTo 和 Equals ,否则更改。这是猜想,但这对我有用。
  • 我确实试过这个,但没有运气。 @MikeKulls 的回答奏效了。关键是为您正在处理的任何内容覆盖模板,然后小心使用正确的 XAML 以避免获取内容的一次性副本。
【解决方案4】:

此示例展示了如何将工具提示添加到网格中,当用户将鼠标悬停在网格中的单元格上时,该工具提示会按需重新计算。

如果您有一个包含 10,000 个项目的巨大网格,并且您想要连续更新网格 - 但您不想连续更新工具提示,这非常有用,因为这很耗时。

此示例适用于 Infragistics,但该原理同样适用于 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>

视图模型

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

public string ToolTip
{
    get
    {
        return DateTime.Now.ToString();
    }
    set
    {
        this.OnPropertyChanged(nameof(this.ToolTip));
    }
}

【讨论】:

    【解决方案5】:

    我遇到了同样的问题,它没有更新。我找到了将控件添加到工具提示模板的解决方案:

    <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>
    

    我不太明白为什么需要这样做,或者为什么这会使其行为不同,但它解决了问题。

    【讨论】:

      【解决方案6】:

      虽然这是一个老问题。

      在这种情况下:

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

      ToolTip 控件由隔离 HWND(也称为本机窗口)托管。它应该有自己的DataContext,正确的绑定表达式应该是这样的:

          <StackPanel.ToolTip>
              <ToolTip 
                  DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
                  <TextBlock Text="{Binding SecondsAlive}"/>
              </ToolTip>
          </StackPanel.ToolTip>
      

      【讨论】:

        猜你喜欢
        • 2010-10-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-17
        • 2015-12-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多