Custom TextBox Not Drawing - c#

I've made a custom TextBox class to have a text box with custom border in my application, based on this other SO post. If I set any of the new custom properties in the form designer, they appear momentarily, until I change the control focus, and when I run the application, the new border settings are not displayed. I did update my form's InitializeComponent method, so that the text box initializes a new BorderedTextBox instead of TextBox. Does anyone know what's wrong here?
public class BorderedTextBox : TextBox
{
private Color _borderColor = Color.Black;
private int _borderWidth = 2;
private int _borderRadius = 5;
public BorderedTextBox() : base()
{
InitializeComponent();
this.Paint += this.BorderedTextBox_Paint;
}
public BorderedTextBox(int width, int radius, Color color) : base()
{
this._borderWidth = Math.Max(1, width);
this._borderColor = color;
this._borderRadius = Math.Max(0, radius);
InitializeComponent();
this.Paint += this.BorderedTextBox_Paint;
}
public Color BorderColor
{
get => this._borderColor;
set
{
this._borderColor = value;
DrawTextBox();
}
}
public int BorderWidth
{
get => this._borderWidth;
set
{
if (value > 0)
{
this._borderWidth = Math.Min(value, 10);
DrawTextBox();
}
}
}
public int BorderRadius
{
get => this._borderRadius;
set
{ // Setting a radius of 0 produces square corners...
if (value >= 0)
{
this._borderRadius = value;
this.DrawTextBox();
}
}
}
private void BorderedTextBox_Paint(object sender, PaintEventArgs e) => DrawTextBox(e.Graphics);
private void DrawTextBox() => this.DrawTextBox(this.CreateGraphics());
private void DrawTextBox(Graphics g)
{
Brush borderBrush = new SolidBrush(this.BorderColor);
Pen borderPen = new Pen(borderBrush, (float)this._borderWidth);
Rectangle rect = new Rectangle(
this.ClientRectangle.X,
this.ClientRectangle.Y,
this.ClientRectangle.Width - 1,
this.ClientRectangle.Height - 1);
// Clear text and border
g.Clear(this.BackColor);
// Drawing Border
g.DrawRoundedRectangle(
borderPen,
(0 == this._borderWidth % 2) ? rect.X + this._borderWidth / 2 : rect.X + 1 + this._borderWidth / 2,
rect.Y,
rect.Width - this._borderWidth,
(0 == this._borderWidth % 2) ? rect.Height - this._borderWidth / 2 : rect.Height - 1 - this._borderWidth / 2,
(float)this._borderRadius);
}
#region Component Designer generated code
/// <summary>Required designer variable.</summary>
private System.ComponentModel.IContainer components = null;
/// <summary>Clean up any resources being used.</summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing);
}
/// <summary>Required method for Designer support - Don't modify!</summary>
private void InitializeComponent() => components = new System.ComponentModel.Container();
#endregion
}

You need to override WndProc:
private const int WM_PAINT = 0x000F;
protected override void WndProc( ref Message m ) {
if(m.Msg == WM_PAINT ) {
base.WndProc( ref m );
Graphics gr = this.CreateGraphics();
//draw what you want
gr.Dispose();
return;
}
base.WndProc( ref m );
}
Works fine without any issues. It draws in client area though. A complete version drawing a custom border, textbox need to have border:
[DllImport( "user32.dll" )]
static extern IntPtr GetWindowDC( IntPtr hWnd );
[DllImport( "user32.dll" )]
static extern bool ReleaseDC( IntPtr hWnd, IntPtr hDC );
[DllImport( "gdi32.dll" )]
static extern bool FillRgn( IntPtr hdc, IntPtr hrgn, IntPtr hbr );
[DllImport( "gdi32.dll" )]
static extern IntPtr CreateRectRgn( int nLeftRect, int nTopRect, int nRightRect,
int nBottomRect );
[DllImport( "gdi32.dll" )]
static extern IntPtr CreateSolidBrush( uint crColor );
[DllImport( "gdi32.dll" )]
static extern bool DeleteObject( IntPtr hObject );
private const int WM_NCPAINT = 0x0085;
private const int WM_PAINT = 0x000F;
private const int RGN_DIFF = 0x4;
private int p_border = 2;
protected override void WndProc( ref Message m ) {
if(m.Msg == WM_PAINT ) {
base.WndProc( ref m );
IntPtr hdc = GetWindowDC( this.Handle ); //gr.GetHdc();
IntPtr rgn = CreateRectRgn( 0, 0, this.Width, this.Height );
IntPtr brush = CreateSolidBrush( 0xFF0000 ); //Blue : B G R
CombineRgn( rgn, rgn, CreateRectRgn( p_border, p_border, this.Width - p_border,
this.Height - p_border ), RGN_DIFF );
FillRgn( hdc, rgn, brush );
ReleaseDC( this.Handle, hdc );
DeleteObject( rgn );
DeleteObject( brush );
m.Result = IntPtr.Zero;
return;
}
if( m.Msg == WM_NCPAINT ) {
return;
}
base.WndProc( ref m );
}

Winforms TextBox is a legacy control I think even from way back before the .Net framework.
It doesn't support owner-drawing and as one can see on MSDN neither Paint not OnPaint are documented to work.
Yes, you can code them, and yes, they will have some effect. But TextBox doesn't play by the normal rules and will mess up your drawing without triggering a paint event.
Possibly you can hook yourself into the windows message queue (WndProc) but it is generally not recommended, especially for something like adorning it with a border.
Usually nesting a TextBox in a Panel and letting the Panel draw a nice Border is the simplest solution..

Related

Windows Forms Move Borderless Window

i have a Windows Forms application which should be borderless (even without the titlebar) and be resizeable and moveable.
so far, i've set the BorderStyle to 'none' which removes all the borders and let my program look pretty.
Now i've added invisble borders with the following:
private const int cGrip = 16; // Grip size
private const int cCaption = 50; // Caption bar height;
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip, this.ClientSize.Height - cGrip, cGrip, cGrip);
ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
rc = new Rectangle(0, 0, this.ClientSize.Width, cCaption);
//e.Graphics.FillRectangle(Brushes.DarkBlue, rc);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x84)
{ // Trap WM_NCHITTEST
Point pos = new Point(m.LParam.ToInt32());
pos = this.PointToClient(pos);
if (pos.Y < cCaption)
{
m.Result = (IntPtr)2; // HTCAPTION
return;
}
if (pos.X >= this.ClientSize.Width - cGrip && pos.Y >= this.ClientSize.Height - cGrip)
{
m.Result = (IntPtr)17; // HTBOTTOMRIGHT
return;
}
}
base.WndProc(ref m);
}
Program Layout
The blue rectangle is rendered via the OnPaint() method and shows the field where the user is able to move the window while holding the left mouse button.
My problem is, that this rectangle is below my label.
Does anyone know how to get the rectangle in front of the label?
Another way would be disabling the label which than turns dark grey.
You would solve my problem if i could change the color of my disabled label.
Leave the Label enabled and make it so that dragging the label causes the form to be dragged as well:
public const int HT_CAPTION = 0x2;
public const int WM_NCLBUTTONDOWN = 0xA1;
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool ReleaseCapture();
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
private void label1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(this.Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
}

Flickering issue when overriding WM_PAINT in windows forms

I guess this is generic issue and not limited to ComboBox, however I have specifically problem with ComboBox. I extended ComboBox object with MyCB MyCB : ComboBox)
What happens is every time I hover over the control, leave the control, expand selection box or select a value, the control flickers. For a short while I can see default (non-replaced) control which is being instantly replaced with mine.
I believe what's happening is that Windows first draws the "original" control (by calling base.WndProc()) and then repaints it with mine.
The question is, can I somehow stop windows from painting it's own control and instantly paint mine?
Below is code overriding WndProc
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
Graphics gg = this.CreateGraphics();
gg.FillRectangle(BorderBrush, this.ClientRectangle);
// ... //
//Draw the arrow
gg.FillPath(ArrowBrush, pth);
// ... //
if(this.Text == "")
gg.DrawString("-- SELECT --", this.Font, new SolidBrush(Color.Black), rf, sf);
else
gg.DrawString(this.Text, this.Font, new SolidBrush(Color.Black), rf, sf);
gg.Dispose();
}
}
What have I tried so far:
I know that I can't do this:
if (m.Msg == WM_PAINT)
{
...
}
else
{
base.WndProc(ref m);
}
as that will cause control to repaint itself infinitely (not sure why)
I was able to remove flickering which happened when mouse leaves/enters the control by adding this code
if (m.Msg == WM_MOUSEFIRST || m.Msg == WM_MOUSELEAVE) // 0x0200 0x02A3
{
m.Result = (IntPtr)1;
}
else
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
...
}
}
however this doesn't solve the problem completely
I looked into ILSpy to check on ComboBox's WndProc but there were so many windows messages that I didn't know which of those I could possibly immitate to achive my goal
public CustomComboBox()
{
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == Win32.WM_MOUSEFIRST || m.Msg == Win32.WM_MOUSELEAVE || m.Msg == Win32.WM_MOUSEHOVER) //0x0200, 0x02A3, 0x02A1
{
m.Result = (IntPtr)1;
return;
}
base.WndProc(ref m);
if (m.Msg == Win32.WM_PAINT)
{
IntPtr hDC = Win32.GetWindowDC(m.HWnd);
Graphics g = Graphics.FromHdc(hDC);
g.SmoothingMode = SmoothingMode.HighSpeed;
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
if (Text.Length == 0)
{
StringFormat stringFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near
};
g.DrawString("Water Marks", Font, new SolidBrush(Color.FromArgb(51, 51, 51)), (Bounds.Width - 2), FontHeight + 2, stringFormat);
}
Pen border = new Pen(ui_BorderLineColor, 1);
g.DrawRectangle(border, 0, 0, this.Width - 1, this.Height - 1);
g.Dispose();
Win32.ReleaseDC(m.HWnd, hDC);
}
}
protected override void InitLayout()
{
base.InitLayout();
DropDownStyle = ComboBoxStyle.DropDownList; //Works only in DropDownList
}
// Edit completing Moses comment
public class Win32
{
public const int WM_MOUSEFIRST = 0x0200;
public const int WM_MOUSELEAVE = 0x02A3;
public const int WM_MOUSEHOVER = 0x02A1;
public const int WM_PAINT = 0x000F;
[DllImport("user32")]
public static extern IntPtr GetWindowDC(IntPtr hwnd);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
[DllImport("user32.dll")]
public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprc, IntPtr hrgn, uint flags);
}

Simulate Windows Dragging Effect in a Winform FlowLayoutPanel

I'm currently simulating the windows multiple selection rectangle when the user is dragging the mouse. To synchronize our understanding, this picture shows the effect I want to simulate:
Now I want to simulate this effect on a FlowLayoutPanel with some controls inside.
So far I am managed to get the effect almost done:
What I did here was putting a unfocused border-less semi-transparent (half the opacity) form on top the main form. To get the border simulated, I handled SizeChanged and Paint to draw the border.
However, this solution sometimes flickers, as in the owner border couldn't get cleared on-time:
I have tried using double buffering on the cover form by setting DoubleBuffer to true, and override CreateParam to set WM_EX_COMPOSITED, but neither works.
My question is: How to reduce this artifact?
Thanks a lot!
My code:
For the 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 have tried to override OnPaint and put my drawing there, but it gave even worse result: the old paints wouldn't get erased:
Code I modified 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
Actually the problem I am facing is about drawing above a FlowLayoutPanel, not a normal Panel. The reason I put Panel before was I was seeking answer for my flickering 2-layers design. But since someone approach the problem by adding control to the panel to get it drawn above all controls, I would like to point this out: adding control to a panel would be trivial, but FlowLayoutPanel will auto-align the newly added control to the next available position, which may screw up the expected effect.
Video Demo of the Solution: Remember to Switch to 1080p
Recorded in a VM on a crappy machine. So kinda slow.
You are getting those artifacts because you're doing a combination of 3 things all at once.
The two big ones are moving the form to another location and resizing the form. It also doesn't help if the form is semi transparent :) To get a better understanding of what I mean, just open VS2013 up and resize the window very quickly (at the top-left corner, and run in random directions really fast), you will see that around the edges it can't keep up. And yes, you will get different results when you're resizing from a different position around the window (just think about it for a minute and you will figure it out).
Aybe, provided a pretty clever solution but it doesn't allow you to see through it or see if any updates to the panel....since it basically just copies the last output to a bitmap and uses that as a back buffer (much like what you assume someone might do when doing the selection thing in a paint program).
If you really want to do it with an overlay form and keep it semi-transparent then you will need to eliminate those three things if you don't want artifacts.
The code requires quite a bit of WIN32 knowledge.... lucky for you Microsoft has already done the hard part. We are going to enable per pixel transparency in your cover frame by using the PerPixelAlphaForm by Microsoft (you can google it) I will paste the code here. It basically just creates a Window with a Style of WS_EX_LAYERED. Keeps a Backbuffer which is AlphaBlended with the 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, that should eliminate your semi-transparent problem. Remember to get rid of the override of the WndProc (you won't need it). Set Double-Buffer to false and TopMost to true.
Now to eliminate the other two problems. I hope you thought of a way of doing it....but I will give you my solution. Always keep the PerPixelAlphaForm the size of your MainForm. Same location, Same SIZE. :) And resize the PerPixelAlphaForm's backbuffer bitmap to the same size as well. When you do it this way, all you have to do is redraw the Selection Rectangle. Why? because it overlays the entire MainForm perfectly.
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 personally have all this hook up to the Control-Key
To clear the PerPixelAlphaForm we need to do in a certain way. Give all values an Alpha of 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
If you need more help, let me know I can find some time to rip the code out of the larger program. But it seems to me you're the kind of person that likes tinkering with stuff :D
EDIT : using an additional PictureBox and Bitmap makes the whole thing working
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 ...

Block "OnPaint" event while resizing

I am painting a control from a buffer. My code for painting is it:
protected override void OnPaint(PaintEventArgs e)
{
if (surface != null)
{
using (Bitmap Pintar = new Bitmap(e.ClipRectangle.Width, e.ClipRectangle.Height))
{
BitmapData bmd = Pintar.LockBits(new Rectangle(0, 0, e.ClipRectangle.Width, e.ClipRectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
e.Graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
int DtX = e.ClipRectangle.X;
int DtY = e.ClipRectangle.Y;
Console.WriteLine(DtX + " - " + bmd.Width + " || " + DtY + " - " + bmd.Height);
unsafe
{
byte* PunteroL = (byte*)bmd.Scan0;
byte* PunteroS = (byte*)(surface.Buffer + (DtX * 4 + surface.Width * DtY * 4));
for (int Y = 0; Y < bmd.Height; Y++)
{
byte* PunteroLT = (byte*)(PunteroL + (bmd.Width * Y * 4));
byte* PunteroST = (byte*)(PunteroS + (surface.Width * Y * 4));
for (int X = 0; X < bmd.Width; X++)
{
byte* PunteroLS = (byte*)(PunteroLT + (X * 4));
byte* PunteroSS = (byte*)(PunteroST + (X * 4));
PunteroLS[0] = PunteroSS[0];
PunteroLS[1] = PunteroSS[1];
PunteroLS[2] = PunteroSS[2];
}
}
}
Pintar.UnlockBits(bmd);
e.Graphics.DrawImage(Pintar, DtX, DtY, Pintar.Width, Pintar.Height);
}
}
}
The problem here is when I resize the window I got an error "Tryng access to protected memory" and this is because of pointers..
I wanna know if there is any way to leave (or block) the OnPaint event while the user is resizing the view..
Thank! =)
What worked best for me in a similar situation was the solution described in this SO thread.
In an nutshell, it is suggested to add to your control the following import:
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
And the following methods:
public static void SuspendDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
public static void ResumeDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
Then all you have to do is call SuspendDrawing before the re-size takes place and ResumeDrawing after.
Update:
Personally I prefer using it as an extension method, thus making it available for all controls I use without duplicating code or inheriting from base class...
public static class MyExtensionClass
{
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
public static void SuspendDrawing(this Control ctrl )
{
var parent = ctrl.Parent;
if(parent != null)
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
}
public static void ResumeDrawing(this Control ctrl )
{
var parent = ctrl.Parent;
if(parent != null)
{
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
}
}
Try to implement a event handler on Form.ResizeBegin, in which you will remove your OnPaint handler from your control.
Then you would just have to restore it in Form.ResizeEnd.
You have to get the ResizeBegin and the ResizeEnd events.
The code in there should look like:
private void OnResizeEnd(object sender, EventArgs e)
{
Paint += OnPaint;
}
private void OnResizeBegin(object sender, EventArgs e)
{
Paint -= OnPaint;
}

Window's MinWidth and MinHeight are both ignored when re-sizing with the ResizeGrip

Firstly here is the XAML code of the window itself:
<!-- Window Main -->
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Name="Window_Main" x:Class="WPF_Test.General_Window"
Title="General_Window" Height="500" Width="1000" BorderThickness="30" ResizeMode="CanResizeWithGrip" AllowsTransparency="True" WindowStyle="None" StateChanged="Window_Main_StateChanged">
<!-- Effects -->
<Window.Effect>
<DropShadowEffect x:Name="Border_Effect" ShadowDepth="0" BlurRadius="30" Direction="0" Opacity="0.5"/>
</Window.Effect>
<!-- Border Main -->
<Border x:Name="Border_Main" BorderBrush="Gray" BorderThickness="1">
<!-- Panel Main -->
<DockPanel x:Name="Panel_Main">
<DockPanel x:Name="Panel_Title" Height="25" LastChildFill="False" Background="#FFEEEEEE" MouseDown="Panel_Title_MouseDown" VerticalAlignment="Top" DockPanel.Dock="Top">
</DockPanel>
<DockPanel x:Name="Panel_Secondary" LastChildFill="true" Background="#FFEEEEEE"/>
</DockPanel>
</Border>
Here is the C# code of the constructor for that window, where I clearly define the minimum sizes of the window, But they are still ignored when re-sizing:
public void Window_Init()
{
Window_Main.MinHeight = Window_Minimum_Height + Window_Thickness * 2;
Window_Main.MinWidth = Window_Minimum_Width + Window_Thickness * 2;
Window_Main.BorderThickness = new Thickness( Window_Thickness );
}
So when I'm re-sizing the window using the ResizeGrip (provided by the CanResizeWithGrip property) the window itself will to stop to re-size at the moment it will reach the minimum width and height. While the rendering area which contains the window entirely still can be re-sized to even smaller dimensions, so it looks like a cut-off image of a window on the desktop.
I've resolved the bug.
You can use the grip resizer or create your own resizing controls , Using OnMouseDown event to send a message to HWND to resize the window while it doesn't pass the minimum sizes you have previously defined for the window.
This code will limit the window size when maximizing as well to ensure that the window will never be outside of the screen or rendered over the task-bar.
This supposed to be at the bottom of your MainWindow.cs code:
private const int WM_SYSCOMMAND = 0X112;
private HwndSource hwndSource;
enum SWP : uint
{
NOSIZE = 0x0001 ,
NOMOVE = 0x0002 ,
NOZORDER = 0x0004 ,
NOREDRAW = 0x0008 ,
NOACTIVATE = 0x0010 ,
FRAMECHANGED = 0x0020 ,
SHOWWINDOW = 0x0040 ,
HIDEWINDOW = 0x0080 ,
NOCOPYBITS = 0x0100 ,
NOOWNERZORDER = 0x0200 ,
NOSENDCHANGING = 0x0400 ,
}
public override void OnApplyTemplate()
{
System.IntPtr handle = ( new WindowInteropHelper( this ) ).Handle;
HwndSource.FromHwnd( handle ).AddHook( new HwndSourceHook( WindowProc ) );
}
private System.IntPtr WindowProc( System.IntPtr hwnd , int msg , System.IntPtr wParam , System.IntPtr lParam , ref bool handled )
{
switch ( msg )
{
case 0x0024:
{
WmGetMinMaxInfo( hwnd , lParam );
handled = true;
break;
}
case 0x0046:
{
WINDOWPOS pos = ( WINDOWPOS )Marshal.PtrToStructure( lParam , typeof( WINDOWPOS ) );
if ( ( pos.flags & ( int )(SWP.NOMOVE) ) != 0 )
{
return IntPtr.Zero;
}
Window wnd = ( Window )HwndSource.FromHwnd( hwnd ).RootVisual;
if ( wnd == null )
{
return IntPtr.Zero;
}
bool changedPos = false;
if ( pos.cx < MinWidth ) { pos.cx = (int)MinWidth; changedPos = true; }
if ( pos.cy < MinHeight ) { pos.cy = ( int )MinHeight; changedPos = true; }
if ( !changedPos )
{
return IntPtr.Zero;
}
Marshal.StructureToPtr( pos , lParam , true );
handled = true;
break;
}
}
return ( System.IntPtr )0;
}
private void WmGetMinMaxInfo( System.IntPtr hwnd , System.IntPtr lParam )
{
MINMAXINFO mmi = ( MINMAXINFO )Marshal.PtrToStructure( lParam , typeof( MINMAXINFO ) );
int MONITOR_DEFAULTTONEAREST = 0x00000002;
System.IntPtr monitor = MonitorFromWindow( hwnd , MONITOR_DEFAULTTONEAREST );
if ( monitor != System.IntPtr.Zero )
{
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo( monitor , monitorInfo );
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.x = Math.Abs( rcWorkArea.left - rcMonitorArea.left );
mmi.ptMaxPosition.y = Math.Abs( rcWorkArea.top - rcMonitorArea.top );
mmi.ptMaxSize.x = Math.Abs( rcWorkArea.right - rcWorkArea.left );
mmi.ptMaxSize.y = Math.Abs( rcWorkArea.bottom - rcWorkArea.top );
}
Marshal.StructureToPtr( mmi , lParam , true );
}
[StructLayout( LayoutKind.Sequential )]
public struct POINT
{
public int x;
public int y;
public POINT( int x , int y )
{
this.x = x;
this.y = y;
}
}
[StructLayout( LayoutKind.Sequential )]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
[StructLayout( LayoutKind.Sequential , CharSet = CharSet.Auto )]
public class MONITORINFO
{
public int cbSize = Marshal.SizeOf( typeof( MONITORINFO ) );
public RECT rcMonitor = new RECT();
public RECT rcWork = new RECT();
public int dwFlags = 0;
}
[StructLayout( LayoutKind.Sequential , Pack = 0 )]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public static readonly RECT Empty = new RECT();
public int Width
{
get { return Math.Abs( right - left ); }
}
public int Height
{
get { return bottom - top; }
}
public RECT( int left , int top , int right , int bottom )
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public RECT( RECT rcSrc )
{
this.left = rcSrc.left;
this.top = rcSrc.top;
this.right = rcSrc.right;
this.bottom = rcSrc.bottom;
}
public bool IsEmpty
{
get
{
return left >= right || top >= bottom;
}
}
public override string ToString()
{
if ( this == RECT.Empty ) { return "RECT {Empty}"; }
return "RECT { left : " + left + " / top : " + top + " / right : " + right + " / bottom : " + bottom + " }";
}
public override bool Equals( object obj )
{
if ( !( obj is Rect ) ) { return false; }
return ( this == ( RECT )obj );
}
public override int GetHashCode()
{
return left.GetHashCode() + top.GetHashCode() + right.GetHashCode() + bottom.GetHashCode();
}
public static bool operator ==( RECT rect1 , RECT rect2 )
{
return ( rect1.left == rect2.left && rect1.top == rect2.top && rect1.right == rect2.right && rect1.bottom == rect2.bottom );
}
public static bool operator !=( RECT rect1 , RECT rect2 )
{
return !( rect1 == rect2 );
}
}
[DllImport( "user32" )]
internal static extern bool GetMonitorInfo( IntPtr hMonitor , MONITORINFO lpmi );
[DllImport( "User32" )]
internal static extern IntPtr MonitorFromWindow( IntPtr handle , int flags );
[StructLayout( LayoutKind.Sequential )]
internal struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
private void InitializeWindowSource( object sender , EventArgs e )
{
hwndSource = PresentationSource.FromVisual( ( Visual )sender ) as HwndSource;
}
public enum ResizeDirection
{
Left = 1 ,
Right = 2 ,
Top = 3 ,
TopLeft = 4 ,
TopRight = 5 ,
Bottom = 6 ,
BottomLeft = 7 ,
BottomRight = 8 ,
}
[DllImport( "user32" , CharSet = CharSet.Auto )]
private static extern IntPtr SendMessage( IntPtr hWnd , uint Msg , IntPtr wParam , IntPtr lParam );
private void ResizeWindow( ResizeDirection direction )
{
SendMessage( hwndSource.Handle , WM_SYSCOMMAND , ( IntPtr )( 61440 + direction ) , IntPtr.Zero );
}
And do not forget to add this line to your window's constructor, otherwise nothing shall work:
SourceInitialized += new EventHandler( InitializeWindowSource );

Categories

Resources