【问题标题】:How can I set different Tooltip text for each item in a listbox?如何为列表框中的每个项目设置不同的工具提示文本?
【发布时间】:2010-09-16 14:16:26
【问题描述】:

我有一个数据绑定到对象集合的列表框。列表框被配置为显示每个对象的标识符属性。我想显示一个工具提示,其中包含特定于悬停在列表框中的项目的信息,而不是整个列表框的一个工具提示。

我在 WinForms 中工作,感谢一些有用的博客文章整理了一个非常好的解决方案,我想分享。

我很想看看这个问题是否有其他优雅的解决方案,或者如何在 WPF 中完成。

【问题讨论】:

    标签: c# .net winforms winapi listbox


    【解决方案1】:

    为了解决这个问题,必须解决两个主要的子问题:

    1. 确定悬停在哪个项目上
    2. 当用户将鼠标悬停在一个项目上,然后在列表框中移动光标并悬停在另一个项目上时,触发 MouseHover 事件。

    第一个问题很容易解决。通过在 MouseHover 的处理程序中调用类似以下的方法,您可以确定悬停在哪个项目上:

    private ITypeOfObjectsBoundToListBox DetermineHoveredItem()
    {
        Point screenPosition = ListBox.MousePosition;
        Point listBoxClientAreaPosition = listBox.PointToClient(screenPosition);
    
        int hoveredIndex = listBox.IndexFromPoint(listBoxClientAreaPosition);
        if (hoveredIndex != -1)
        {
            return listBox.Items[hoveredIndex] as ITypeOfObjectsBoundToListBox;
        }
        else
        {
            return null;
        }
    }
    

    然后根据需要使用返回值设置工具提示。

    第二个问题是通常鼠标悬停事件不会再次触发,直到光标离开控件的客户区域然后回来。

    您可以通过包装TrackMouseEvent Win32API 调用来解决此问题。
    在下面的代码中,ResetMouseHover 方法封装了 API 调用以获得所需的效果:重置控制悬停事件何时触发的底层计时器。

    public static class MouseInput
    {
        // TME_HOVER
        // The caller wants hover notification. Notification is delivered as a 
        // WM_MOUSEHOVER message.  If the caller requests hover tracking while 
        // hover tracking is already active, the hover timer will be reset.
    
        private const int TME_HOVER = 0x1;
    
        private struct TRACKMOUSEEVENT
        {
            // Size of the structure - calculated in the constructor
            public int cbSize;
    
            // value that we'll set to specify we want to start over Mouse Hover and get
            // notification when the hover has happened
            public int dwFlags;
    
            // Handle to what's interested in the event
            public IntPtr hwndTrack;
    
            // How long it takes for a hover to occur
            public int dwHoverTime;
    
            // Setting things up specifically for a simple reset
            public TRACKMOUSEEVENT(IntPtr hWnd)
            {
                this.cbSize = Marshal.SizeOf(typeof(TRACKMOUSEEVENT));
                this.hwndTrack = hWnd;
                this.dwHoverTime = SystemInformation.MouseHoverTime;
                this.dwFlags = TME_HOVER;
            }
        }
    
        // Declaration of the Win32API function
        [DllImport("user32")]
        private static extern bool TrackMouseEvent(ref TRACKMOUSEEVENT lpEventTrack);
    
        public static void ResetMouseHover(IntPtr windowTrackingMouseHandle)
        {
            // Set up the parameter collection for the API call so that the appropriate
            // control fires the event
            TRACKMOUSEEVENT parameterBag = new TRACKMOUSEEVENT(windowTrackingMouseHandle);
    
            // The actual API call
            TrackMouseEvent(ref parameterBag);
        }
    }
    

    有了包装器,您只需在 MouseHover 处理程序结束时调用 ResetMouseHover(listBox.Handle),即使光标停留在控件范围内,悬停事件也会再次触发。

    我确信这种方法,将所有代码粘贴在 MouseHover 处理程序中必然会导致触发比实际需要更多的 MouseHover 事件,但它会完成工作。任何改进都非常受欢迎。

    【讨论】:

    • 感谢@reformed 的修改建议。审稿人拒绝了它,但我对你觉得令人困惑的语言进行了调整。
    【解决方案2】:

    使用 MouseMove 事件,您可以跟踪鼠标所在项目的索引,并将其存储在一个变量中,该变量在 MouseMove 之间保持其值。每次触发 MouseMove 时,它​​都会检查索引是否已更改。如果是这样,它将禁用工具提示,更改此控件的工具提示文本,然后重新激活它。

    下面是一个示例,其中 Car 类的单个属性显示在 ListBox 中,但是当将鼠标悬停在任何一行上时会显示完整信息。为了使这个示例正常运行,您只需要一个名为 lstCars 的 ListBox 以及一个 MouseMove 事件和一个在 WinForm 上名为 tt1 的 ToolTip 文本组件。

    汽车类的定义:

        class Car
        {
            // Main properties:
            public string Model { get; set; }
            public string Make { get; set; }
            public int InsuranceGroup { get; set; }
            public string OwnerName { get; set; }
            // Read only property combining all the other informaiton:
            public string Info { get { return string.Format("{0} {1}\nOwner: {2}\nInsurance group: {3}", Make, Model, OwnerName, InsuranceGroup); } }
        }
    

    表单加载事件:

        private void Form1_Load(object sender, System.EventArgs e)
        {
            // Set up a list of cars:
            List<Car> allCars = new List<Car>();
            allCars.Add(new Car { Make = "Toyota", Model = "Yaris", InsuranceGroup = 6, OwnerName = "Joe Bloggs" });
            allCars.Add(new Car { Make = "Mercedes", Model = "AMG", InsuranceGroup = 50, OwnerName = "Mr Rich" });
            allCars.Add(new Car { Make = "Ford", Model = "Escort", InsuranceGroup = 10, OwnerName = "Fred Normal" });
    
            // Attach the list of cars to the ListBox:
            lstCars.DataSource = allCars;
            lstCars.DisplayMember = "Model";
        }
    

    工具提示代码(包括创建名为 hoveredIndex 的类级变量):

            // Class variable to keep track of which row is currently selected:
            int hoveredIndex = -1;
    
            private void lstCars_MouseMove(object sender, MouseEventArgs e)
            {
                // See which row is currently under the mouse:
                int newHoveredIndex = lstCars.IndexFromPoint(e.Location);
    
                // If the row has changed since last moving the mouse:
                if (hoveredIndex != newHoveredIndex)
                {
                    // Change the variable for the next time we move the mouse:
                    hoveredIndex = newHoveredIndex;
    
                    // If over a row showing data (rather than blank space):
                    if (hoveredIndex > -1)
                    {
                        //Set tooltip text for the row now under the mouse:
                        tt1.Active = false;
                        tt1.SetToolTip(lstCars, ((Car)lstCars.Items[hoveredIndex]).Info);
                        tt1.Active = true;
                    }
                }
            }
    

    【讨论】:

    • 代码中的“.InsuranceGroup”是什么?我在互联网上找不到任何相关信息,而且我的 Visual Studio 也不知道这个词。
    • Insurance group 只是我想展示的汽车类的一个属性。在英国,保险组表明保险费用是便宜还是昂贵。车越便宜,投保数量越少,投保数量越大越贵。我已经扩展了我上面的解释,使它成为一个完整的例子。
    • 它对我来说很完美,但可能缺少“ToolTip tt1 = new ToolTip();”作为代码中的类字段?我不得不添加它。
    • 好点法尔科。在 Visual Studio 中使用设计器时,我通过从工具箱中添加工具提示来创建 tt1。在您自己的代码中创建它也可以很好地工作。
    【解决方案3】:

    我认为最好的选择是使用 一个数据模板。所以你可以这样做:

    <ListBox Width="400" Margin="10" 
             ItemsSource="{Binding Source={StaticResource myTodoList}}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=TaskName}" 
                           ToolTipService.ToolTip="{Binding Path=TaskName}"/>
            </DataTemplate>
         </ListBox.ItemTemplate>
    </ListBox>
    

    当然,您可以将 ItemsSource 绑定替换为您的绑定源,并将绑定 Path 部分替换为您实际想要显示的列表中对象的任何公共属性。 更多详情请访问msdn

    【讨论】:

    • 这个问题针对的是 WinForms,而不是 WPF。
    【解决方案4】:

    您可以在 WinForms 中使用这个使用 ListBox 的 onMouseMove 事件的简单代码:

    private void ListBoxOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
    {
            var listbox = sender as ListBox;
            if (listbox == null) return;
    
            // set tool tip for listbox
            var strTip = string.Empty;
            var index = listbox.IndexFromPoint(mouseEventArgs.Location);
    
            if ((index >= 0) && (index < listbox.Items.Count))
                strTip = listbox.Items[index].ToString();
    
            if (_toolTip.GetToolTip(listbox) != strTip)
            {
                _toolTip.SetToolTip(listbox, strTip);
            }
    }
    

    当然,您必须在构造函数或一些 init 函数中初始化 ToolTip 对象:

    _toolTip = new ToolTip
    {
                AutoPopDelay = 5000,
                InitialDelay = 1000,
                ReshowDelay = 500,
                ShowAlways = true
    };
    

    享受吧!

    【讨论】:

    • 对我来说是完美的解决方案,我已经稍微更改了您的代码以重置弹出延迟,使用您当前的代码,一旦显示工具提示,当我切换项目时它不会进入冷却时间。因此,我更改了重新设置部分,在使用 setToolTip 方法设置工具提示文本之前,我处理了旧的并重新设置了它
    【解决方案5】:

    这是一个使用 ListBox 创建一组 RadioButtons 的 Style。一切都注定要进行 MVVM-ing。 MyClass 包含两个字符串属性:MyName 和 MyToolTip。这将显示 RadioButtons 列表,包括正确的 ToolTip-ing。该线程感兴趣的是靠近底部的 ToolTip 的 Setter,使其成为一个全 Xaml 解决方案。

    示例用法:

    ListBox Style="{StaticResource radioListBox}" ItemsSource="{Binding MyClass}" SelectedValue="{Binding SelectedMyClass}"/>

    风格:

        <Style x:Key="radioListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="Margin" Value="5" />
        <Setter Property="Background" Value="{x:Null}" />
        <Setter Property="ItemContainerStyle">
            <Setter.Value>
                <Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="ListBoxItem">
                                <Grid Background="Transparent">
                                    <RadioButton Focusable="False" IsHitTestVisible="False" IsChecked="{TemplateBinding IsSelected}" Content="{Binding MyName}"/>
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                    <Setter Property="ToolTip" Value="{Binding MyToolTip}" />
                </Style>
            </Setter.Value>
        </Setter>
    </Style>
    

    【讨论】:

    • 这个问题针对的是 WinForms,而不是 WPF。
    • 当我发布这个问题时,StackOverflow 将自己描述为一个围绕问题组织的 wiki。实际上,我的问题是“我很想看看是否有任何其他优雅的解决方案可以解决这个问题,或者如何在 WPF 中完成。”我无法评估此 WPF 解决方案的正确性,但无需仅仅因为它不是 WinForms 而投反对票
    【解决方案6】:

    使用title属性,我们可以为列表框中的每个列表项设置工具提示。

    为列表框中的所有项目循环。

    ListItem li = new ListItem("text","key");
    li.Attributes.Add("title","tool tip text");
    

    希望这会有所帮助。

    【讨论】:

    • 这仅在打开选择列表框时显示工具提示。具有讽刺意味的是,这对我来说很好,我已经使用了这个解决方案,非常感谢。
    【解决方案7】:

    使用onmouseover,您可以遍历列表中的每一项并显示ToolTip

    onmouseover="doTooltipProd(event,'');
    
    function doTooltipProd(e,tipObj)
    {
    
        Tooltip.init();
          if ( typeof Tooltip == "undefined" || !Tooltip.ready ) {
          return;
          }
          mCounter = 1;
       for (m=1;m<=document.getElementById('lobProductId').length;m++) {
    
        var mCurrent = document.getElementById('lobProductId').options[m];
            if(mCurrent != null && mCurrent != "null") {
                if (mCurrent.selected) {
                    mText = mCurrent.text;
                    Tooltip.show(e, mText);
                    }
            }   
        }   
    }
    

    【讨论】:

    • 这是javascript吗?很确定问题是关于 C#
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-08
    相关资源
    最近更新 更多