【问题标题】:Drawing outside of column area in listview column header在列表视图列标题中的列区域之外绘制
【发布时间】:2009-09-16 14:22:43
【问题描述】:

是否可以所有者绘制列表视图的整个列标题部分? (包括列标题右侧的区域)? ListView 处于详细信息视图中。

这里的答案表示剩余空间可以连同最后一列标题一起绘制:http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework.windowsforms/topic32927.aspx

但它似乎根本不起作用 - 在标题区域之外没有绘制任何内容。

建议的解决方案基于在传递的边界之外绘制:

if (e.ColumnIndex == 3) //last column index
{
    Rectangle rc = new Rectangle(e.Bounds.Right, //Right instead of Left - offsets the rectangle
            e.Bounds.Top, 
            e.Bounds.Width, 
            e.Bounds.Height);

    e.Graphics.FillRectangle(Brushes.Red, rc);
}

可用 Graphics 实例的 ClipBounds 属性表示一个未绑定区域(从大的负数到大的正数)。但是在最后一列的列标题区域之外没有绘制任何内容。

有人对此有解决方案吗?

【问题讨论】:

    标签: c# winforms user-interface listview drawing


    【解决方案1】:

    我对 Jeffery Tan 在该帖子中的回答感到惊讶。他的解决方案无法工作,因为代码试图在标题控件客户区域之外绘制。在自定义绘图(以及所有者绘图)中使用的hDC 用于控件的客户区,因此不能用于在非客户区进行绘制。标题控件中最右侧列的右侧区域位于非客户区。所以你需要一个不同的解决方案。

    可能的解决方案

    1. 高科技且部分有效

    您可以使用GetDC() WinAPI 调用在客户区之外启用绘图:

    [System.Runtime.InteropServices.DllImport("user32")]
    private static extern IntPtr GetDC(IntPtr hwnd);
    [System.Runtime.InteropServices.DllImport("user32")]
    private static extern IntPtr ReleaseDC(IntPtr hwnd, IntPtr hdc);
    
    public static IntPtr GetHeaderControl(ListView list) {
        const int LVM_GETHEADER = 0x1000 + 31;
        return SendMessage(list.Handle, LVM_GETHEADER, 0, 0);
    }
    

    在您的列绘制事件处理程序中,您将需要这样的东西:

    if (e.ColumnIndex == 3) //last column index
    {
      ListView lv = e.Header.ListView;
      IntPtr headerControl = NativeMethods.GetHeaderControl(lv);
      IntPtr hdc = GetDC(headerControl);
      Graphics g = Graphics.FromHdc(hdc);
    
      // Do your extra drawing here
      Rectangle rc = new Rectangle(e.Bounds.Right, //Right instead of Left - offsets the rectangle
                e.Bounds.Top, 
                e.Bounds.Width, 
                e.Bounds.Height);
    
        e.Graphics.FillRectangle(Brushes.Red, rc);
    
      g.Dispose();
      ReleaseDC(headerControl, hdc);
    }
    

    但是这样做的问题是,由于您的绘图在客户区域之外,Windows 并不总是知道应该何时绘制它。所以它有时会消失,然后在Windows认为需要重新绘制标题时重新绘制。

    1. 技术含量低但丑

    向您的控件添加一个额外的空列,所有者可以随意绘制它,使其非常宽,并关闭水平滚动(可选)。

    我知道这很可怕,但您正在寻找建议 :)

    1. 最有效,但仍不完美

    使用ObjectListView。这个 .NET ListView 的包装器允许您将覆盖添加到列表中——覆盖可以在 ListView 中的任何位置绘制,包括标题。 【声明:我是ObjectListView的作者,但我还是觉得是最好的解决方案】

    public class HeaderOverlay : AbstractOverlay
    {
        public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
            if (olv.View != System.Windows.Forms.View.Details)
                return;
    
            Point sides = NativeMethods.GetColumnSides(olv, olv.Columns.Count-1);
            if (sides.X == -1)
                return;
    
            RectangleF headerBounds = new RectangleF(sides.Y, 0, r.Right - sides.Y, 20);
            g.FillRectangle(Brushes.Red, headerBounds);
            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Center;
            sf.LineAlignment = StringAlignment.Center;
            g.DrawString("In non-client area!", new Font("Tahoma", 9), Brushes.Black, headerBounds, sf);
        }
    }
    

    这给出了这个:

    [阅读这个答案,我认为这是一个努力尝试的例子 :) 希望你在这里找到一些有用的东西。]

    【讨论】:

    • 感谢您的出色回答。在我们的例子中,ObjectListView 将是一个矫枉过正。您的第一个解决方案就足够了,并且在我们的场景中运行良好。您在代码中有一个小错误 - 自然应该在 g 实例上绘制,而不是在原始 e.Graphics 上绘制: e.Graphics.FillRectangle(Brushes.Red, rc); //应该是g.FillRectangle 也缺少SendMessage方法导入: [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr handle, int messg, int wparam, int lparam);
    • OLV 很棒,但我相信您需要在 HeaderControl 中处理 WM_PAINT 和 WM_ERASEBKGND 才能完全自定义绘制标题。像这样的东西:codeproject.com/Articles/4243/…
    • 在 .NET Framework 4.5 和 Windows 10 上,它根本不起作用。它仅在您调整最后一列的大小时有效。只要您抬起鼠标按钮结束调整列标题的大小,Windows 就会将自己的背景样式绘制到(矩形)rc 区域。我认为这个问题的正确方法(和完美的解决方案)是使用 WndProc 方法。当我有一个体面的工作时,我会发布这个问题的更新。
    • 另一个简单的解决方案是添加代码以自动调整最后一列的大小以填充剩余空间
    【解决方案2】:

    我选择了@grammarian 的 2 号,因为不想与 InteropServices 混为一谈。此解决方案仅使用标准 .net。如上所述,在 Text 属性中放置一个没有任何内容的备用列(在下面的视频中,我使用“{filler}”只是为了帮助查看正在发生的事情)。然后使用各种事件处理程序来发挥他们的魔力。结果有一些粗糙的边缘,但我认为它非常及格。实战视频:

    https://youtu.be/987FtPE13KE

    及相关代码:

    Dim ResizingFillerColumn As Boolean = False
    Private Sub ListView_Resize(sender As Object, e As EventArgs) Handles ListView.Resize
        ResizeFillerColumn()
    End Sub
    
    Private Sub ResizeFillerColumn()
        Dim columnsWidth = 0
        For i = 0 To ListView.Columns.Count - 2
            columnsWidth += ListView.Columns(i).Width
        Next
        ResizingFillerColumn = True
        ListView.Columns(ListView.Columns.Count - 1).Width = ListView.Width - columnsWidth
        ResizingFillerColumn = False
    End Sub
    
    Private Sub ListView_ColumnReordered(sender As Object, e As ColumnReorderedEventArgs) Handles ListView.ColumnReordered
        Dim FillerColumnIndex = ListView.Columns.Count - 1
        If e.OldDisplayIndex = FillerColumnIndex Then e.Cancel = True
        If e.NewDisplayIndex = FillerColumnIndex Then e.Cancel = True
    End Sub
    
    Private Sub ListView_ColumnWidthChanged(sender As Object, e As ColumnWidthChangedEventArgs) Handles ListView.ColumnWidthChanged
        If ResizingFillerColumn Then Return
        ResizeFillerColumn()
    End Sub
    
    Private Sub ListView_ColumnWidthChanging(sender As Object, e As ColumnWidthChangingEventArgs) Handles ListView.ColumnWidthChanging
        If ResizingFillerColumn Then Return
        ResizeFillerColumn()
    End Sub
    

    【讨论】:

      猜你喜欢
      • 2020-07-08
      • 2012-07-10
      • 2010-09-07
      • 1970-01-01
      • 1970-01-01
      • 2015-11-10
      相关资源
      最近更新 更多