C#WinForms透明点击控件来绘制

时间:2012-04-07 10:06:45

标签: c# winforms controls transparent

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

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

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

然而,我完全不知道如何做到这一点。我知道我可以将面板颜色设置为透明,但这不会使它变成点击 - 但是......

PS:我最终走出了简单的方法,那就是 - 重新绘制我以前用白色绘制的内容,然后用黑色绘制新的选择指示器,而不会使控件无效。这消除了闪烁。但是,由于StackOverflow强制您选择答案,我会选择我觉得最合适的答案......可能......

5 个答案:

答案 0 :(得分:3)

看一下Locus Effect library,它们会绘制可以点击的透明顶层窗口。

在解决方案的核心,这是通过一个覆盖窗口过程的窗口完成的,类似于:

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

0x0084HTTRANSPARENT-1WndProc

虽然我不确定此解决方案是否也适用于儿童控件,但值得尝试一下。只需派生一个控件并覆盖{{1}}。

答案 1 :(得分:3)

        protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x84)
        {
            m.Result= new IntPtr(-1);
            return;
        }
        base.WndProc(ref m);
    }

在2012年有效......

答案 2 :(得分: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);
        }

    }
}

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

如果你在一条线上直接点击,那么点击将不会通过,同样如果鼠标直接悬停在画布上的一条线上,滚动事件之类的东西将无法通过但是如果您在{{3}上阅读你可以很容易地从TreeCanvas类中将它们连接在一起。

答案 3 :(得分:2)

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

答案 4 :(得分:1)

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

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

所以它的基础设施部分非常简单,摆脱了闪烁,但基本上是关于最小化绘图量,这是多少次,多少。

我怀疑如果你的透明面板和树视图一样大,那么无论如何它都将绘制整个事物。您可能能够从带有矩形的Invalidate的重载中获得更多,也许......