4

我在默认 ListView 中发现了一个不太有趣的错误(不是所有者绘制的!)。当项目不断添加到其中(通过使用示例)并且用户试图看到项目稍微远离所选项目(向上或向下滚动)时,它会严重闪烁。Timer

我在这里做错了吗?

这是一些重现它的代码:

  • 创建 WindowsFormsApplication1;
  • 将表格设置WindowState为最大化;
  • 穿上form timer1,设置Enabled为true;
  • 放在表格listView1上:

        this.listView1.Dock = System.Windows.Forms.DockStyle.Fill;
        this.listView1.View = System.Windows.Forms.View.Details;
        this.listView1.VirtualMode = true;
    
  • 添加一列;

  • 添加事件

        this.listView1.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.listView1_RetrieveVirtualItem);
    
  • 最后

    private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
    {
        e.Item = new ListViewItem(e.ItemIndex.ToString());
    }
    
    private void timer1_Tick(object sender, EventArgs e)
    {
        listView1.VirtualListSize++;
    }
    

现在运行它并等待列表视图上的滚动条出现(因为计时器将添加足够的项目),然后:

  • 选择列表视图中的第一个项目(使用鼠标或键),然后使用滚动条或鼠标滚轮向下滚动,以便所选项目将超出当前视图(向上)。向下滚动的次数越多,闪烁就越重!看看滚动条在做什么?!?!?

  • 如果向下滚动所选项目,则会出现类似的效果。


问题

我该如何处理?想法是有一种不断更新的日志窗口,可以停止自动滚动并向上/向下调查附近的事件。但有了这种kek 效应,这是不可能的!

4

4 回答 4

3

看起来问题与Selected/Focused组合有关(也许微软的人可以确认)。

这是一个可能的解决方法(它很脏,我说谎!):

    private void timer1_Tick(object sender, EventArgs e)
    {
        // before adding
        if (listView1.SelectedIndices.Count > 0)
        {
            if (!listView1.Items[listView1.SelectedIndices[0]].Bounds.IntersectsWith(listView1.ClientRectangle))
                listView1.TopItem.Focused = true;
            else
                listView1.Items[listView1.SelectedIndices[0]].Focused = true;
        }
        // add item
        listView1.VirtualListSize++;
    }

诀窍是在当前选定的项目离开时在添加新项目之前进行检查(这里是如何检查的主题)。如果项目不在,则TopItem暂时将焦点设置为当前(直到用户向后滚动,以便选定的项目将再次“可见”,这是它重新获得焦点的时候)。

于 2013-08-20T16:37:00.887 回答
1

在寻找解决方法后,我意识到一旦选择一个项目就无法防止闪烁。我尝试使用一些ListView消息但失败了。如果您想对此进行更多研究,我认为您应该注意一下LVM_SETITEMSTATE,也许还有其他一些消息。毕竟,我想到了这个想法,我们要阻止用户选择一个项目。所以要伪造一个选定的项目,我们必须像这样进行一些自定义绘图和伪造:

public class CustomListView : ListView
{
        public CustomListView(){
            SelectedIndices = new List<int>();
            OwnerDraw = true;
            DoubleBuffered = true;
        }
        public new List<int> SelectedIndices {get;set;}
        public int SelectedIndex { get; set; }
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x1000 + 43) return;//LVM_SETITEMSTATE                
            else if (m.Msg == 0x201 || m.Msg == 0x202)//WM_LBUTTONDOWN and WM_LBUTTONUP
            {
                int x = m.LParam.ToInt32() & 0x00ff;
                int y = m.LParam.ToInt32() >> 16;
                ListViewItem item = GetItemAt(x, y);
                if (item != null)
                {
                    if (ModifierKeys == Keys.Control)
                    {
                        if (!SelectedIndices.Contains(item.Index)) SelectedIndices.Add(item.Index);
                    }
                    else if (ModifierKeys == Keys.Shift)
                    {
                        for (int i = Math.Min(SelectedIndex, item.Index); i <= Math.Max(SelectedIndex, item.Index); i++)
                        {
                            if (!SelectedIndices.Contains(i)) SelectedIndices.Add(i);
                        }
                    }
                    else
                    {
                        SelectedIndices.Clear();
                        SelectedIndices.Add(item.Index);
                    }
                    SelectedIndex = item.Index;                        
                    return;
                }                    
            }
            else if (m.Msg == 0x100)//WM_KEYDOWN
            {
                Keys key = ((Keys)m.WParam.ToInt32() & Keys.KeyCode);
                if (key == Keys.Down || key == Keys.Right)
                {
                    SelectedIndex++;
                    SelectedIndices.Clear();
                    SelectedIndices.Add(SelectedIndex);
                }
                else if (key == Keys.Up || key == Keys.Left)
                {
                    SelectedIndex--;
                    SelectedIndices.Clear();
                    SelectedIndices.Add(SelectedIndex);
                }
                if (SelectedIndex == VirtualListSize) SelectedIndex = VirtualListSize - 1;
                if (SelectedIndex < 0) SelectedIndex = 0;
                return;
            }
            base.WndProc(ref m);                
        }
        protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
        {
            e.DrawDefault = true;
            base.OnDrawColumnHeader(e);
        }
        protected override void OnDrawItem(DrawListViewItemEventArgs e)
        {
            i = 0;           
            base.OnDrawItem(e);
        }
        int i;
        protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
        {                
            if (!SelectedIndices.Contains(e.ItemIndex)) e.DrawDefault = true;
            else
            {
                bool isItem = i == 0;
                Rectangle iBound = FullRowSelect ? e.Bounds : isItem ? e.Item.GetBounds(ItemBoundsPortion.ItemOnly) : e.SubItem.Bounds;
                Color iColor = FullRowSelect || isItem ? SystemColors.HighlightText : e.SubItem.ForeColor;
                Rectangle focusBound = FullRowSelect ? e.Item.GetBounds(ItemBoundsPortion.Entire) : iBound;
                if(FullRowSelect || isItem) e.Graphics.FillRectangle(SystemBrushes.Highlight, iBound);                    
                TextRenderer.DrawText(e.Graphics, isItem ? e.Item.Text : e.SubItem.Text,
                    isItem ? e.Item.Font : e.SubItem.Font, iBound, iColor,
                    TextFormatFlags.LeftAndRightPadding | TextFormatFlags.VerticalCenter);
                if(FullRowSelect || isItem) 
                  ControlPaint.DrawFocusRectangle(e.Graphics, focusBound);
            }
            i++;                
            base.OnDrawSubItem(e);
        }
}

注意:上面的代码将禁用MouseDown, MouseUp(用于左键)和KeyDown事件(用于箭头键),如果您想在 之外处理这些事件CustomListView,您可能需要自己引发这些事件。(默认情况下,这些事件由 in 或 after 的某些代码引发base.WndProc)。

还有一种情况是用户可以通过 选择项目holding mouse down and drag to select。要禁用此功能,我认为我们必须捕获消息WM_NCHITTEST,但我们必须在正确的条件下捕获和过滤它。我试过处理这个但没有运气。我希望你能做到。这只是一个演示。然而,正如我所说,我们似乎无法走另一条路。BUG我认为你的问题是某种ListView控制。

更新

事实上,我之前想过,Focused但那Selected是我尝试访问SelectedItemwith的时候ListView.SelectedItems(这是错误的)。所以我没有尝试这种方法。但是在发现我们可以通过and访问虚拟模式下SelectedItem的 a之后,我认为这个解决方案是最有效和最简单的解决方案:ListViewListView.SelectedIndicesListView.Items

int selected = -1;
bool suppressSelectedIndexChanged;
private void timer1_Tick(object sender, EventArgs e)
{
  listView1.SuspendLayout();                
  if (selected > -1){
     ListViewItem item = listView1.Items[selected];
     Rectangle rect = listView1.GetItemRect(item.Index);
     suppressSelectedIndexChanged = true;                    
     item.Selected = item.Focused = !(rect.Top <= 2 || rect.Bottom >= listView1.ClientSize.Height-2);
     suppressSelectedIndexChanged = false;
  }
  listView1.VirtualListSize++;
  listView1.ResumeLayout(true);
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e){
   if (suppressSelectedIndexChanged) return;
   selected = listView1.SelectedIndices.Count > 0 ? listView1.SelectedIndices[0] : -1;
}

注意:代码只是一个示例,用户只选择了一项,您可以添加更多代码来处理用户选择多于一项的情况。

于 2013-08-20T15:35:37.560 回答
0

我遇到了同样的问题,@Sinatr 的代码几乎可以完美运行,但是当所选项目正好位于列表视图的顶部边框时,它开始在每次更新时在所选项目和下一个项目之间跳转。

我必须将列标题的高度包含在为我解决问题的可见性测试中:

if (lstLogMessages.SelectedIndices.Count > 0)
{
    Rectangle selectedItemArea = lstLogMessages.Items[lstLogMessages.SelectedIndices[0]].Bounds;
    Rectangle listviewClientArea = lstLogMessages.ClientRectangle;
    int headerHeight = lstLogMessages.TopItem.Bounds.Top;
    if (selectedItemArea.Y + selectedItemArea.Height > headerHeight && selectedItemArea.Y + selectedItemArea.Height < listviewClientArea.Height)   // if the selected item is in the visible region 
    {
        lstLogMessages.Items[lstLogMessages.SelectedIndices[0]].Focused = true;
    }
    else
    {
        lstLogMessages.TopItem.Focused = true;
    }
}

lstLogMessages.VirtualListSize = currentView.MessageCount;
于 2016-06-22T07:22:32.677 回答
0

我知道这是旧帖子,[King King] 已经给出了一个双缓冲区示例,但是如果它对某些人有帮助,仍然会发布一个简单的代码,即使您选择了一个项目,这也可以消除闪烁,但是您需要继承 ListView 才能使用这导致 SetStyle 无法从外部访问

C# 代码

public class ListViewEX : ListView
{
    public ListViewEX()
    {
        SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
    }
}

VB.NET

Public Class ListViewEX
    Inherits ListView
    Public Sub New()
        SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.OptimizedDoubleBuffer, True)
    End Sub
End Class
于 2016-09-23T11:39:08.210 回答