Simulate the drag and drop effect of Windows in Winflow FlowLayoutPanel

I am currently simulating a select box for multiple windows when the user drags the mouse. To synchronize our understanding, this figure shows the effect I want to simulate:

enter image description here

Now I want to simulate this effect on a FlowLayoutPanel with some controls inside.

So far I have managed to achieve an almost complete effect:

enter image description here

What I did here was overlaying an opaque translucent translucent (translucent) shape on top of the main shape . To get an imitation of the border, I processed SizeChanged and Paint to draw the border.

However, this solution sometimes flickers, because at the owner’s border it cannot be cleared on time:

enter image description here

I tried using double buffering in the form of a cover by setting DoubleBuffer to true and overriding WM_EX_COMPOSITED to set WM_EX_COMPOSITED , but it doesn't work.

My question is: how to reduce this artifact?

Thanks a lot!

My code is:

For cover form:

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

For the main form:

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

Update

I tried to redefine OnPaint and put my drawing there, but it gave an even worse result: old paints will not be erased:

enter image description here

Code I changed for cover form:

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

Update 2

In fact, the problem I am facing is the picture above the FlowLayoutPanel , not the regular panel . The reason I put Panel before, I was looking for an answer for my flickering 2-layer design. But since someone approaches the problem by adding a control to the panel in order to get it over all the controls , I would like to point out this: adding a control to the panel would be trivial, but FlowLayoutPanel would be automatic - add the newly added element control to the next available position, which can ruin the expected effect.

+6
source share
2 answers

Video demonstration of the solution: do not forget to switch to 1080p
Written in a virtual machine on a crappy machine. So curious slowly.


You get these artifacts because you are simultaneously doing a combination of three things.

Two large files move the form to another location and resize the form. It also does not help if the shape is translucent. To better understand what I mean, just open VS2013 and resize the window very quickly (in the upper left corner and very quickly follow arbitrary directions), you will see that it cannot keep up at the edges. And yes, you will get different results when you resize from a different position around the window (think about it for a minute and you figure it out).

Aybe, provided a pretty clever solution, but it doesn’t allow you to look at it or see if there are any updates on the panel .... since it basically just copies the last output into a bitmap and uses this as a back buffer (like what, in your opinion, someone can do when he makes a selection in the drawing program).

If you really want to do this with the overlay shape and keep it translucent, you will need to eliminate these three things if you don't want artifacts.


This code requires quite a bit of WIN32 knowledge .... fortunately for you Microsoft has already made a difficult role. We are going to enable pixel transparency in your cover using Microsoft's PerPixelAlphaForm (you can use it). I am inserting the code here. It just creates a window with the WS_EX_LAYERED style. Saves a backbuffer that is alpha-blended with a screen (simple huh?).

 /******************************** 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 } } 

OK, this should fix your translucent problem. Remember to get rid of the WndProc override (you won’t need it). Set the double buffer to false and TopMost to true .

Now, to fix two other problems. I hope you thought about how to do this ... but I will give you my decision. Always keep PerPixelAlphaForm the size of MainForm. Same location, same SIZE. :) And change the size of the BackBuffer PerPixelAlphaForm bitmap to the same size as . When you do this, all you have to do is redraw the selection rectangle. What for? because it completely covers the entire MainForm.

So basically

 `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) 

I have everything related to the Control-Key


To clear PerPixelAlphaForm, we need to do it in a specific way. Give all alpha values ​​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 demonstration of the solution: do not forget to switch to 1080p


If you need more help, let me know, I can find the time to snatch the code from a larger program. But it seems to me that you are the kind of person who likes to mess with things: D

+1
source

EDIT: Using an optional PictureBox and bitmap does everything that works

The following panel draws a rectangle without flickering:

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

However, the rectangle will be below the controls, not sure why ...

0
source

Source: https://habr.com/ru/post/975229/


All Articles