1

我正在使用自定义 TreeView 类,因为我需要拖放功能。我在树视图上画线,以向用户显示拖动的项目将去哪里。这会产生很多可见的闪烁,因为每次“放置目标”发生变化时,我都必须重新绘制树视图(以清理之前绘制的任何内容)

不幸的是,TreeView 没有 DoubleBuffered 选项。

所以,我想到了这个解决方案:一个可以在自身上绘制的不可见控件,位于树视图上,但传递所有鼠标事件并且不接收焦点。我可以在该控件上绘画,从而防止闪烁。

但是,我完全不知道该怎么做。我知道我可以将面板颜色设置为透明,但这不会让它变成点击...

PS:我最终选择了简单的方法,即 - 用白色重新绘制我之前绘制的内容,然后用黑色绘制新的选择指示器,而不会使控件无效。这消除了闪烁。但是,由于 StackOverflow 迫使您选择一个答案,我会选择一个我认为最合适的答案......可能......

4

5 回答 5

3

Taking a look at the Locus Effect library, they do paint transparent top-level windows that can be clicked-through.

In the heart of the solutions, this is done by a window that overrides the Window Procedure similar to this:

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 ) ;
}

With WM_NCHITTEST being 0x0084 and HTTRANSPARENT being -1.

While I'm not sure whether this solution works for child controls, too, it might be worth giving it a try. Simply derive a control and override the WndProc.

于 2012-04-07T11:12:44.757 回答
3
        protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x84)
        {
            m.Result= new IntPtr(-1);
            return;
        }
        base.WndProc(ref m);
    }

这是2012年的工作......

于 2013-01-14T21:59:21.807 回答
2

即使是这样的透明画布也必须重新绘制其背景(包括其背后的东西)。也许这有一些技巧,但每次我遇到组件闪烁问题时,从来没有真正令人满意的解决方案,包括利用组件(甚至自定义)双缓冲。当像这样的实时图形交互成为应用程序的必要部分时,我总是发现最好的选择是转向以“正确方式”处理图形的东西。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);
        }

    }
}

这为您在画布内提供了一个树视图,您可以在其顶部进行绘制,同时仍然可以单击和操作下面的树视图(包括鼠标滚动事件等)。

如果您直接在一行上单击,则单击将不会通过,同样,如果鼠标直接悬停在画布上的一行上,则滚动事件之类的事情将不会通过,但是如果您阅读了Routed Events您可以很容易地连线在 TreeCanvas 类中将它们组合在一起。

于 2012-04-07T10:42:20.077 回答
1

我将发布无聊但有效的答案。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() 的使用。从问题中不清楚您是否需要它,但通常需要它,因为用户没有其他好的方法可以访问由于其父节点折叠而隐藏的子节点。

于 2012-04-07T12:12:35.950 回答
1

好吧,您的叠加层必须获得焦点才能将自己附加到鼠标点击和移动上。不确定这样做是否一定会解决您的问题。

但是,如果您创建自定义面板使其透明,请添加一个属性以提供指向自定义树视图的链接。添加说一个 onmouseclick 处理程序。此时,您可以解释单击方式并在面板上绘制内容并在关联的自定义树视图上调用方法。

所以它的基础设施部分是非常微不足道的,摆脱闪烁,虽然基本上是关于尽量减少绘制的数量,这就是频率和多少。

我怀疑如果你的透明面板和树视图一样大,它无论如何都会画出整个东西。您也许可以从需要 rect 的 Invalidate 过载中获得更多收益,也许......

于 2012-04-07T10:55:36.880 回答