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.
Related
Somehow I manage to get myself in a place again where neither Google or my Wording of my problem gives me a solution.
So, to put it this way, I'm creating a TextBox from scratch, derived from System.Windows.Forms.Control. I have so far been able to draw the Text variable, and also added editing functionality to it (Like a normal TextBox usually do).
The idea of the dreadful time-consuming exercise is to create my own library of custom controls that have maximum theme-capabilities for use with the software I develop.
So, my problem: Drawing the blinking cursor in the TextBox.
I'm able to draw a static Line, which moves left to right depending on where the CursorPosition takes it. Added a timer to the control (hence the if(blinker)) through System.Timers.Timer and also tried the System.Windows.Forms.Timer but messes up my Designer by not allowing me to set any of my properties when the control is added to a form and still does not fire up my drawing event. I did place Invalidate(_Cursor) in the Tick event to ensure that only the Cursor position is redrawn, even tried without the argument and still to no avail.
protected override void OnPaint(PaintEventArgs e)
{
...
if (Focused)
{
if (blinker)
e.Graphics.FillRectangle(new SolidBrush(_CursorColor), _Cursor);
foreach (Rectangle border in Borders)
{
if(_DrawBorders[Borders.IndexOf(border)])
e.Graphics.FillRectangle(new SolidBrush(BorderColor), border);
}
}
...
}
I have also set
DoubleBuffered = true;
SetStyle(ControlStyles.UserPaint | ControlStyles.Selectable | ControlStyles.ResizeRedraw |
ControlStyles.OptimizedDoubleBuffer | ControlStyles.StandardClick | ControlStyles.AllPaintingInWmPaint, true);
Which helped with a terrible flickering and input issue I had.
EDIT **(REMOVED AS IT IS FIXED USING COMMENT BY Hans Passant's SUGGESTION )**
Okay, so now that the Timer is working, on to the problem I stated above. Drawing the Cursor.
private void _CursorBlinkTime_Tick(object sender, EventArgs e)
{
blinker = !blinker;
Console.WriteLine("Blink!");
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
Height = TextRenderer.MeasureText("A", Font).Height + (2 * BorderWidth) + (2 * TextPadding);
CursorPosition = (BorderWidth + TextPadding) + (_SelectedStart * (TextRenderer.MeasureText("_", Font).Width));
_InputBounds = new Rectangle(BorderWidth + TextPadding, BorderWidth + TextPadding, Width - (2 * BorderWidth) - (2 * TextPadding), Height - (2 * BorderWidth));
base.OnPaint(e);
e.Graphics.Clear(BackColor);
e.Graphics.DrawString(Text, Font, new SolidBrush(TextColor), _InputBounds.Location);
foreach (Rectangle border in Borders)
{
if (_DrawBorders[Borders.IndexOf(border)])
e.Graphics.FillRectangle(new SolidBrush(InactiveBorderColor), border);
}
if (Focused)
{
// This is where the Cursor is drawn, right before the borders.
// I've tried to move it to after the border is drawn but same result, nothing is drawn.
// I've ran through the entire OnPaint using Step-by-Step and while it does
// fire the draw event, nothing is drawn.
// BackColor = FromArgb(40,40,40), _CursorColor = Color.Red
// _Cursor is a Rectangle that reads at this moment:
// Rectangle(5,0,2,21) Which given all other variables should show result?
if (blinker)
e.Graphics.FillRectangle(new SolidBrush(_CursorColor), _Cursor);
foreach (Rectangle border in Borders)
{
if(_DrawBorders[Borders.IndexOf(border)])
e.Graphics.FillRectangle(new SolidBrush(BorderColor), border);
}
}
else if (!Focused)
{
if(string.IsNullOrWhiteSpace(Text))
e.Graphics.DrawString(WaterMarkText, Font, new SolidBrush(WaterMarkColor), _InputBounds.Location);
}
}
Okay, so after I decided to give up and stop trying, I removed every trace of the cursor I had, and started to look at drawing it again. Got that fixed. Then I went back to visit the Timers, and figured that when not using a class derived from a System.Windows.Forms.Form, the System.Windows.Forms.Timer messes every ounce of what you think you had up. So, I moved on to the System.Timers.Timer (Which I had used previously on Mobile Development to great success).
All the problems I had are now resolved, thank you for your help yet again! I love this place.
Here is the code changes:
private double _CursorInterval;
/// <summary>
/// Values below 500ms not recommended, due to safety hazards on epilepsy.
/// </summary>
public double CursorInterval
{
get { return _CursorInterval; }
set { _CursorInterval = value; }
}
private bool blink = false;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Height = TextRenderer.MeasureText("A", Font).Height + (2 * BorderWidth) + (2 * TextPadding);
_InputBounds = new Rectangle(BorderWidth + TextPadding, BorderWidth + TextPadding, Width - (2 * BorderWidth) - (2 * TextPadding), Height - (2 * BorderWidth) - (2 * TextPadding));
if (blink)
e.Graphics.FillRectangle(new SolidBrush(BorderColor), new Rectangle(_InputBounds.Location, new Size(1, _InputBounds.Height)));
}
System.Timers.Timer blinkTimer;
protected override void OnGotFocus(EventArgs e)
{
if (!DesignMode)
{
blinkTimer = new System.Timers.Timer(CursorInterval);
blinkTimer.Elapsed += TimerElapsed;
blinkTimer.Start();
Console.WriteLine("OnGotFocus - I was here.");
}
blink = true;
Invalidate();
base.OnGotFocus(e);
}
private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
blink = !blink;
Console.WriteLine("TimerElapsed - I was here.");
Invalidate();
}
protected override void OnLostFocus(EventArgs e)
{
if(!DesignMode)
{
blink = false;
blinkTimer.Stop();
blinkTimer.Elapsed -= TimerElapsed;
Console.WriteLine("OnLostFocus - I was here.");
}
Invalidate();
base.OnLostFocus(e);
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
Focus();
}
I found sample code here for drawing on a form:
http://msdn.microsoft.com/en-us/library/aa287522(v=vs.71).aspx
As a followup to this requirement (discovering which controls are beneath a rectangle described by
the user dragging the mouse):
There seems to be a mismatch between the location of my controls and the location of my MouseDown and -Up events
...I want to provide the user instant/constant feedback about just what they are about to select
(when/if they release the mouse button). I want to not just draw a line following the mouse's
movement, but draw the rectangle that is being described by their mousewrangling efforts.
I'm thinking the MouseMove event, coupled with code from the two links above, could do the trick, but is that fired too often/would that have a malevolent impact on performance? If so, what would be a preferable event to hook, or would a timer be the way to go here?
UPDATE
This code, adapted from John's example below (the only difference is the StackOverflow-inducing calls to base.* are commented out, and I changed the color from red to black (no reference to Stendahl intended)), works except that previously drawn rectangles display again after releasing the mouse. IOW, the first rectangle draws perfectly - it disappears with the mouse up click (as intended). However, when I describe a second rectangle by depressing the left mouse key and dragging down and to the right, the first rectangle displays again! And this continues to happen - every previously drawn rectangle is remembered and brought back to the fore when a new rectangle is being drawn.
public partial class Form1 : Form
{
private Point? _start;
private Rectangle _previousBounds;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
_start = e.Location;
//base.OnMouseDown(e);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (_start.HasValue)
DrawFrame(e.Location);
//base.OnMouseMove(e);
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
ReverseFrame();
_start = null;
//base.OnMouseUp(e);
}
private void ReverseFrame()
{
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Black, FrameStyle.Dashed);
}
private void DrawFrame(Point end)
{
ReverseFrame();
var size = new Size(end.X - _start.Value.X, end.Y - _start.Value.Y);
_previousBounds = new Rectangle(_start.Value, size);
_previousBounds = this.RectangleToScreen(_previousBounds);
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Black, FrameStyle.Dashed);
}
}
ControlPaint.DrawReversibleFrame() will do what you want. Performance is not generally a problem - just keep it small and clean.
--
EDIT: Added a code sample. StackOverflowException indicates something is wrong - but without seeing yours, can't answer directly.
private Point? _start;
private Rectangle _previousBounds;
protected override void OnMouseDown(MouseEventArgs e)
{
_start = e.Location;
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if( _start.HasValue ) {
ReverseFrame();
DrawFrame(e.Location);
}
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
ReverseFrame();
_start = null;
base.OnMouseUp(e);
}
private void ReverseFrame()
{
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Red, FrameStyle.Dashed);
}
private void DrawFrame(Point end)
{
ReverseFrame();
var size = new Size(end.X - _start.Value.X, end.Y - _start.Value.Y);
_previousBounds = new Rectangle(_start.Value, size);
_previousBounds = this.RectangleToScreen(_previousBounds);
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Red, FrameStyle.Dashed);
}
I've created a label override that will allow my new control to be moved around the screen easily.
I've attached the code below, but when I run the application or attempt to move the label it's always off. It also will sometimes just completely vanish, leave a trail or reset to the 0,0 location. Have a look at the screenshot.
I used to have this working 100%, but after some recent tweaking it has gone to the dogs again and I'm not sure how to get it working.
Screenshot:
Code:
internal sealed class DraggableLabel : Label
{
private bool _dragging;
private int _mouseX, _mouseY;
public DraggableLabel()
{
DoubleBuffered = true;
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (_dragging)
{
Point mposition = PointToClient(MousePosition);
mposition.Offset(_mouseX, _mouseY);
Location = mposition;
}
base.OnMouseMove(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
_dragging = true;
_mouseX = -e.X;
_mouseY = -e.Y;
BringToFront();
Invalidate();
}
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (_dragging)
{
_dragging = false;
Cursor.Clip = new Rectangle();
Invalidate();
}
base.OnMouseUp(e);
}
}
The OnMouseMove() code is bad. Do it like this instead:
private Point lastPos;
protected override void OnMouseMove(MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
int dx = e.X - lastPos.X;
int dy = e.Y - lastPos.Y;
Location = new Point(Left + dx, Top + dy);
// NOTE: do NOT update lastPos, the relative mouse position changed
}
base.OnMouseMove(e);
}
protected override void OnMouseDown(MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
lastPos = e.Location;
BringToFront();
this.Capture = true;
}
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e) {
this.Capture = false;
base.OnMouseUp(e);
}
The screen shot also shows evidence of the form not redrawing properly. You didn't leave any clue as to what might cause that.
I had a problem like this recently, and I solved it by setting the background color of the label to Color.Transparent.
If this doesn't solve it, then you should consider monitoring your event handling. It could be that you're registering more than one mouse move event and thus each method would interfere with the other.
EDIT :
Have you also tried overriding the OnPaint method of its parent class?
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
}
Maybe you should put focus on your element like
label1.Focus();
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.
Hey all, I am not sure if this is possible, but I am trying to dynamically add a tooltip to an image using the Graphics method - DrawImage. I dont see any properties or events for when the image is moused over or anything so I don't know where to begin. I am using WinForms (in C# - .NET 3.5). Any ideas or suggestions would be appreciated. Thanks.
I would guess that you have some sort of UserControl and you call DrawImage() in the OnPaint method.
Given that, your tooltip will have to controlled explicitly. Basically, create a Tooltip on your Form, give that to your control via a property, show the tooltip when your control received a MouseHover event and hide the tooltip when you receive a MouseLeave event.
Something like this:
public partial class UserControl1 : UserControl
{
public UserControl1() {
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
// draw image here
}
public ToolTip ToolTip { get; set; }
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
if (this.ToolTip != null)
this.ToolTip.Hide(this);
}
protected override void OnMouseHover(EventArgs e) {
base.OnMouseHover(e);
if (this.ToolTip == null)
return;
Point pt = this.PointToClient(Cursor.Position);
String msg = this.CalculateMsgAt(pt);
if (String.IsNullOrEmpty(msg))
return;
pt.Y += 20;
this.ToolTip.Show(msg, this, pt);
}
private string CalculateMsgAt(Point pt) {
// Calculate the message that should be shown
// when the mouse is at thegiven point
return "This is a tooltip";
}
}
Remember, you have to store bounds of the Image that you are drawing
and in the mouseMove event check if the location of current Mouse cursor at that region, then display ToolTip else hide it.
ToolTip t;
private void Form1_Load(object sender, EventArgs e)
{
t = new ToolTip(); //tooltip to control on which you are drawing your Image
}
Rectangle rect; //to store the bounds of your Image
private void Panel1_Paint(object sender, PaintEventArgs e)
{
rect =new Rectangle(50,50,200,200); // setting bounds to rect to draw image
e.Graphics.DrawImage(yourImage,rect); //draw your Image
}
private void Panel1_MouseMove(object sender, MouseEventArgs e)
{
if (rect.Contains(e.Location)) //checking cursor Location if inside the rect
{
t.SetToolTip(Panel1, "Hello");//setting tooltip to Panel1
}
else
{
t.Hide(Panel1); //hiding tooltip if the cursor outside the rect
}
}