my Problem is, that i want to draw a rectangle over an existing Textbox.
I have a solution now, but the Textbox is always redrawing, which is a behavior i do not want.
here is the code
private bool isDragging = false;
void Form2_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
endPos = e.Location;
Rectangle rect;
if (endPos.Y > startPos.Y)
{
rect = new Rectangle(startPos.X, startPos.Y,
endPos.X - startPos.X, endPos.Y - startPos.Y);
}
else
{
rect = new Rectangle(endPos.X, endPos.Y,
startPos.X - endPos.X, startPos.Y - endPos.Y);
}
Region dragRegion = new Region(rect);
this.Invalidate();
}
}
void Form2_MouseUp(object sender, MouseEventArgs e)
{
isDragging = false;
this.Invalidate();
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp;
cp = base.CreateParams;
cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
return cp;
}
}
void Form2_MouseDown(object sender, MouseEventArgs e)
{
isDragging = true;
startPos = e.Location;
}
// this is where we intercept the Paint event for the TextBox at the OS level
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 15: // this is the WM_PAINT message
// invalidate the TextBox so that it gets refreshed properly
Input.Invalidate();
// call the default win32 Paint method for the TextBox first
base.WndProc(ref m);
// now use our code to draw extra stuff over the TextBox
break;
default:
base.WndProc(ref m);
break;
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (isDragging)
{
using (Pen p = new Pen(Color.Gray))
{
p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawRectangle(p,
startPos.X, startPos.Y,
endPos.X - startPos.X, endPos.Y - startPos.Y);
}
}
base.OnPaint(e);
}
the problem here is, the textbox is flickering, and the Rectangle is not set in front of the textbox after finish dragging.
How do i solve this ?
You are turning off the flag: WS_CLIPCHILDREN in WndProc.
WS_CLIPCHILDREN value is 0x02000000 and you are turning it off in your code:
cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
WS_CLIPCHILDREN flag is set by default, it prevents flickering of child controls. If you will not turn off the flag, the flickering will stop.
According to the documentation on WS_CLIPCHILDREN: Excludes the area occupied by child windows when drawing occurs within the parent window. This style is used when creating the parent window.
Side Note: when turning off a flag, it is more clear to use the flag value you want to turn off:
cp.Style &= ~0x02000000; //WS_CLIPCHILDREN
Related
I want to select some structures (polygons and rectangles) in a panel. I have used the new ExtendedPanel Class, for the opacity of the mouse panel.
panel1 is for the structure, extendedPanel1 is for the selected area of the mouse. (SetSelectionRect() ist the set of the selections area)
In the figure below, the red is the graph I drew on panel1, and the green is the rectangle selected by the mouse. In fact, it should present a green rectangle, which is the rectangle when the mouse selection ends, but now there are many. This shows that the transparency setting in the extendetPanel1 works after extendedPanel1.Invalidate();, but the historical rectangle drawn does not disappear.
Can you please tell me, how should I write the code for the mouse selection in the Panel?
I actually want to realize some polygons and editing. I drew some polygons (rectangles) in panel1, and now I want to use the mouse to select some parts and make some changes (such as deleting some polygons).
My thoughts on this are: Draw the polygons on panel1, and panel2 displays the selection by the mouse, but the bottom of panel2 is transparent.
Then, according to the coordinate calculation, etc., it is judged whether the geometric figure in panel1 is in the area selected in panel2. If it is, then I will delete it. I don’t know if my thoughts are reasonable.
If you can provide a suitable solution, I am very grateful.
code of extendetpanel:
public class ExtendedPanel : Panel
{
private const int WS_EX_TRANSPARENT = 0x20;
public ExtendedPanel()
{
SetStyle(ControlStyles.Opaque, true);
}
private int opacity = 0;
[DefaultValue(0)]
public int Opacity
{
get
{
return this.opacity;
}
set
{
if (value < 0 || value > 100)
throw new ArgumentException("value must be between 0 and 100");
this.opacity = value;
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | WS_EX_TRANSPARENT;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
using (var brush = new SolidBrush(Color.FromArgb(this.opacity * 255 / 100, this.BackColor)))
{
e.Graphics.FillRectangle(brush, this.ClientRectangle);
}
base.OnPaint(e);
}
}
Code of paint and events:
private void extendedPanel1_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
extendedPanel1.Opacity = 0;
if (mouseDown)
{
using (Pen pen = new Pen(Color.Green, 1F))
{
pen.DashStyle = DashStyle.Dash;
e.Graphics.DrawRectangle(pen, selection);
}
}
}
private void extendedPanel1_MouseDown(object sender, MouseEventArgs e)
{
selectionStart = extendedPanel1.PointToClient(MousePosition);
mouseDown = true;
}
private void extendedPanel1_MouseUp(object sender, MouseEventArgs e)
{
mouseDown = false;
SetSelectionRect();
extendedPanel1.Invalidate();
}
private void extendedPanel1_MouseMove(object sender, MouseEventArgs e)
{
if (!mouseDown)
return;
selectionEnd = extendedPanel1.PointToClient(MousePosition);
SetSelectionRect();
extendedPanel1.Invalidate();
}
I have a custom user control in my program. It is a panel which needs to be resizable from the left-hand side. Here is my code:
private void ResizePanel_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && e.X == ClientRectangle.Left)
{
resizeMode = true;
}
}
private void ResizePanel_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
resizeMode = false;
}
}
private void ResizePanel_MouseMove(object sender, MouseEventArgs e)
{
if (resizeMode == true)
{
Size newSize = new Size();
newSize.Height = Height;
newSize.Width = Math.Abs(e.X - ClientRectangle.Left); // Distance between the mouse position and
// left side of the panel
if (e.X < this.ClientRectangle.Left)
{
Width = newSize.Width;
Left -= newSize.Width;
}
}
}
In theory, the solution would be to move the panel to the left by the new width as the width increases. That's what this code is supposed to do. The problem with it at the moment is that as the panel moves to the left, the width stays the same and doesn't increase. Is there a way to do this so I can grab the control on the left side and drag to the left, so the size increases and it appears to stay in place?
Here's a quick and dirty panel that can only be dragged from the left side:
class LeftSideResizePanel : Panel
{
private const int HTLEFT = 10;
private const int HTCLIENT = 1;
private const int WM_NCHITTEST = 0x0084;
private const int WS_THICKFRAME = 0x40000;
protected override CreateParams CreateParams
{
get
{
var cp = base.CreateParams;
cp.Style |= WS_THICKFRAME;
return cp;
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCHITTEST:
Point pt = this.PointToClient(new Point(m.LParam.ToInt32()));
Rectangle hit = new Rectangle(new Point(-3, 0), new Size(6, this.Height));
m.Result = hit.Contains(pt) ? (IntPtr)HTLEFT : (IntPtr)HTCLIENT;
return;
}
base.WndProc(ref m);
}
}
If you don't like the look that WS_THICKFRAME gives, you'll have to go for finer control using a method as described in Jimi's comment.
I am trying to create a swype like keyboard in Windows Forms using c#
I have two problems:
a. I am not able to reproduce the finger swipe action.
b. I am not able to recognize the letters under different keys.
For the first problem:
I used the Graphics Class and the Pen class and used it like this :
bool draw;
Graphics g;
Pen p;
int prevX;
int prevY;
private void Form1_Load(object sender, EventArgs e)
{
draw = false;
g = CreateGraphics();
p = new Pen(Color.Black, 2);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
draw = true;
prevX = e.X;
prevY = e.Y;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
draw = false;
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (draw)
{
g.DrawLine(p, prevX, prevY, e.X, e.Y);
prevX = e.X;
prevY = e.Y;
}
}
But the problem here is that it starts drawing from the Left-top corner of the screen and not from the position of the button. It also does't draw over the buttons. Any control that is in the path overlaps the line.
For the second Issue:
a. I used the mouse down event to set a boolean value "Allow" as true and then when the mouse moves I tried getting the text of the button. Just as in the above picture. I tried to start from letter "S" and as I move over other letters only the letter "S" keeps recording. The Mouse Enter or Mouse Hover event of the other methods do not even occur.
b. I also tried using dragdrop event but it doesn't work too. I am not sure but I am assuming that as buttons are not draggable objects the dragdrop event doesn't work.
I don't understand or am confused how to achieve my goal.
You can create a custom control and handle its drawing yourself.
But if you are going to use Button controls and want to draw over those buttons, you can put a transparent panel as overlay on those buttons and draw the swipe path over the transparent panel:
Transparent Panel
using System.Windows.Forms;
public class TransparentPanel : Panel
{
const int WS_EX_TRANSPARENT = 0x20;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | WS_EX_TRANSPARENT;
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
}
Sample Form
This is just an example to demonstrate the usage of transparent control. To do so, it's enough to create a form and replace it's contents with following code:
TransparentPanel overlay;
TableLayoutPanel table;
List<Point> points = new List<Point>();
List<String> keys = new List<string>();
public Form1()
{
overlay = new TransparentPanel() { Dock = DockStyle.Fill };
this.Controls.Add(overlay);
table = new TableLayoutPanel()
{ ColumnCount = 6, RowCount = 4, Dock = DockStyle.Fill };
this.Controls.Add(table);
var list = Enumerable.Range('A', 'Z' - 'A' + 1)
.Select(x => ((char)x).ToString()).ToList();
list.ForEach(x => table.Controls.Add(new Button()
{ Text = x, Width = 32, Height = 32 }));
overlay.MouseDown += OverlayMouseDown;
overlay.MouseMove += OverlayMouseMove;
overlay.MouseUp += OverlayMouseUp;
overlay.Paint += OverlayPaint;
}
void OverlayMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
ProccessPoint(e.Location);
}
void OverlayMouseDown(object sender, MouseEventArgs e)
{
Clear();
ProccessPoint(e.Location);
}
void OverlayMouseUp(object sender, MouseEventArgs e)
{
if (points.Count > 0)
MessageBox.Show(string.Join(",", keys));
Clear();
}
void OverlayPaint(object sender, PaintEventArgs e)
{
if (points.Count >= 3)
e.Graphics.DrawCurve(Pens.Red, points.ToArray());
}
void ProccessPoint(Point p)
{
points.Add(p);
var c = table.Controls.Cast<Control>()
.Where(x => table.RectangleToScreen(x.Bounds)
.Contains(overlay.PointToScreen(p))).FirstOrDefault();
if ((c != null) && (keys.Count == 0 || keys[keys.Count - 1] != c.Text))
keys.Add(c.Text);
overlay.Invalidate();
}
void Clear()
{
keys.Clear();
points.Clear();
this.Refresh();
}
I'm trying to make a simple paint program in C#, but it keeps flickering when I'm drawing, like I need some kind of double buffering, but I don't know how to do it.
I am drawing on a Panel and I'm using a Bitmap to store the graphics.
Here's my code:
public partial class Form1 : Form
{
private Bitmap drawing;
private bool leftDown = false;
private int prevX;
private int prevY;
private int currentX;
private int currentY;
public Form1()
{
InitializeComponent();
drawing = new Bitmap(panel1.Width, panel1.Height);
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
leftDown = true;
prevX = e.X;
prevY = e.Y;
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (leftDown)
{
Graphics g = Graphics.FromImage(drawing);
currentX = e.X;
currentY = e.Y;
g.DrawLine(new Pen(Color.Black), new Point(currentX, currentY), new Point(prevX, prevY));
panel1.Invalidate();
prevX = currentX;
prevY = currentY;
g.Dispose();
}
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
leftDown = false;
}
private void panel1_MouseLeave(object sender, EventArgs e)
{
leftDown = false;
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImageUnscaled(drawing, 0, 0);
}
}
You not only should turn DoubleBuffered to true, but also use PictureBox instead of Panel and draw on it. This should solve your issue :).
You need to subclass Panel to do this, because you need to override certain things. A Panel like this should work:
class DoubleBufferedPanel : Panel {
public DoubleBufferedPanel() : base() {
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.DoubleBuffered |
ControlStyles.Opaque |
ControlStyles.OptimizedDoubleBuffer, true);
}
public override void OnPaint(PaintEventArgs e) {
// Do your painting *here* instead, and don't call the base method.
}
// Override OnMouseMove, etc. here as well.
}
However, you don't need the functionality Panel adds to Control at all, that is for it to be functioning as a container. So, in fact, you should be inheriting from Control unless you need subcontrols.
Another improvement might be to only Invalidate with the Rectangle that changed. This will repaint one area and reduce drawing time. You can also pass a srcRect to Graphics.DrawImage, that srcRect being calculated from e.ClipRectangle, for even better performance if subclassing Panel doesn't work for you.
When painting pixel maps in the Paint event, the flickering is often caused by Windows forms because it first draws the background and then the pixel map. So the flicker is the background that becomes visible for a fraction of a second.
You can set the Opaque style in the panel's ControlStyle property. That will turn of the background drawing because Windows Forms now assumes that your code will completely draw the contents of the panel.
Normally, simply adding this.DoubleBuffered = true; in your code should do the trick.
If it does not, add this code to your form :
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
You can also change it using Reflection, but there is no advantage, neither readability or speed.
I am making a CustomControl based on a ToolStripButton control, I am trying to know when the mouse is Hover the button to draw it differently. Here is a quick view of my code :
private bool m_IsHover = false;
...
protected override void OnMouseEnter(EventArgs e)
{
m_IsHover = true;
Debug.WriteLine("Mouse IN");
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
m_IsHover = false;
Debug.WriteLine("Mouse OUT");
base.OnMouseLeave(e);
}
...
protected override void OnPaint(PaintEventArgs e)
{
// Define rectangle used to draw
Rectangle borderRec = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
if (m_IsHover)
{
// Draw border
e.Graphics.DrawRectangle(m_BorderPen, borderRec);
...
}
else
{
// Default draw
base.OnPaint(e);
}
}
My problem is that I clearly see in the debug panel that Mouse IN and Mouse OUT are right, so variable should be correctly set, but in the OnPaint event, I never enter in the m_IsHover conditionnal ...
I really don't understand what the problem is, it seem so easy ...
The ToolStripItem.Select() method runs on MouseEnter. Call this.Invalidate() to force a repaint.