在Winform FlowLayoutPanel中模拟Windows拖动效果

时间:2014-09-13 16:35:14

标签: c# .net winforms drag

当用户拖动鼠标时,我正在模拟窗口多重选择矩形。为了同步我们的理解,这张图片显示了我想要模拟的效果:

enter image description here

现在我想在 FlowLayoutPanel 上使用一些控件来模拟这种效果。

到目前为止,我设法几乎完成了效果:

enter image description here

我在这里做的是在主窗体的顶部放置一个没有焦点的无边框半透明(不透明度的一半)窗体。为了模拟边框,我处理了SizeChangedPaint以绘制边框。

然而,这种解决方案有时会闪烁,因为在业主边界无法准时清算:

enter image description here

我已尝试将DoubleBuffer设置为true,并覆盖CreateParam以设置WM_EX_COMPOSITED,然后在封面上使用双缓冲,但两者均无效。

我的问题是:如何减少这种神器?

非常感谢!

我的代码:

封面形式:

public partial class CoverForm : Form
{
    public CoverForm()
    {
        InitializeComponent();

        BackColor = Color.CadetBlue;
        FormBorderStyle = FormBorderStyle.None;

        SizeChanged += (s, e) => Invalidate();
        Paint += (s, e) =>
                     {
                         e.Graphics.Clear(BackColor);

                         using (var pen = new Pen(Color.DodgerBlue))
                         {
                             e.Graphics.DrawRectangle(pen, 1, 1, Size.Width - 2, Size.Height - 2);
                         }
                     };
    }

    protected override bool ShowWithoutActivation
    {
        get { return true; }
    }
}

主要表格:

public Form1()
{
    InitializeComponent();

    // mainPanel is the panel that simulates the dragging effect
    mainPanel.MouseDown += (s, e) =>
                                {
                                    _isMouseDown = true;
                                    _startPosition = e.Location;
                                    coverForm.Location = mainPanel.PointToScreen(e.Location);
                                    coverForm.Show();
                                };
    mainPanel.MouseUp += (s, e) =>
                                {
                                    _isMouseDown = false;
                                    coverForm.Hide();
                                };
    mainPanel.MouseMove += CoverPanelMouseMoveHandler;

    DoubleBuffered = true;
}

~Form1()
{
    if (coverForm != null && !coverForm.IsDisposed)
    {
        coverForm.Dispose();
    }
}

# region Dragging Effect
private void CoverPanelMouseMoveHandler(object sender, MouseEventArgs e)
{
    if (_isMouseDown)
    {
        _curPosition = e.Location;
        // find the dragging rectangle
        var rect = CreateRect(_curPosition, _startPosition);

        coverForm.Size = rect.Size;
        coverForm.Location = mainPanel.PointToScreen(rect.Location);

        foreach (Control control in mainPanel.Controls)
        {
            // logic to get button backcolor changed
        }

        mainPanel.Invalidate(true);
    }
}

更新

我试图覆盖OnPaint并将我的绘图放在那里,但结果却更糟:旧的涂料不会被删除:

enter image description here

代码我修改了封面形式:

public partial class CoverForm : Form
{
    public CoverForm()
    {
        InitializeComponent();

        BackColor = Color.CadetBlue;
        FormBorderStyle = FormBorderStyle.None;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.Clear(BackColor);

        using (var pen = new Pen(Color.FromArgb(255, 0, 0, 255)))
        {
            e.Graphics.DrawRectangle(pen, 0, 0, Size.Width - 1, Size.Height - 1);
        }
    }

    protected override bool ShowWithoutActivation
    {
        get { return true; }
    }
}

更新2

实际上我遇到的问题是在FlowLayoutPanel上面绘图,而不是普通的面板。我之前提出Panel的原因是我正在为我闪烁的2层设计寻找答案。但是,由于某人通过向控制面板添加控件以将其绘制在所有控件之上来解决问题,我想指出这一点:向面板添加控件将是微不足道的,但是FlowLayoutPanel将新添加的控件自动对齐到下一个可用位置,这可能会影响预期的效果。

2 个答案:

答案 0 :(得分:1)


Video Demo of the Solution: Remember to Switch to 1080p
在蹩脚的机器上记录在VM中。所以有点慢。


您正在获取这些工件,因为您一次完成了3件事的组合。

两个大的将表单移动到另一个位置并调整表单大小。如果表单是半透明的,它也无济于事:)为了更好地理解我的意思,只需打开VS2013并快速调整窗口大小(在左上角,并以非常快的速度随机运行) ,你会看到它无法跟上边缘。是的,当你从窗口周围的不同位置调整大小时,你会得到不同的结果(只需要考虑一下,你就会明白它。)

Aybe,提供了一个非常聪明的解决方案,但它不允许您透视它或查看是否对面板进行任何更新....因为它基本上只是将最后一个输出复制到位图并将其用作背面缓冲区(很像你在绘画程序中做选择时你可能会做的事情)。

如果你真的想用叠加形式来做它并保持半透明,那么如果你不想要工件,就需要消除这三件事。


代码需要相当多的WIN32知识....幸运的是,微软已经做了很多困难。我们将使用Microsoft的PerPixelAlphaForm启用封面框架中的每像素透明度(您可以谷歌)我将在此处粘贴代码。它基本上只创建一个样式为WS_EX_LAYERED的窗口。保持与屏幕AlphaBlended的Backbuffer(简单吧?)。

/******************************** Module Header ********************************\
Module Name:  PerPixelAlphaForm.cs
Project:      CSWinFormLayeredWindow
Copyright (c) Microsoft Corporation.



This source is subject to the Microsoft Public License.
See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
All other rights reserved.

THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
\*******************************************************************************/

#region Using directives
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
#endregion

namespace CSWinFormLayeredWindow
{
    public partial class PerPixelAlphaForm : Form
    {
        public PerPixelAlphaForm()
        {
            InitializeComponent();
        }


        protected override CreateParams CreateParams
        {
            get
            {
                // Add the layered extended style (WS_EX_LAYERED) to this window.
                CreateParams createParams = base.CreateParams;
                createParams.ExStyle |= WS_EX_LAYERED;
                return createParams;
            }
        }


        /// <summary>
        /// Let Windows drag this window for us (thinks its hitting the title 
        /// bar of the window)
        /// </summary>
        /// <param name="message"></param>
        protected override void WndProc(ref Message message)
        {
            if (message.Msg == WM_NCHITTEST)
            {
                // Tell Windows that the user is on the title bar (caption)
                message.Result = (IntPtr)HTCAPTION;
            }
            else
            {
                base.WndProc(ref message);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="bitmap"></param>
        public void SelectBitmap(Bitmap bitmap)
        {
            SelectBitmap(bitmap, 255);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="bitmap">
        /// 
        /// </param>
        /// <param name="opacity">
        /// Specifies an alpha transparency value to be used on the entire source 
        /// bitmap. The SourceConstantAlpha value is combined with any per-pixel 
        /// alpha values in the source bitmap. The value ranges from 0 to 255. If 
        /// you set SourceConstantAlpha to 0, it is assumed that your image is 
        /// transparent. When you only want to use per-pixel alpha values, set 
        /// the SourceConstantAlpha value to 255 (opaque).
        /// </param>
        public void SelectBitmap(Bitmap bitmap, int opacity)
        {
            // Does this bitmap contain an alpha channel?
            if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
            {
                throw new ApplicationException("The bitmap must be 32bpp with alpha-channel.");
            }

            // Get device contexts
            IntPtr screenDc = GetDC(IntPtr.Zero);
            IntPtr memDc = CreateCompatibleDC(screenDc);
            IntPtr hBitmap = IntPtr.Zero;
            IntPtr hOldBitmap = IntPtr.Zero;

            try
            {
                // Get handle to the new bitmap and select it into the current 
                // device context.
                hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
                hOldBitmap = SelectObject(memDc, hBitmap);

                // Set parameters for layered window update.
                Size newSize = new Size(bitmap.Width, bitmap.Height);
                Point sourceLocation = new Point(0, 0);
                Point newLocation = new Point(this.Left, this.Top);
                BLENDFUNCTION blend = new BLENDFUNCTION();
                blend.BlendOp = AC_SRC_OVER;
                blend.BlendFlags = 0;
                blend.SourceConstantAlpha = (byte)opacity;
                blend.AlphaFormat = AC_SRC_ALPHA;

                // Update the window.
                UpdateLayeredWindow(
                    this.Handle,     // Handle to the layered window
                    screenDc,        // Handle to the screen DC
                    ref newLocation, // New screen position of the layered window
                    ref newSize,     // New size of the layered window
                    memDc,           // Handle to the layered window surface DC
                    ref sourceLocation, // Location of the layer in the DC
                    0,               // Color key of the layered window
                    ref blend,       // Transparency of the layered window
                    ULW_ALPHA        // Use blend as the blend function
                    );
            }
            finally
            {
                // Release device context.
                ReleaseDC(IntPtr.Zero, screenDc);
                if (hBitmap != IntPtr.Zero)
                {
                    SelectObject(memDc, hOldBitmap);
                    DeleteObject(hBitmap);
                }
                DeleteDC(memDc);
            }
        }


        #region Native Methods and Structures

        const Int32 WS_EX_LAYERED = 0x80000;
        const Int32 HTCAPTION = 0x02;
        const Int32 WM_NCHITTEST = 0x84;
        const Int32 ULW_ALPHA = 0x02;
        const byte AC_SRC_OVER = 0x00;
        const byte AC_SRC_ALPHA = 0x01;

        [StructLayout(LayoutKind.Sequential)]
        struct Point
        {
            public Int32 x;
            public Int32 y;

            public Point(Int32 x, Int32 y)
            { this.x = x; this.y = y; }
        }

        [StructLayout(LayoutKind.Sequential)]
        struct Size
        {
            public Int32 cx;
            public Int32 cy;

            public Size(Int32 cx, Int32 cy)
            { this.cx = cx; this.cy = cy; }
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct ARGB
        {
            public byte Blue;
            public byte Green;
            public byte Red;
            public byte Alpha;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct BLENDFUNCTION
        {
            public byte BlendOp;
            public byte BlendFlags;
            public byte SourceConstantAlpha;
            public byte AlphaFormat;
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
            ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc,
            Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr CreateCompatibleDC(IntPtr hDC);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetDC(IntPtr hWnd);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool DeleteDC(IntPtr hdc);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool DeleteObject(IntPtr hObject);

        #endregion
    }
}

好的,这应该消除你的半透明问题。记得摆脱WndProc的覆盖(你不需要它)。将Double-Buffer设置为false,将TopMost设置为true

现在要消除其他两个问题。我希望你能想到一种方法......但我会给你解决方案。 始终将PerPixelAlphaForm保持为MainForm的大小。相同的位置,相同的大小。 :) 并将PerPixelAlphaForm的后备缓冲区位图调整为相同的大小。当你这样做时,你所要做的就是重绘选择矩形。为什么?因为它完美地覆盖了整个MainForm。

所以基本上

`OnMouseDown`  = Save initial point of mouse, show the Cover layer
`OnMouseMove`  = clear the PerPixelAlphaForm bitmap, draw your rectangle
                 call SelectBitmap again update the form
`OnMouseUp`    = hide the Cover layer (or whatever you want to do)

我个人已将所有这些连接到Control-Key


要清除我们需要以某种方式执行的PerPixelAlphaForm。将所有值设为Alpha为0。

public void ClearBackbuffer()
{
    Graphics g = Graphics.FromImage(_reference_to_your_backbuffer_);
    g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
    SolidBrush sb = new SolidBrush(Color.FromArgb(0x00, 0x00, 0x00, 0x00));
    g.FillRectangle(sb, this.ClientRectangle);
    sb.Dispose();
    g.Dispose();
}

Video Demo of the Solution: Remember to Switch to 1080p


如果您需要更多帮助,请告诉我,我可以找一些时间从较大的程序中删除代码。但在我看来,你是那种喜欢摆弄东西的人:D

答案 1 :(得分:0)

编辑:使用额外的PictureBox和Bitmap使整个工作正常工作

以下面板绘制一个没有闪烁的矩形:

internal sealed class MyPanel : Panel
{
    private readonly PictureBox _pictureBox;
    private Bitmap _bitmapContent;
    private Bitmap _bitmapForeground;
    private Point? _point1;
    private Point? _point2;

    public MyPanel()
    {
        DoubleBuffered = true;
        _pictureBox = new PictureBox();
    }

    protected override void OnSizeChanged(EventArgs e)
    {
        if (_bitmapForeground != null) _bitmapForeground.Dispose();
        _bitmapForeground = new Bitmap(Size.Width, Size.Height);
        if (_bitmapContent != null) _bitmapContent.Dispose();
        _bitmapContent = new Bitmap(Size.Width, Size.Height);
        _pictureBox.Size = Size;
        _pictureBox.Image = _bitmapForeground;
        base.OnSizeChanged(e);
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        _point1 = e.Location;
        DrawToBitmap(_bitmapContent, new Rectangle(0, 0, Size.Width, Size.Height));
        SetControlsVisibility(false);
        Controls.Add(_pictureBox);
        base.OnMouseDown(e);
    }

    private void SetControlsVisibility(bool visible)
    {
        IEnumerable<Control> ofType = Controls.OfType<Control>();
        foreach (Control control in ofType)
        {
            control.Visible = visible;
        }
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        Controls.Remove(_pictureBox);
        SetControlsVisibility(true);
        _point1 = null;
        _point2 = null;
        Refresh();
        base.OnMouseUp(e);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (_point1 != null)
        {
            _point2 = e.Location;
            if (_point1 != null && _point2 != null)
            {
                Point p1 = _point1.Value;
                Point p2 = _point2.Value;
                int x1 = p1.X;
                int y1 = p1.Y;
                int x2 = p2.X;
                int y2 = p2.Y;
                int xmin = Math.Min(x1, x2);
                int ymin = Math.Min(y1, y2);
                int xmax = Math.Max(x1, x2);
                int ymax = Math.Max(y1, y2);
                using (Graphics graphics = Graphics.FromImage(_bitmapForeground))
                {
                    graphics.DrawImageUnscaled(_bitmapContent, 0, 0, _bitmapContent.Width, _bitmapContent.Height);
                    graphics.DrawRectangle(Pens.Red, new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin));
                }
                _pictureBox.Refresh();
            }
        }
        base.OnMouseMove(e);
    }
}

但是,矩形将位于控件下方,不确定原因......

相关问题