4

我在 C# Net 2.0 Windows Forms 中有一个 ListView,其中大约十列在应用程序启动时填充了数据。数据很大,因此无法在中间快速重新加载。我的 ListView 具有详细信息视图,并允许用户分别调整每列的大小。

用户应能够一次隐藏十列中的任何一列或多列,并在非特定行中随时再次取消隐藏列。隐藏列时不删除数据。

以下是我尝试过但结果并不令人满意的事情:

1) 调整大小为 0 的列会使其在某种程度上消失,直到用户开始使用这些列。用户会意外地调整它们的大小,因为在我的 ListView 中有允许用户手动调整每列大小的选项。

2)只删除列会出现以下问题:当我尝试添加列时,列不记得它的位置和大小。位置是主要问题,我将尝试解释原因。如果我的用户决定先隐藏“第 2 列”,然后再隐藏“第 3 列”,然后用户在 2 之前取消隐藏 3,则“索引 2”不存在,因此我无法在第 3 号索引处插入,并且出现异常。即使我在删除之前记得索引位置,我也不能简单地将列放回该索引,因为我不知道之前的列是否已经隐藏,或者之前和之后缺少哪一列或隐藏与否。所以场景可能是这样的:1显示,2隐藏,3也隐藏,4显示,5隐藏,6隐藏,7隐藏,8显示,9隐藏,10隐藏。

可能的解决方案“1)”和“2)”在场景中被自动排除。甚至将列的宽度设置为零更好,但是由于我的用户可以随时根据需要调整列的大小,因此无法对用户隐藏它的大小。用户将通过调整大小来取消隐藏它,我的系统会认为它仍然是隐藏的等等。如果隐藏的列可以只是“调整大小”或者我们如何命名它,它看起来并不专业。

有人有更好的主意吗?我想知道为什么 listView 列没有“可见”或“隐藏”属性?如果有人之前已经这样做了,请发布解决方案。

我必须补充一点,当添加数据时,我使用列表视图中所有列的自动调整大小。出于这个原因,下面的答案不起作用。resize 事件无法检测到 -1 的宽度。添加数据时,宽度为 0 的“所有不可见列”将重新调整大小。由于列表视图会删除与列长度重叠的数据,因此我需要永久自动调整它的大小。Explorer 没有这个问题,因为它使列适合数据的长度。C#没有这样高级的listview,这里我每次添加数据时都必须将列设置为-1。在这个概念中, column.width = 0 用于隐藏列的想法不起作用。

4

2 回答 2

8

OK,你的问题其实是如何隐藏一个ListView Column?. 网上很多人都问过这个问题。我试过搜索很多,但找不到任何东西。我最终得到了唯一的解决方案:将列宽设置为零。这将在这里使用一些技巧:

//This will try hiding the column at index 1
listView1.Columns[1].Width = 0;
//ColumnWidthChanging event handler of your ListView
private void listView1_ColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e){
  if(e.ColumnIndex == 1){
     e.Cancel = true;
     e.NewWidth = 0;
  }
}

它工作得几乎完美。但是,当用户将鼠标移到pipe隐藏列的位置时,会出现一个Cursor指示符来通知用户,例如There is a Zero-width column here,只需按住鼠标并拖动即可调整其大小。当然,用户不能从调整它的大小,因为我们Cancel做了它NewWidth = 0(就像上面的代码一样)。但是Cursor通知这样的操作有点讨厌,这里是演示问题的屏幕截图:

在此处输入图像描述

解决这个问题并不容易。至少我是这样的感觉。我想到了这个似乎可以正常工作的解决方案。这个想法是我们必须检测鼠标是否在隐藏列的管道附近,我们必须设置Cursor = Cursors.Arrow. 这是我认为对你很有用的整个课程:

public class CustomListView : ListView
{
    [DllImport("user32")]
    private static extern bool EnumChildWindows(IntPtr parentHwnd, EnumChildProc proc, object lParam);
    delegate bool EnumChildProc(IntPtr childHwnd, object lParam);
    public CustomListView()
    {
        VisibleChanged += (s, e) =>
        {
            if (Visible && headerHandle == IntPtr.Zero&&!DesignMode)
            {
                EnumChildWindows(Handle, EnumChild, null);
                headerProc = new HeaderProc(this);
                headerProc.AssignHandle(headerHandle);
            }
        };
        columnPipeLefts[0] = 0;
    }      
    //Save the Handle to the Column Headers, a ListView has only child Window which is used to render Column headers  
    IntPtr headerHandle;
    //This is used use to hook into the message loop of the Column Headers
    HeaderProc headerProc;        
    private bool EnumChild(IntPtr childHwnd, object lParam)
    {
        headerHandle = childHwnd;
        return true;
    }
    //Updated code
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x101e&&hiddenColumnIndices.Contains(m.WParam.ToInt32()))//WM_SETCOLUMNWIDTH = 0x101e
        {                
            if(m.LParam.ToInt32() > 0) hiddenColumnWidths[m.WParam.ToInt32()] = m.LParam.ToInt32();                    
            return;//Discard the message changing hidden column width so that it won't be shown again.                
        }
        base.WndProc(ref m);
    }
    //Save the column indices which are hidden
    List<int> hiddenColumnIndices = new List<int>();
    //Save the width of hidden columns
    Dictionary<int, int> hiddenColumnWidths = new Dictionary<int, int>();
    //Save the Left (X-Position) of the Pipes which separate Column Headers.
    Dictionary<int, int> columnPipeLefts = new Dictionary<int, int>();
    protected override void OnColumnWidthChanging(ColumnWidthChangingEventArgs e)
    {
        if (hiddenColumnIndices.Contains(e.ColumnIndex))
        {
            e.Cancel = true;
            e.NewWidth = 0;
        }
        base.OnColumnWidthChanging(e);
    }
    //We need to update columnPipeLefts whenever the width of any column changes
    protected override void OnColumnWidthChanged(ColumnWidthChangedEventArgs e)
    {            
        base.OnColumnWidthChanged(e);
        UpdateColumnPipeLefts(Columns[e.ColumnIndex].DisplayIndex + 1);
    }
    int index = -1;
    protected override void OnColumnReordered(ColumnReorderedEventArgs e)
    {
        int i = Math.Min(e.NewDisplayIndex, e.OldDisplayIndex);
        index = index != -1 ? Math.Min(i + 1, index) : i + 1;            
        base.OnColumnReordered(e);                                
    }
    //This is used to update the columnPipeLefts every reordering columns or resizing columns.
    private void UpdateColumnPipeLefts(int fromIndex)
    {
        int w = fromIndex > 0 ? columnPipeLefts[fromIndex - 1] : 0;
        for (int i = fromIndex; i < Columns.Count; i++)
        {
            w += i > 0 ? Columns.OfType<ColumnHeader>().Where(k=>k.DisplayIndex == i - 1).Single().Width : 0;
            columnPipeLefts[i] = w;
        }
    }
    //This is used to hide a column with ColumnHeader passed in
    public void HideColumn(ColumnHeader col)
    {
        if (!hiddenColumnIndices.Contains(col.Index))
        {                
            hiddenColumnWidths[col.Index] = col.Width;//Save the current width to restore later                
            col.Width = 0;//Hide the column
            hiddenColumnIndices.Add(col.Index);
        }
    }
    //This is used to hide a column with column index passed in
    public void HideColumn(int columnIndex)
    {
        if (columnIndex < 0 || columnIndex >= Columns.Count) return;
        if (!hiddenColumnIndices.Contains(columnIndex))
        {
            hiddenColumnWidths[columnIndex] = Columns[columnIndex].Width;//Save the current width to restore later                
            Columns[columnIndex].Width = 0;//Hide the column
            hiddenColumnIndices.Add(columnIndex);
        }
    }
    //This is used to show a column with ColumnHeader passed in
    public void ShowColumn(ColumnHeader col)
    {
        hiddenColumnIndices.Remove(col.Index);
        if(hiddenColumnWidths.ContainsKey(col.Index))
           col.Width = hiddenColumnWidths[col.Index];//Restore the Width to show the column
        hiddenColumnWidths.Remove(col.Index);
    }
    //This is used to show a column with column index passed in
    public void ShowColumn(int columnIndex)
    {
        if (columnIndex < 0 || columnIndex >= Columns.Count) return;
        hiddenColumnIndices.Remove(columnIndex);
        if(hiddenColumnWidths.ContainsKey(columnIndex))
           Columns[columnIndex].Width = hiddenColumnWidths[columnIndex];//Restore the Width to show the column            
        hiddenColumnWidths.Remove(columnIndex);
    }
    //The helper class allows us to hook into the message loop of the Column Headers
    private class HeaderProc : NativeWindow
    {
        [DllImport("user32")]
        private static extern int SetCursor(IntPtr hCursor);
        public HeaderProc(CustomListView listView)
        {
            this.listView = listView;
        }
        bool mouseDown;
        CustomListView listView;
        protected override void WndProc(ref Message m)
        {                
            if (m.Msg == 0x200 && listView!=null && !mouseDown)
            {
                int x = (m.LParam.ToInt32() << 16) >> 16;
                if (IsSpottedOnAnyHiddenColumnPipe(x)) return;
            }
            if (m.Msg == 0x201) { 
                mouseDown = true;
                int x = (m.LParam.ToInt32() << 16) >> 16;
                IsSpottedOnAnyHiddenColumnPipe(x);
            }
            if (m.Msg == 0x202) mouseDown = false;
            if (m.Msg == 0xf && listView.index != -1 && MouseButtons == MouseButtons.None) { //WM_PAINT = 0xf
                listView.UpdateColumnPipeLefts(listView.index); 
                listView.index = -1; 
            };
            base.WndProc(ref m);
        }
        private bool IsSpottedOnAnyHiddenColumnPipe(int x)
        {                
            foreach (int i in listView.hiddenColumnIndices.Select(j=>listView.Columns[j].DisplayIndex))
            {
                if (x > listView.columnPipeLefts[i] - 1 && x < listView.columnPipeLefts[i] + 15)
                {
                    SetCursor(Cursors.Arrow.Handle);
                    return true;
                }
            }
            return false;
        }
    }
}
于 2013-08-08T01:21:52.497 回答
0

请在此处替换第一行代码 - 它更优雅地使用内部列表视图 API 来获取标题句柄 -旧代码崩溃 @EnumChildWindows):

        [DllImport("user32.dll", EntryPoint = "SendMessage")]
    private static extern IntPtr SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
    const int LVM_FIRST = 0x1000;
    const int LVM_GETHEADER = (LVM_FIRST + 31);

    public CustomListView()
    {
        VisibleChanged += (s, e) =>
        {
            if (Visible && headerHandle == IntPtr.Zero && !DesignMode)
            {
                IntPtr hwnd = SendMessage(this.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
                if (hwnd != null)
                {
                    headerProc = new HeaderProc(this);
                    headerHandle = hwnd;
                    headerProc.AssignHandle(hwnd);
                }
            }
        };

        columnPipeLefts[0] = 0;
    }
于 2016-05-12T12:51:20.390 回答