Good time of day.
The problem is very strange and incomprehensible.
After successfully creating the button, and then using it, I noticed a very unpleasant bug, flickering buttons.
Namely, it is played very easily, open the project, and press the ALT button.
For some reason, the buttons blink once, and that's it. I don't understand why this happens and how to fix it.
Please help.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace TestProject
{
public class RoundButton : Control
{
public Color BackColor2 { get; set; }
public Color ButtonBorderColor { get; set; }
public int ButtonRoundRadius { get; set; }
public Color ButtonHighlightColor { get; set; }
public Color ButtonHighlightColor2 { get; set; }
public Color ButtonHighlightForeColor { get; set; }
public Color ButtonPressedColor { get; set; }
public Color ButtonPressedColor2 { get; set; }
public Color ButtonPressedForeColor { get; set; }
private bool IsHighlighted;
private bool IsPressed;
public RoundButton()
{
Size = new Size(100, 40);
ButtonRoundRadius = 30;
BackColor = Color.Gainsboro;
BackColor2 = Color.Silver;
ButtonBorderColor = Color.Black;
ButtonHighlightColor = Color.Orange;
ButtonHighlightColor2 = Color.OrangeRed;
ButtonHighlightForeColor = Color.Black;
ButtonPressedColor = Color.Red;
ButtonPressedColor2 = Color.Maroon;
ButtonPressedForeColor = Color.White;
}
protected override CreateParams CreateParams
{
get
{
CreateParams createParams = base.CreateParams;
createParams.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
return createParams;
}
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
var foreColor = IsPressed ? ButtonPressedForeColor : IsHighlighted ? ButtonHighlightForeColor : ForeColor;
var backColor = IsPressed ? ButtonPressedColor : IsHighlighted ? ButtonHighlightColor : BackColor;
var backColor2 = IsPressed ? ButtonPressedColor2 : IsHighlighted ? ButtonHighlightColor2 : BackColor2;
using (var pen = new Pen(ButtonBorderColor, 1))
e.Graphics.DrawPath(pen, Path);
using (var brush = new LinearGradientBrush(ClientRectangle, backColor, backColor2, LinearGradientMode.Vertical))
e.Graphics.FillPath(brush, Path);
using (var brush = new SolidBrush(foreColor))
{
var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
var rect = ClientRectangle;
rect.Inflate(-4, -4);
e.Graphics.DrawString(Text, Font, brush, rect, sf);
}
base.OnPaint(e);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
IsHighlighted = true;
Parent.Invalidate(Bounds, false);
Invalidate();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
IsHighlighted = false;
IsPressed = false;
Parent.Invalidate(Bounds, false);
Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
IsPressed = true;
Parent.Invalidate(Bounds, false);
Invalidate();
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
Parent.Invalidate(Bounds, false);
Invalidate();
IsPressed = false;
}
protected GraphicsPath Path
{
get
{
var rect = ClientRectangle;
rect.Inflate(-1, -1);
return GetRoundedRectangle(rect, ButtonRoundRadius);
}
}
public static GraphicsPath GetRoundedRectangle(Rectangle rect, int d)
{
var gp = new GraphicsPath();
gp.AddArc(rect.X, rect.Y, d, d, 180, 90);
gp.AddArc(rect.X + rect.Width - d, rect.Y, d, d, 270, 90);
gp.AddArc(rect.X + rect.Width - d, rect.Y + rect.Height - d, d, d, 0, 90);
gp.AddArc(rect.X, rect.Y + rect.Height - d, d, d, 90, 90);
gp.CloseFigure();
return gp;
}
}
}
When pressing the ALT key the OnPaint() event gets called and the control is redrawn (if you put a breakpoint you will see it).
It comes from the base class of Control class.
you can solve this by adding this method to the parent form of the custom control:
protected override void WndProc(ref Message msg)
{
if (msg.Msg == 0x128) return;
base.WndProc(ref msg);
}
I guess the reason is that ALT key is used for other functionality of Control,
for example, selecting a menu item from MenuStrip control.
Related
I'm trying to change the border color of a ListBox.
I made this custom control where i have a function that makes the border by drawing.
After had it working, noticed that can't use ListBox.Items anymore, methods such as .Add() or .Clear().
Code of the Custom ListBox:
class CustomListBox : UserControl
{
//Fields
private Color borderColor = Color.MediumSlateBlue;
private int borderSize = 1;
//Items
private ListBox Listb;
//Properties
[Category("Custom")]
public Color BorderColor
{
get { return borderColor; }
set
{
borderColor = value;
}
}
[Category("Custom")]
public int BorderSize
{
get { return borderSize; }
set
{
borderSize = value;
this.Padding = new Padding(borderSize);//Border Size
AdjustListBoxDimensions();
}
}
public CustomListBox()
{
Listb = new ListBox();
this.SuspendLayout();
// ListBox
Listb.BorderStyle = BorderStyle.None;
Listb.DrawMode = DrawMode.OwnerDrawFixed;
Listb.ForeColor = Color.FromArgb(((int)(((byte)(249)))), ((int)(((byte)(249)))), ((int)(((byte)(249)))));
Listb.FormattingEnabled = true;
Listb.ItemHeight = 24;
Listb.Location = new Point(567, 64);
Listb.Name = "CustomListBox";
Listb.Size = new Size(235, 936);
Listb.DrawItem += new DrawItemEventHandler(Listb_DrawItem);
this.MinimumSize = new Size(200, 30);
this.Size = new Size(200, 30);
this.Padding = new Padding(borderSize);//Border Size
this.Font = new Font(this.Font.Name, 12F);
this.ResumeLayout();
AdjustListBoxDimensions();
}
// Highlight event
private void Listb_DrawItem(object sender, DrawItemEventArgs e)
{
Color backgroundColor = Color.FromArgb(50, 50, 50);
Color horizontalColor = Color.FromArgb(100, 100, 100);
if (e.Index >= 0)
{
SolidBrush sb = new SolidBrush(((e.State & DrawItemState.Selected) == DrawItemState.Selected) ? horizontalColor : backgroundColor);
e.Graphics.FillRectangle(sb, e.Bounds);
string text = Listb.Items[e.Index].ToString();
SolidBrush tb = new SolidBrush(e.ForeColor);
e.Graphics.DrawString(text, e.Font, tb, Listb.GetItemRectangle(e.Index).Location);
}
}
//Adjust Dimension (Still in test)
private void AdjustListBoxDimensions()
{
Listb.Location = new Point()
{
X = this.Width - this.Padding.Right - Listb.Width,
Y = Listb.Height
};
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics graph = e.Graphics;
//Draw border
using (Pen penBorder = new Pen(borderColor, borderSize))
{
penBorder.Alignment = PenAlignment.Inset;
graph.DrawRectangle(penBorder, 0, 0, this.Width - 0.5F, this.Height - 0.5F);
}
}
}
My problem is that i can't use the ListBox properties/methods, is there a way to inherit them?
Since you've made your own control, you have to make your own properties and methods, calling Listbox's methods and getters/setters
something like this:
public void Add(object item)
{
this.Listb.Items.Add(item);
}
public ObjectCollection ListItems
{
get
{
return this.Listb.Items;
}
set
{
this.Listb.Items.AddRange(value);
}
}
//etc...
public void Add(object item)
{
this.Listb.BeginUpdate();
this.Listb.Items.Add(item);
this.Listb.EndUpdate();
}
public void Clear()
{
this.Listb.BeginUpdate();
this.Listb.Items.Clear();
this.Listb.EndUpdate();
}
public int SelectedIndex()
{
return this.Listb.SelectedIndex;
}
public object Item(int index)
{
return this.Listb.Items[index];
}
Add to code:
Dock=Fill
This.control.add(listb)
Is there a way to drawstring and then remove it?
I've used following classes to Undo/Redo Rectangle, Circle, Line, Arrow type shapes but cant figure how i can remove drawn string.
https://github.com/Muhammad-Khalifa/Free-Snipping-Tool/blob/master/Free%20Snipping%20Tool/Operations/UndoRedo.cs
https://github.com/Muhammad-Khalifa/Free-Snipping-Tool/blob/master/Free%20Snipping%20Tool/Operations/Shape.cs
https://github.com/Muhammad-Khalifa/Free-Snipping-Tool/blob/master/Free%20Snipping%20Tool/Operations/ShapesTypes.cs
Here is how i'm adding Rectangle in shape list: This works well when i undo or redo from the list.
DrawString
Shape shape = new Shape();
shape.shape = ShapesTypes.ShapeTypes.Rectangle;
shape.CopyTuplePoints(points);
shape.X = StartPoint.X;
shape.Y = StartPoint.Y;
shape.Width = EndPoint.X;
shape.Height = EndPoint.Y;
Pen pen = new Pen(new SolidBrush(penColor), 2);
shape.pen = pen;
undoactions.AddShape(shape);
This is how i'm drawing text:
var fontFamily = new FontFamily("Calibri");
var font = new Font(fontFamily, 12, FontStyle.Regular, GraphicsUnit.Point);
Size proposedSize = new Size(int.MaxValue, int.MaxValue);
TextFormatFlags flags = TextFormatFlags.WordEllipsis | TextFormatFlags.NoPadding | TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.WordBreak;
Size size = TextRenderer.MeasureText(e.Graphics, textAreaValue, font, proposedSize, flags);
Shape shape = new Shape();
shape.shape = ShapesTypes.ShapeTypes.Text;
shape.X = ta.Location.X;
shape.Y = ta.Location.Y;
shape.Width = size.Width;
shape.Height = size.Height;
shape.Value = textAreaValue;
Pen pen = new Pen(new SolidBrush(penColor), 2);
shape.pen = pen;
undoactions.AddShape(shape);
But this does not work with undo-redo list. Maybe problem is with pen and font-size but i cant figure it out how to use pen with DrawString.
Edit:
Here's how i'm drawing in paint event
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
foreach (var item in undoactions.lstShape)
{
if (item.shape == ShapesTypes.ShapeTypes.Line)
{
e.Graphics.DrawLine(item.pen, item.X, item.Y, item.Width, item.Height);
}
else if (item.shape == ShapesTypes.ShapeTypes.Pen)
{
if (item.Points.Count > 1)
{
e.Graphics.DrawCurve(item.pen, item.Points.ToArray());
}
}
else if (item.shape == ShapesTypes.ShapeTypes.Text)
{
var fontFamily = new FontFamily("Calibri");
var font = new Font(fontFamily, 12, FontStyle.Regular, GraphicsUnit.Point);
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
e.Graphics.DrawString(item.Value, font, new SolidBrush(item.pen.Color), new PointF(item.X, item.Y));
}
}
}
Shape.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Drawing
{
public class Shape : ICloneable
{
public ShapesTypes.ShapeTypes shape { get; set; }
public List<Point> Points { get; }
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public Pen pen { get; set; }
public String Value { get; set; }
public Shape()
{
Points = new List<Point>();
}
public void CopyPoints(List<Point> points)
{
for (int i = 0; i < points.Count; i++)
{
Point p = new Point();
p.X = points[i].X;
p.Y = points[i].Y;
Points.Add(p);
}
}
public void CopyCopyPoints(List<List<Point>> points)
{
for (int j = 0; j < points.Count; j++)
{
List<Point> current = points[j];
for (int i = 0; i < current.Count; i++)
{
Point p = new Point();
p.X = current[i].X;
p.Y = current[i].Y;
Points.Add(p);
}
}
}
public void CopyTuplePoints(List<Tuple<Point, Point>> points)
{
foreach (var line in points)
{
Point p = new Point();
p.X = line.Item1.X;
p.Y = line.Item1.Y;
Points.Add(p);
p.X = line.Item2.X;
p.Y = line.Item2.Y;
Points.Add(p);
}
}
public object Clone()
{
Shape shp = new Shape();
shp.X = X;
shp.Y = Y;
shp.Width = Width;
shp.Height = Height;
shp.pen = pen;
shp.shape = shape;
shp.Value = Value;
for (int i = 0; i < Points.Count; i++)
{
shp.Points.Add(new Point(Points[i].X, Points[i].Y));
}
return shp;
}
}
}
DrawCircle
if (currentshape == ShapesTypes.ShapeTypes.Circle)
{
Shape shape = new Shape();
shape.shape = ShapesTypes.ShapeTypes.Circle;
shape.CopyTuplePoints(cLines);
shape.X = StartPoint.X;
shape.Y = StartPoint.Y;
shape.Width = EndPoint.X;
shape.Height = EndPoint.Y;
Pen pen = new Pen(new SolidBrush(penColor), 2);
shape.pen = pen;
undoactions.AddShape(shape);
}
Undo
if (currentshape != ShapesTypes.ShapeTypes.Undo)
{
oldshape = currentshape;
currentshape = ShapesTypes.ShapeTypes.Undo;
}
if (undoactions.lstShape.Count > 0)
{
undoactions.Undo();
this.Invalidate();
}
if (undoactions.redoShape.Count > 0)
{
btnRedo.Enabled = true;
}
UndoRedo
public class UndoRedo
{
public List<Shape> lstShape = new List<Shape>();
public List<Shape> redoShape = new List<Shape>();
public void AddShape(Shape shape)
{
lstShape.Add(shape);
}
public void Undo()
{
redoShape.Add((Shape)lstShape[lstShape.Count - 1].Clone());
lstShape.RemoveAt(lstShape.Count - 1);
}
public void Redo()
{
lstShape.Add((Shape)redoShape[redoShape.Count - 1].Clone());
redoShape.RemoveAt(redoShape.Count - 1);
}
}
you can create a TextShape deriving from Shape, having Text, Font, Location and Color properties and treat it like other shapes, so redo and undo will not be a problem.
Here are some tips which will help you to solve the problem:
Create a base Shape class or interface containing basic methods like Draw, Clone, HitTest, etc.
All shapes, including TextShape should derive from Shape. TextShape is also a shape, having Text, Font, Location and Color properties.
Each implementation of Shape has its implementation of base methods.
Implement INotifyPropertyChanged in all your shapes, then you can listen to changes of properties and for example, add something to undo buffer after change of color, border width, etc.
Implement IClonable or base class Clone method. All shapes should be clonable when adding to undo buffer.
Do dispose GDI objects like Pen and Brush. It's not optional.
Instead of adding a single shape to undo buffer, create a class like drawing context containing List of shapes, Background color of drawing surface and so on. Also in this class implement INotifyPropertyChanged, then by each change in the shapes or this class properties, you can add a clone of this class to undo buffer.
Shape
Here is an example of Shapeclass:
public abstract class Shape : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string name = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public abstract void Draw(Graphics g);
public abstract Shape Clone();
}
TextShape
Pay attention to the implementation of properties to raise PropertyChanged event and also Clone method to clone the object for undo buffer, also the way that GDI object have been used in Draw:
public class TextShape : Shape {
private string text;
public string Text {
get { return text; }
set {
if (text != value) {
text = value;
OnPropertyChanged();
}
}
}
private Point location;
public Point Location {
get { return location; }
set {
if (!location.Equals(value)) {
location = value;
OnPropertyChanged();
}
}
}
private Font font;
public Font Font {
get { return font; }
set {
if (font!=value) {
font = value;
OnPropertyChanged();
}
}
}
private Color color;
public Color Color {
get { return color; }
set {
if (color!=value) {
color = value;
OnPropertyChanged();
}
}
}
public override void Draw(Graphics g) {
using (var brush = new SolidBrush(Color))
g.DrawString(Text, Font, brush, Location);
}
public override Shape Clone() {
return new TextShape() {
Text = Text,
Location = Location,
Font = (Font)Font.Clone(),
Color = Color
};
}
}
DrawingContext
This class in fact contains all shapes and some other properties like back color of drawing surface. This is the class which you need to add its clone to undo buffer:
public class DrawingContext : INotifyPropertyChanged {
public DrawingContext() {
BackColor = Color.White;
Shapes = new BindingList<Shape>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string name = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private Color backColor;
public Color BackColor {
get { return backColor; }
set {
if (!backColor.Equals(value)) {
backColor = value;
OnPropertyChanged();
}
}
}
private BindingList<Shape> shapes;
public BindingList<Shape> Shapes {
get { return shapes; }
set {
if (shapes != null)
shapes.ListChanged -= Shapes_ListChanged;
shapes = value;
OnPropertyChanged();
shapes.ListChanged += Shapes_ListChanged;
}
}
private void Shapes_ListChanged(object sender, ListChangedEventArgs e) {
OnPropertyChanged("Shapes");
}
public DrawingContext Clone() {
return new DrawingContext() {
BackColor = this.BackColor,
Shapes = new BindingList<Shape>(this.Shapes.Select(x => x.Clone()).ToList())
};
}
}
DrawingSurface
This class is in fact the control which has undo and redo functionality and also draws the current drawing context on its surface:
public class DrawingSurface : Control {
private Stack<DrawingContext> UndoBuffer = new Stack<DrawingContext>();
private Stack<DrawingContext> RedoBuffer = new Stack<DrawingContext>();
public DrawingSurface() {
DoubleBuffered = true;
CurrentDrawingContext = new DrawingContext();
UndoBuffer.Push(currentDrawingContext.Clone());
}
DrawingContext currentDrawingContext;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false)]
public DrawingContext CurrentDrawingContext {
get {
return currentDrawingContext;
}
set {
if (currentDrawingContext != null)
currentDrawingContext.PropertyChanged -= CurrentDrawingContext_PropertyChanged;
currentDrawingContext = value;
Invalidate();
currentDrawingContext.PropertyChanged += CurrentDrawingContext_PropertyChanged;
}
}
private void CurrentDrawingContext_PropertyChanged(object sender, PropertyChangedEventArgs e) {
UndoBuffer.Push(CurrentDrawingContext.Clone());
RedoBuffer.Clear();
Invalidate();
}
public void Undo() {
if (CanUndo) {
RedoBuffer.Push(UndoBuffer.Pop());
CurrentDrawingContext = UndoBuffer.Peek().Clone();
}
}
public void Redo() {
if (CanRedo) {
CurrentDrawingContext = RedoBuffer.Pop();
UndoBuffer.Push(CurrentDrawingContext.Clone());
}
}
public bool CanUndo {
get { return UndoBuffer.Count > 1; }
}
public bool CanRedo {
get { return RedoBuffer.Count > 0; }
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var brush = new SolidBrush(CurrentDrawingContext.BackColor))
e.Graphics.FillRectangle(brush, ClientRectangle);
foreach (var shape in CurrentDrawingContext.Shapes)
shape.Draw(e.Graphics);
}
}
In the future, please follow the guidelines for a Minimal, Complete, and Verifiable example. This will help us to help you. For example, you could have excluded all of the code related to cloning, since it's not related to your problem.
I refactored your code a little and created a small, reproducible example. This example works with the general approach you outlined, so I can't tell you exactly why your code doesn't work unless you could also post a similar example that I can copy / paste into my environment. Please do not link to external code - it must be hosted here.
I refactored it to highlight some language features which could help to make your code more maintainable. Please let me know if you have any questions about what I put here. Please let me know if this helps. If not, please use it as a template and replace my code with yours so I can assist you.
public partial class Form1 : Form
{
private EntityBuffer _buffer = new EntityBuffer();
private System.Windows.Forms.Button btnUndo;
private System.Windows.Forms.Button btnRedo;
public Form1()
{
this.btnUndo = new System.Windows.Forms.Button();
this.btnRedo = new System.Windows.Forms.Button();
this.SuspendLayout();
this.btnUndo.Location = new System.Drawing.Point(563, 44);
this.btnUndo.Name = "btnUndo";
this.btnUndo.Size = new System.Drawing.Size(116, 29);
this.btnUndo.TabIndex = 0;
this.btnUndo.Text = "Undo";
this.btnUndo.UseVisualStyleBackColor = true;
this.btnUndo.Click += new System.EventHandler(this.btnUndo_Click);
this.btnRedo.Location = new System.Drawing.Point(563, 79);
this.btnRedo.Name = "btnRedo";
this.btnRedo.Size = new System.Drawing.Size(116, 29);
this.btnRedo.TabIndex = 0;
this.btnRedo.Text = "Redo";
this.btnRedo.UseVisualStyleBackColor = true;
this.btnRedo.Click += new System.EventHandler(this.btnRedo_Click);
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.btnRedo);
this.Controls.Add(this.btnUndo);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
protected override void OnLoad(EventArgs e)
{
_buffer.Add(new Rectangle(10, 10, 10, 10, Color.Red));
_buffer.Add(new Rectangle(20, 20, 10, 10, Color.Red));
_buffer.Add(new Rectangle(30, 30, 10, 10, Color.Red));
_buffer.Add(new Text(40, 40, "Test", Color.Black));
_buffer.Add(new Rectangle(50, 50, 10, 10, Color.Red));
_buffer.Add(new Text(60, 60, "Test", Color.Black));
base.OnLoad(e);
}
protected override void OnPaint(PaintEventArgs e)
{
foreach (var entity in _buffer.Entities)
entity.Draw(e.Graphics);
base.OnPaint(e);
}
private void btnUndo_Click(object sender, EventArgs e)
{
if (!_buffer.CanUndo)
return;
_buffer.Undo();
Invalidate();
}
private void btnRedo_Click(object sender, EventArgs e)
{
if (!_buffer.CanRedo)
return;
_buffer.Redo();
Invalidate();
}
}
public abstract class Entity
{
public int X { get; set; }
public int Y { get; set; }
public Color Color { get; set; }
public abstract void Draw(Graphics g);
public Entity(int x, int y, Color color)
{
X = x;
Y = y;
Color = color;
}
}
public class Text : Entity
{
private static Font _font = new Font(new FontFamily("Calibri"), 12, FontStyle.Regular, GraphicsUnit.Point);
public string Value { get; set; }
public Text(int x, int y, string value, Color color) : base(x,y,color) => Value = value;
public override void Draw(Graphics g) => g.DrawString(Value, _font, new SolidBrush(Color), new PointF(X, Y));
}
public abstract class Shape : Entity
{
public int Width { get; set; }
public int Height { get; set; }
public Pen Pen { get; set; }
public Shape(int x, int y, int width, int height, Color color) : base(x, y, color)
{
Width = width;
Height = height;
}
}
public class Rectangle : Shape
{
public Rectangle(Point start, Point end, Color color) : this(start.X, start.Y, end.X, end.Y, color) { }
public Rectangle(int x, int y, int width, int height, Color color) : base(x, y, width, height, color) { }
public override void Draw(Graphics g) => g.DrawRectangle(new Pen(new SolidBrush(Color)), X, Y, Width, Height);
}
public class EntityBuffer
{
public Stack<Entity> Entities { get; set; } = new Stack<Entity>();
public Stack<Entity> RedoBuffer { get; set; } = new Stack<Entity>();
public bool CanRedo => RedoBuffer.Count > 0;
public bool CanUndo => Entities.Count > 0;
public void Add(Entity entity)
{
Entities.Push(entity);
RedoBuffer.Clear();
}
public void Undo() => RedoBuffer.Push(Entities.Pop());
public void Redo() => Entities.Push(RedoBuffer.Pop());
}
I have done a similar kind of project, after drawing shapes and writing it's dimension as a string on images; after pressing Ctrl-Z/Ctrl-Y it does undo/redo the operations performed on images.
Here is a link to my Github project, a C# win-form soln. After running the soln, tool usage instruction will get appear on the tool itself.
Hope this helps you...
I am developing a custom combobox control dervied from the standard combobox that will look flat and borderless based on the article here:
http://www.codeproject.com/Articles/6971/Making-Standard-ComboBox-appear-flat
According to various sources I will have to override the windows procedure to do that so here is my WndProc override where I have blocked several messages from reaching the control:
protected override void WndProc(ref Message m)
{
IntPtr hDC = IntPtr.Zero;
Graphics gdc = null;
switch (m.Msg)
{
// Block these messages
case WM_CHANGEUISTATE:
case WM_MOUSEACTIVATE:
case WM_MOUSEFIRST:
case WM_MOUSELAST:
case WM_MOUSEHOVER:
case WM_MOUSELEAVE:
m.Result = (IntPtr)1;
break;
// Here we paint the border when non-client paint is received
case WM_NC_PAINT:
hDC = GetWindowDC(this.Handle);
gdc = Graphics.FromHdc(hDC);
SendMessage(this.Handle, WM_ERASEBKGND, hDC, 0);
SendPrintClientMsg(); // Send to draw client area
PaintFlatControlBorder(this, gdc);
m.Result = (IntPtr) 1; // Indicate message has been processed
ReleaseDC(m.HWnd, hDC);
gdc.Dispose();
break;
case WM_PAINT:
base.WndProc(ref m);
// Flatten the border area again
hDC = GetWindowDC(this.Handle);
gdc = Graphics.FromHdc(hDC);
Pen p = new Pen((this.Enabled? BackColor:SystemColors.Control), 2);
gdc.DrawRectangle(p, new Rectangle(2, 2, this.Width-3, this.Height-3));
PaintFlatDropDown(this, gdc);
PaintFlatControlBorder(this, gdc);
ReleaseDC(m.HWnd, hDC);
gdc.Dispose();
break;
default:
base.WndProc(ref m);
break;
}
}
Here is my constructor:
public FlatComboBox() : base()
{
this.FlatStyle = FlatStyle.Flat;
this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true);
}
What other wndproc messages do I have to block or is there another way around this?
Here is a good reference of all wndproc messages:
https://www.autoitscript.com/autoit3/docs/appendix/WinMsgCodes.htm
I am using .NET v4.5.
Thanks in advance.
I have done this before with a combo box to give it a customisable flat appearance, I am showing the full code so that is easier to get the idea of how it works, however it uses a custom drop down menu and menu items to complete the appearance.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace Custom.Controls
{
public class CustomComboBox : UserControl
{
private const int DEFAULT_WIDTH = 121;
private const int DEFAULT_HEIGHT = 20;
private CustomContextMenuStrip _popupControl;
private CustomToolStripMenuItem _selectedItem;
private bool _bDroppedDown;
private Color _borderColour;
private Color _dropDownButtonColour;
private Color _droppedDownArrowColour;
private Color _closedArrowColour;
private Color _dropDownBackColour;
private Color _dropDownForeColour;
private Color _dropDownBorderColour;
public Color BorderColour
{
get
{
return _borderColour;
}
set
{
_borderColour = value;
this.Invalidate();
}
}
public Color ClosedArrowColour
{
get
{
return _closedArrowColour;
}
set
{
_closedArrowColour = value;
this.Invalidate();
}
}
public Color DroppedDownArrowColour
{
get
{
return _droppedDownArrowColour;
}
set
{
_droppedDownArrowColour = value;
this.Invalidate();
}
}
public Color DropDownButtonColour
{
get
{
return _dropDownButtonColour;
}
set
{
_dropDownButtonColour = value;
this.Invalidate();
}
}
public Color DropDownBorderColour
{
get
{
return _dropDownBorderColour;
}
set
{
_dropDownBorderColour = value;
_popupControl.BorderColour = value;
this.Invalidate();
}
}
public Color DropDownBackColour
{
get
{
return _dropDownBackColour;
}
set
{
_dropDownBackColour = value;
_popupControl.BackColor = value;
this.Invalidate();
}
}
public Color DropDownForeColour
{
get
{
return _dropDownForeColour;
}
set
{
_dropDownForeColour = value;
_popupControl.ForeColor = value;
this.Invalidate();
}
}
public bool DropShadowEnabled
{
get
{
return _popupControl.DropShadowEnabled;
}
set
{
_popupControl.DropShadowEnabled = value;
this.Invalidate();
}
}
public bool DroppedDown
{
get
{
return _bDroppedDown;
}
set
{
_bDroppedDown = value;
if (value)
{
_popupControl.Show(this, new Point(0, this.Height), ToolStripDropDownDirection.BelowRight);
}
else
{
_popupControl.Hide();
}
this.Invalidate();
}
}
public ToolStripItemCollection Items
{
get
{
return _popupControl.Items;
}
}
public CustomToolStripMenuItem SelectedItem
{
get
{
return _selectedItem;
}
}
public CustomComboBox()
{
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.Selectable, true);
this.SetStyle(ControlStyles.UserMouse, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SuspendLayout();
this.ResumeLayout(false);
_popupControl = new CustomContextMenuStrip();
_popupControl.BackColor = this.BackColor;
_popupControl.Closed += new ToolStripDropDownClosedEventHandler(PopupControl_Closed);
_popupControl.ItemClicked += new ToolStripItemClickedEventHandler(PopupControl_ItemClicked);
this.Width = DEFAULT_WIDTH;
this.Height = DEFAULT_HEIGHT;
_selectedItem = null;
_bDroppedDown = false;
this.BackColor = SystemColors.Control;
this.ForeColor = SystemColors.ControlText;
_borderColour = SystemColors.ActiveBorder;
_dropDownButtonColour = SystemColors.ButtonFace;
_droppedDownArrowColour = SystemColors.ControlLight;
_closedArrowColour = SystemColors.ControlDark;
//Set these via the properties so they take effect on _popupControl
this.DropDownBackColour = SystemColors.Control;
this.DropDownForeColour = SystemColors.ControlText;
this.DropDownBorderColour = SystemColors.ActiveBorder;
}
protected override void OnFontChanged(EventArgs e)
{
_popupControl.Font = this.Font;
base.OnFontChanged(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
this.Invalidate();
this.DroppedDown = !this.DroppedDown;
base.OnMouseDown(e);
}
protected override void OnMouseWheel(MouseEventArgs e)
{
int nIndex = this.Items.IndexOf(_selectedItem);
if (e.Delta < 0)
{
if (nIndex < (this.Items.Count - 1))
{
_selectedItem = (CustomToolStripMenuItem)this.Items[nIndex + 1];
OnSelectedItemChanged(EventArgs.Empty);
this.Invalidate();
}
}
else if (e.Delta > 0)
{
if (nIndex > 0)
{
_selectedItem = (CustomToolStripMenuItem)this.Items[nIndex - 1];
OnSelectedItemChanged(EventArgs.Empty);
this.Invalidate();
}
}
base.OnMouseWheel(e);
}
protected override void OnPaint(PaintEventArgs e)
{
Rectangle boundingRect = new Rectangle(0, 0, this.Width, this.Height);
using (SolidBrush brush = new SolidBrush(this.BackColor))
{
e.Graphics.FillRectangle(brush, boundingRect);
}
ControlPaint.DrawBorder(e.Graphics, boundingRect, _borderColour, ButtonBorderStyle.Solid);
//Draw the dropdown button background
int nButtonWidth = SystemInformation.VerticalScrollBarWidth + SystemInformation.VerticalFocusThickness;
Rectangle dropDownButtonRect = new Rectangle(this.Width - nButtonWidth, 0, nButtonWidth, this.Height);
using (SolidBrush brush = new SolidBrush(_dropDownButtonColour))
{
e.Graphics.FillRectangle(brush, dropDownButtonRect);
}
//Draw the dropdown button arrow
using (GraphicsPath path = new GraphicsPath())
{
Point pTopLeft = new Point()
{
X = (int)Math.Round((float)dropDownButtonRect.X + ((float)dropDownButtonRect.Width * 0.30f)),
Y = (int)Math.Round((float)dropDownButtonRect.Y + ((float)dropDownButtonRect.Height * 0.35f))
};
Point pTopRight = new Point()
{
X = (int)Math.Round((float)dropDownButtonRect.X + ((float)dropDownButtonRect.Width * 0.70f)),
Y = (int)Math.Round((float)dropDownButtonRect.Y + ((float)dropDownButtonRect.Height * 0.35f))
};
Point pBottom = new Point()
{
X = (int)Math.Round((float)dropDownButtonRect.X + ((float)dropDownButtonRect.Width * 0.5f)),
Y = (int)Math.Round((float)dropDownButtonRect.Y + ((float)dropDownButtonRect.Height * 0.65f))
};
path.AddLine(pTopLeft, pTopRight);
path.AddLine(pTopRight, pBottom);
SmoothingMode previousSmoothingMode = e.Graphics.SmoothingMode;
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
{
using (SolidBrush brush = new SolidBrush(_bDroppedDown ? _droppedDownArrowColour : _closedArrowColour))
{
e.Graphics.FillPath(brush, path);
}
}
e.Graphics.SmoothingMode = previousSmoothingMode;
}
if (_selectedItem != null)
{
using (SolidBrush brush = new SolidBrush(this.ForeColor))
{
SizeF stringSize = e.Graphics.MeasureString(_selectedItem.Text, this.Font);
e.Graphics.DrawString(_selectedItem.Text, this.Font, brush, new Point(0, (this.Height / 2) - ((int)stringSize.Height / 2)));
}
}
}
protected override void OnSizeChanged(EventArgs e)
{
if (_popupControl != null)
{
_popupControl.MaximumItemSize = new Size(this.Width - 1, _popupControl.MaximumItemSize.Height);
_popupControl.Width = this.Width;
}
base.OnSizeChanged(e);
}
private void OnSelectedItemChanged(EventArgs e)
{
if (SelectedItemChanged != null)
{
SelectedItemChanged(this, e);
}
}
public void SelectFirstItem()
{
if (this.Items != null && this.Items.Count > 0)
{
_selectedItem = (CustomToolStripMenuItem)this.Items[0];
OnSelectedItemChanged(EventArgs.Empty);
}
}
public event EventHandler SelectedItemChanged;
private void PopupControl_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
this.DroppedDown = false;
this.Invalidate(true);
}
private void PopupControl_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
_selectedItem = (CustomToolStripMenuItem)e.ClickedItem;
OnSelectedItemChanged(EventArgs.Empty);
}
}
}
I am building a puzzle game in winforms, and i want to recognize the mousedown over any piece, and move it with the mousemove.
The issue is that when i touch the transparent part of the puzzle piece, i want to verify if there is any piece behind that one, and if so, start the mousemove of that other piece, got it?
I am also able to recognize if the mousedown was over the image, or if it happens in the transparent part of the puzzle piece. My problem is to get the best way to pass the mouse event to the piece behind.
Many Thanks in advance.
UPDATE 1
The following is the class for the puzzle piece:
class Peça : DrawingArea
{
private Point _Offset = Point.Empty;
public Image imagem
{
get;
set;
}
protected override void OnDraw()
{
Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
this.graphics.DrawImage(imagem, location);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (_Offset != Point.Empty)
{
Point newlocation = this.Location;
newlocation.X += e.X - _Offset.X;
newlocation.Y += e.Y - _Offset.Y;
this.Location = newlocation;
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
_Offset = Point.Empty;
}
protected override void OnMouseDown(MouseEventArgs e)
{
Down(e);
//Console.WriteLine(color.ToString());
}
public void Down(MouseEventArgs e)
{
Bitmap b = new Bitmap(imagem);
Color? color = null;
try
{
color = b.GetPixel(e.X, e.Y);
if (color.Value.A != 0 && color != null)
{
if (e.Button == MouseButtons.Left)
{
_Offset = new Point(e.X, e.Y);
this.BringToFront();
}
}
}
catch {
}
}
}
The following code, is my DrawingArea (Panel) that i create in order to work with transparency:
abstract public class DrawingArea : Panel
{
protected Graphics graphics;
abstract protected void OnDraw();
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
public DrawingArea()
{
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
protected override void OnPaint(PaintEventArgs e)
{
this.graphics = e.Graphics;
this.graphics.TextRenderingHint =
System.Drawing.Text.TextRenderingHint.AntiAlias;
this.graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
this.graphics.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
this.graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.HighQuality;
OnDraw();
}
}
And you can also see my Form code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
}
protected override CreateParams CreateParams
{
get
{
CreateParams handleParam = base.CreateParams;
handleParam.ExStyle |= 0x02000000;
return handleParam;
}
}
}
Those are my pieces, and when i touch the transparent space in the first piece, i want to pick up the second one and move it on mouseMouse instead of doing nothing...
It looks like this:
Apologize my bad english.
UPDATE 2
I think i am getting very close to the solution, but something strange happens now, when i touch the piece behind another one, it disappear...
What am i doing wrong?
SOME CODE UPDATES
Piece Class:
class Peça : DrawingArea
{
private Point _Offset = Point.Empty;
public Boolean movable = false;
public Image imagem
{
get;
set;
}
protected override void OnDraw()
{
Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
this.graphics.DrawImage(imagem, location);
}
public void Move(MouseEventArgs e)
{
if (_Offset != Point.Empty)
{
Point newlocation = this.Location;
newlocation.X += e.X - _Offset.X;
newlocation.Y += e.Y - _Offset.Y;
this.Location = newlocation;
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
_Offset = Point.Empty;
movable = false;
}
protected override void OnMouseDown(MouseEventArgs e)
{
Down(e);
//Console.WriteLine(color.ToString());
}
public Boolean Down(MouseEventArgs e, bool propaga=true)
{
Form parentForm = (this.Parent as Form);
Bitmap b = new Bitmap(imagem);
Color? color = null;
Boolean flag = false;
try
{
color = b.GetPixel(e.X, e.Y);
if (color.Value.A != 0 && color != null)
{
if (e.Button == MouseButtons.Left)
{
_Offset = new Point(e.X, e.Y);
this.BringToFront();
flag = true;
movable = true;
}
}
else
{
if(propaga)
(this.Parent as Form1).propagaEvento(this, e);
flag = false;
}
return flag;
}
catch {
return flag; }
}
}
Form1:
public partial class Form1 : Form
{
private List<Peça> peças;
private Point _Offset = Point.Empty;
public Form1()
{
InitializeComponent();
peças = new List<Peça>();
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
criaListaPecas();
associaEventosPecas();
}
private void associaEventosPecas()
{
foreach (Peça p in peças)
{
p.MouseMove += Form1_MouseMove;
}
}
private void criaListaPecas()
{
peças.Clear();
foreach (Control p in this.Controls)
{
if (p.GetType() == typeof(Peça))
peças.Add((Peça)p);
}
Console.WriteLine(peças[0].Name);
Console.WriteLine(peças[1].Name);
Console.WriteLine(peças[2].Name);
}
protected override CreateParams CreateParams
{
get
{
CreateParams handleParam = base.CreateParams;
handleParam.ExStyle |= 0x02000000;
return handleParam;
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
label1.Text = e.Location.ToString();
gereMovimento(e);
}
private void gereMovimento(MouseEventArgs e)
{
foreach (Peça p in peças)
{
if (p.movable)
{
p.Move(e);
}
}
}
internal void propagaEvento(Peça peça, MouseEventArgs e)
{
foreach (Peça p in peças)
{
if (p != peça)
{
if (p.Down(e, false))
break;
}
}
}
}
Thanks in advance again :)
Pieces can be represent as:
public class Piece
{
public Point Location {get; set;}
public int Z {get; set;}
public int ID {get; set;} // to be bound to control or a control itself?
public Image Image {get; set;} // texture?
public DockStyle PlusArea {get; set;}
public DockStyle MinusArea {get; set;} // can be None
...
public bool HitTest(Point point)
{
// assuming all of same size
if((new Rectangle(Location, new Size(...)).Contains(point))
{
switch(MinusArea)
{
case Top:
if((new Rectangle(...)).Contains(point))
return false;
...
}
}
switch(MinusArea)
{
case Top:
if((new Rectangle(...)).Contains(point))
return true;
...
}
return false;
}
Then puzzle is
public class Puzzle
{
public List<Piece> Pieces {get; set;}
public void Draw(Graphics graphics)
{
// draw all pictures with respect to z order
}
public Piece HitTest(Point point)
{
... // hittest all pieces, return highest z-order or null
}
}
It is not a complete solution, but should give you idea.
Basically:
In mouse event you call Figure.HitTest() to get figure to start moving (that's what you need).
You draw everything into owner drawn control by calling Figure.Draw().
Obviously, drag-n-drop operations are calling Invalidate().
You may have special flag to indicate figure being dragged and draw it differently (with shadows, a bit offsetted to simulate it's pulled over other pieces, etc).
Each figure is represented as rectangle and PlusArea or MinusArea (don't know how to call them better, it's this extra or missing area of piece connectors), this is simplification, you can improve it.
In general, keep a list of all your puzzle piece controls, sorted top down. When you get a mouse down event on one piece, check the transparency at that point, if it it not transparent handle the event on that piece. If it is transparent forward the event to the next piece down in your list (directly calling the event handler is probably the easiest way). Keep doing this until you either find a non transparent point, or run out of pieces.
UPDATE
Here is a link to a project showing an example of how to do this in pure GDI.
https://drive.google.com/file/d/0B42fIyGTLNv3WlJwNGVRN2txTGs/edit?usp=sharing
SOLVED :)
i have figured it out...
Here's the code to anybody how needs
(I have made it right now, so the code is not clean yet):
Piece Class:
class Peça : DrawingArea
{
private Point _Offset = Point.Empty;
public Boolean movable = false;
public Image imagem
{
get;
set;
}
protected override void OnDraw()
{
Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
this.graphics.DrawImage(imagem, location);
}
public Boolean Down(Point e, bool propaga = true)
{
Bitmap b = new Bitmap(imagem);
Color? color = null;
Boolean flag = false;
try
{
color = b.GetPixel(e.X, e.Y);
if (color.Value.A != 0 && color != null)
{
flag = true;
}
else
{
flag = false;
}
return flag;
}
catch
{
return flag;
}
}
}
Form1:
public partial class Form1 : Form
{
private List<Peça> peças;
private Point _Offset = Point.Empty;
private Peça peça1, peça2, peça3, peça4;
private bool canMove;
private Peça atual;
private bool other=false;
public Form1()
{
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;
InitializeComponent();
atual = new Peça();
peça1 = new Peça();
peça2 = new Peça();
peça3 = new Peça();
peça4 = new Peça();
peça1.imagem = Properties.Resources._4p1_1;
peça2.imagem = Properties.Resources._4p1_2;
peça3.imagem = Properties.Resources._4p1_3;
peça4.imagem = Properties.Resources._4p1_4;
peças = new List<Peça>();
peça1.Name = "peça1";
peça2.Name = "peça2";
peça3.Name = "peça3";
peça4.Name = "peça4";
this.Controls.Add(peça1);
this.Controls.Add(peça2);
this.Controls.Add(peça3);
this.Controls.Add(peça4);
criaListaPecas();
foreach (Peça p in peças)
{
p.Size = new Size(p.imagem.Width, p.imagem.Height);
}
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
associaEventosPecas();
canMove = false;
}
private void associaEventosPecas()
{
foreach (Peça p in peças)
{
p.MouseMove += Form1_MouseMove;
p.MouseDown += Form1_MouseDown;
p.MouseUp += Form1_MouseUp;
}
}
private void criaListaPecas()
{
peças.Clear();
foreach (Control p in this.Controls)
{
if (p.GetType() == typeof(Peça))
peças.Add((Peça)p);
}
Console.WriteLine(peças[0].Name);
Console.WriteLine(peças[1].Name);
Console.WriteLine(peças[2].Name);
Console.WriteLine(peças[3].Name);
}
protected override CreateParams CreateParams
{
get
{
CreateParams handleParam = base.CreateParams;
handleParam.ExStyle |= 0x02000000;
return handleParam;
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (sender.GetType().Equals(typeof(Peça)))
{
label1.Text = new Point(e.Location.X + (sender as Peça).Location.X, e.Location.Y + (sender as Peça).Location.Y).ToString();
}
else
label1.Text = e.Location.ToString();
gereMovimento(sender, e);
}
private void gereMovimento(object sender, MouseEventArgs e)
{
if (canMove)
{
if (other)
{
Point p = atual.PointToClient(new Point(e.X + (sender as Peça).Location.X, e.Y + (sender as Peça).Location.Y));
Point newlocation = atual.Location;
newlocation.X += p.X - _Offset.X;
newlocation.Y += p.Y - _Offset.Y;
atual.Location = newlocation;
}
else
{
Point newlocation = atual.Location;
newlocation.X += e.X - _Offset.X;
newlocation.Y += e.Y - _Offset.Y;
atual.Location = newlocation;
}
}
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (sender.GetType().Equals(typeof(Peça)) && e.Button == MouseButtons.Left)
{
atual = sender as Peça;
atual.BringToFront();
criaListaPecas();
if (atual.Down(e.Location))
{
_Offset = new Point(e.X, e.Y);
canMove = true;
other = false;
}
else
{
Console.WriteLine(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)));
Console.WriteLine(atual.Location);
Point p = new Point();
if (peças[1].ClientRectangle.Contains(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
&& peças[1].Down(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
{
p = peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
atual = peças[1];
atual.BringToFront();
criaListaPecas();
_Offset = p;
canMove = true;
other = true;
}
else if (peças[2].ClientRectangle.Contains(peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
&& peças[2].Down(peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
{
p = peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
atual = peças[2];
atual.BringToFront();
criaListaPecas();
_Offset = p;
canMove = true;
other = true;
}
else if (peças[3].ClientRectangle.Contains(peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
&& peças[3].Down(peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
{
p = peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
atual = peças[3];
atual.BringToFront();
criaListaPecas();
_Offset = p;
canMove = true;
other = true;
}
}
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
canMove = false;
}
}
Apologize the repeated and confused code, but as i said, i have made it seconds ago, and did not clean the code yet ;)
I'm going to post my code first since it is short and easy to understand, then i'll ask my question.
public class BatteryLabel : Control
{
private Color _captionColor = SystemColors.Control;
private Color _textColor = SystemColors.Info;
private Color _failColor = Color.Red;
private Color _passColor = Color.Green;
private string _caption;
string text2;
string text3;
bool battery1Fail = false;
bool battery2Fail = false;
bool battery3Fail = false;
public BatteryLabel()
{
}
public Color BackgroundTextColor
{
get{ return _textColor;}
set{_textColor = value; Invalidate();}
}
public string Caption
{
get
{
return _caption;
}
set
{
_caption = value;
Invalidate();
}
}
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
Invalidate();
}
}
public string Text2
{
get { return text2; }
set { text2 = value; Invalidate(); }
}
public string Text3
{
get { return text3; }
set { text3 = value; Invalidate(); }
}
public bool Battery1Fail
{
get { return battery1Fail; }
set { battery1Fail = value; Invalidate(); }
}
public bool Battery2Fail
{
get { return battery2Fail; }
set { battery2Fail = value; Invalidate(); }
}
public bool Battery3Fail
{
get { return battery3Fail; }
set { battery3Fail = value; Invalidate(); }
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawRectangle(Pens.Black, 0,0, Width-1, Height-1);
var x1 = 50;
var x2 = 98;
var x3 = 146;
var color1 = battery1Fail?_failColor:BackgroundTextColor;
var color2 = battery2Fail?_failColor:BackgroundTextColor;
var color3 = battery3Fail?_failColor:BackgroundTextColor;
e.Graphics.FillRectangle(new SolidBrush(color1),x1+1, 1, 47, Height-2);
e.Graphics.FillRectangle(new SolidBrush(color2),x2+1, 1, 47, Height-2);
e.Graphics.FillRectangle(new SolidBrush(color3),x3+1, 1, 47, Height-2);
e.Graphics.DrawLine(Pens.Black, x1,0, x1, Height-1);
e.Graphics.DrawLine(Pens.Black, x2,0, x2, Height-1);
e.Graphics.DrawLine(Pens.Black, x3,0, x3, Height-1);
var BoldFont = new Font(this.Font, FontStyle.Bold);
e.Graphics.DrawString(Caption, BoldFont, new SolidBrush(ForeColor), 0,0);
e.Graphics.DrawString(Text, this.Font, new SolidBrush(ForeColor), x1,0);
e.Graphics.DrawString(Text2, this.Font, new SolidBrush(ForeColor), x2,0);
e.Graphics.DrawString(Text3, this.Font, new SolidBrush(ForeColor), x3,0);
}
}
The controls size is meant to be 195,14 just in case you decide to try to use it. I have 8 of these in a panel that is 200,200 running on a 1.6Ghz atom processor. It is used to display values from up to 3 batteries on a computer. The labels get refreshed every 500ms. As you may have gathered there is a little bit of flickering, but it is tolerable. I'd just like to have even less if possible. So I started looking into using Update, and moving some of my code around such as the background bit I thought maybe i should move that to OnPaintBackground(), but in a test frame that i made up the Update method does not change anything, and when I use Invalidate method it runs both OnPaintBackground and OnPaint. Here is what I tried in that case.
public class InformationLabel : Control
{
Random r = new Random();
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
Color randomCOlor = Color.FromArgb(r.Next());
e.Graphics.FillRectangle(new SolidBrush(randomCOlor),0,0, Width-1, Height-1);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Color randomCOlor = Color.FromArgb(r.Next());
e.Graphics.FillPie(new SolidBrush(randomCOlor),15,15,15,15, 0.0f, 120.0f);
}
}
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
void Button1Click(object sender, EventArgs e)
{
informationLabel1.Update();
}
void Button2Click(object sender, EventArgs e)
{
informationLabel1.Invalidate();
}
}
I made that one usercontrol about 300,300 so i could be sure of what i was seeing. I forgot to mention that in the battery control in my 500ms timer i just renew text, text2, and text3. I'm thinking that if the value of that text is out of spec that I'll set the battery fail flag and then invalidate.. but i'm not sure. So how should I go about updating only the text???
You can get rid of the flickering by adding this line in your constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.Opaque | ControlStyles.AllPaintingInWmPaint, true);
Now paint both background and everything else in your paint handler.
Optimizing can be done by passing a Rectangle to Invalidate for only the area that needs repainting. Then in your OnPaint override you use e.ClipRectangle to figure out what to draw. This is probably not necessary for such a simple drawing.
I believe you are looking in the wrong place to eliminate flicker. I can use your BatteryLabel to get flickerless updating of the text with basically a single line. Change your constructor to look like this:
public BatteryLabel()
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer,true);
}
This tells the control to double-buffer its graphics, which makes the flicker go away.
To test with a 100ms refresh interval:
Timer t;
public Form1()
{
InitializeComponent();
t = new Timer();
t.Interval = 100;
t.Tick += new EventHandler(t_Tick);
t.Start();
}
void t_Tick(object sender, EventArgs e)
{
string ticks = DateTime.Now.Ticks.ToString();
string ticks1 = ticks.Substring(ticks.Length-4),
ticks2 = ticks.Substring(ticks.Length - 5,4),
ticks3 = ticks.Substring(ticks.Length - 6,4);
batteryLabel1.Text = ticks1;
batteryLabel1.Text2 = ticks2;
batteryLabel1.Text3 = ticks3;
batteryLabel1.Battery1Fail = ticks1.StartsWith("1");
batteryLabel1.Battery2Fail = ticks2.StartsWith("1");
batteryLabel1.Battery3Fail = ticks3.StartsWith("1");
}
Does this help, or have I misunderstood you?