我正在使用自定义 TreeView 类,因为我需要拖放功能。我在树视图上画线,以便向用户显示拖动的项目将去往何处。这会产生很多可见的闪烁,因为每次“放置目标”发生变化时,我都必须重新绘制树视图(以清理之前绘制的任何内容)
不幸的是,TreeView 没有 DoubleBuffered 选项。
所以,我想到了这个解决方案:一个可以在自身上绘制的不可见控件,位于树视图上方,但传递所有鼠标事件并且不接收焦点。我可以在该控件上绘画,从而防止闪烁。
但是,我完全不知道该怎么做。我知道我可以将面板颜色设置为透明,但这不会使它成为点击...
PS:我最终采用了简单的方法,即用白色重新绘制之前绘制的内容,然后用黑色绘制新的选择指示器,而不使控件无效。这消除了闪烁。然而,由于 StackOverflow 强制你选择一个答案,我会选择我认为最合适的一个......可能......
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x84)
{
m.Result= new IntPtr(-1);
return;
}
base.WndProc(ref m);
}
这是 2012 年有效的方法。
看看轨迹效果库,他们确实绘制了可以点击的透明顶层窗口。
在解决方案的核心,这是通过一个覆盖窗口过程的窗口来完成的,类似于这样:
protected override void WndProc(ref Message m)
{
// Do not allow this window to become active - it should be "transparent"
// to mouse clicks i.e. Mouse clicks pass through this window
if ( m.Msg == Win32Constants.WM_NCHITTEST )
{
m.Result = new IntPtr( Win32Constants.HTTRANSPARENT );
return;
}
base.WndProc( ref m ) ;
}
WM_NCHITTEST
为 0x0084
且 HTTRANSPARENT
为 -1
。
虽然我不确定这个解决方案是否也适用于子控件,但可能值得一试。只需派生一个控件并覆盖
WndProc
。
即使像这样的透明画布也必须重新绘制其背景(其中包括其背后的事物)。也许有一些解决办法,但每次我遇到组件闪烁问题时,都没有一个真正令人满意的解决方案,包括利用组件(甚至自定义)双缓冲。当像这样的实时图形交互成为应用程序的必要部分时,我总是发现最好的选择是转向“以正确的方式”处理图形的东西。 WPF、XNA 或 DirectX 可能是此问题的最佳答案。 WPF 还添加了诸如路由事件之类的功能,这使得这种单事件->多组件范例的编码更加简单。
这是在 winforms 应用程序中使用 WPF 互操作性组件的示例:
1)向您的表单添加一个新的 ElementHost(我在这里称之为
elementHost1
)
2)向您的项目添加一个新的 WPF UserControl(我称之为
TreeCanvas
)
XAML
<UserControl x:Class="WindowsFormsApplication1.TreeCanvas"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300">
<Grid>
<Canvas Name="Canvas1">
<TreeView Canvas.Left="0" Canvas.Top="0" Height="300" Name="TreeView1" Width="300" />
</Canvas>
</Grid>
TreeCanvas 的后台代码不需要任何东西 - 只需
InitializeComponent();
生成的代码就是您现在所需要的。
以及您的表单代码
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
//make a new TreeCanvas
private TreeCanvas MyTreeCanvas = new TreeCanvas();
public Form1()
{
InitializeComponent();
//attach the TreeCanvas component to the element host
this.Width = 400;
this.Height = 400;
elementHost1.Child = MyTreeCanvas;
elementHost1.Location = new System.Drawing.Point(30, 30);
elementHost1.Height = 300;
elementHost1.Width = 300;
// Just adding some random stuff to the treeview
int i = 0;
int j = 0;
for (i = 0; i <= 10; i++)
{
TreeViewItem nitm = new TreeViewItem();
nitm.Header = "Item " + Convert.ToString(i);
MyTreeCanvas.TreeView1.Items.Add(nitm);
for (j = 1; j <= 3; j++)
{
TreeViewItem itm = (TreeViewItem)MyTreeCanvas.TreeView1.Items[i];
itm.Items.Add("Item " + Convert.ToString(j));
}
}
//Draw a line on the canvas with the treeview
Line myLine = new Line();
myLine.Stroke = System.Windows.Media.Brushes.Red;
myLine.X1 = 1;
myLine.X2 = 50;
myLine.Y1 = 1;
myLine.Y2 = 300;
myLine.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
myLine.VerticalAlignment = VerticalAlignment.Center;
myLine.StrokeThickness = 2;
MyTreeCanvas.Canvas1.Children.Add(myLine);
}
}
}
这为您提供了画布内的树视图,您可以在其顶部进行绘画,同时仍然能够单击和操作下面的树视图(包括鼠标滚动事件等)。
如果您直接单击一行,则单击将不会执行,同样,如果鼠标直接悬停在画布上的一行上,则滚动事件之类的事情将不会执行,但如果您阅读了路由事件,您可以在 TreeCanvas 类中很容易将它们连接在一起。
您的叠加层必须获得焦点才能将其自身附加到鼠标单击和移动。 不确定这样做是否一定能解决您的问题。
但是如果您创建自定义面板使其透明, 添加属性以提供指向自定义树视图的链接。 添加一个 onmouseclick 处理程序。 此时,您可以解释单击的含义并在面板上绘制内容并调用关联的自定义树视图上的方法。
因此,它的基础设施部分非常琐碎,消除了闪烁,但基本上是为了最大限度地减少绘制量,即绘制频率和绘制量。
我怀疑如果你的透明面板和树视图一样大,它无论如何都会绘制整个东西。您也许能够从需要矩形的 Invalidate 重载中获得更多信息,也许......
我将发布无聊但有效的答案。 TreeView 已经有一种非常好的、无闪烁的方式来突出显示节点。只需为其 DragOver 事件实现一个事件处理程序即可使用它。像这样:
private void treeView_DragOver(object sender, DragEventArgs e) {
var tree = (TreeView)sender;
var pos = tree.PointToClient(new Point(e.X, e.Y));
var hit = tree.HitTest(pos);
if (hit.Node == null) e.Effect = DragDropEffects.None;
else {
tree.SelectedNode = hit.Node;
tree.SelectedNode.Expand(); // Optional
e.Effect = DragDropEffects.Copy;
}
}
注意代码中 TreeNode.Expand() 的使用。问题中不清楚您是否需要它,但通常需要它,因为用户没有其他好方法来访问因父节点折叠而隐藏的子节点。