Add Timer to Custom Control to make a Blinking Cursor - c#

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

Related

C# Winform Panel clear / fully update --- used for mouse multiple selection

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

Resize form to both directions simultaneously

I have launcher application which contains borderless full form sized FlowLayoutPanel, and loads shortcuts to it's child FlowLayoutPanels. Form should be always centered to the screen, so ResizeEnd event centers it.
I want that when user resizes form bigger from bottom (this.Bottom increases), form automatically resizes bigger from top also (this.Top increases).
I tried following approach (works only when resizing bigger to save code here). Yes it works, but it flickers way too much and content is jumping up and down.
private void Form1_ResizeBegin(object sender, EventArgs e)
{
resizeStarted = true;
top = this.Top;
bottom = this.Bottom;
height = this.Height;
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
if (resizeStarted)
{
// if resizing up, automatically resize form bigger so that bottom goes down
if (this.Top < top)
{
int change = top - this.Top;
this.Height = height + (2 * change);
}
// if resizing down, automatically resize form bigger and move up
else if (this.Bottom > bottom)
{
int change = this.Bottom - bottom;
this.Top = top - change;
this.Height = height + (2 * change);
}
}
}
private void Form1_ResizeEnd(object sender, EventArgs e)
{
resizeStarted = false;
centerForm();
}
Is there any smarter way to accomplish this behaviour?

How do I draw a rectangle based on the movement of the mouse?

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

How to prevent tooltip from flickering in custom control?

I have made a custom control and when a condition is met, I want to show a tooltip:
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
var plannedItem = GetPlannedItemByPosition(e.Location);
if (plannedItem != null)
_tooltip.SetToolTip(this, plannedItem.Description);
else
_tooltip.RemoveAll();
}
This code works fine, excepts for the face that the tooltip flickers.
This custom control, paints all the information in the OnPaint event, maybe this has something to do with it? And if it does, how can I prevent the tooltip from flickering?
Remember last mouse position and set the tooltip only when the mouse position changes.
public partial class Form1 : Form
{
private int lastX;
private int lastY;
private void button1_MouseMove(object sender, MouseEventArgs e)
{
if (e.X != this.lastX || e.Y != this.lastY)
{
toolTip1.SetToolTip(button1, "test");
this.lastX = e.X;
this.lastY = e.Y;
}
}
This will happen when you display the tooltip at the mouse cursor position. As soon as the tip window shows up, Windows notices that the mouse is located in that window and posts a MouseMove message. Which makes the tooltip disappear. Which makes Windows send a MouseMove message to your control, running your OnMouseMove() method. Which makes the tooltip appear again. Etcetera, you'll see the tooltip rapidly flickering.
Solve this by any of the following methods:
show the tooltip well away from the mouse position so it won't overlap the mouse cursor
only update/show the tooltip when it needs to be changed
set the control's Capture property to true so the tooltip won't get a MouseMove message
Since this is a painted custom control, I think it might be easier to just have a variable hold the last shown tip, and instead of always "setting" the tooltip, just show it.
Simple example (using just a form):
public partial class Form1 : Form {
private List<TipRect> _Tips = new List<TipRect>();
private TipRect _LastTip;
private ToolTip _tooltip = new ToolTip();
public Form1() {
InitializeComponent();
_Tips.Add(new TipRect(new Rectangle(32, 32, 32, 32), "Tip #1"));
_Tips.Add(new TipRect(new Rectangle(100, 100, 32, 32), "Tip #2"));
}
private void Form1_Paint(object sender, PaintEventArgs e) {
foreach (TipRect tr in _Tips)
e.Graphics.FillRectangle(Brushes.Red, tr.Rect);
}
private void Form1_MouseMove(object sender, MouseEventArgs e) {
TipRect checkTip = GetTip(e.Location);
if (checkTip == null) {
_LastTip = null;
_tooltip.Hide(this);
} else {
if (checkTip != _LastTip) {
_LastTip = checkTip;
_tooltip.Show(checkTip.Text, this, e.Location.X + 10, e.Location.Y + 10, 1000);
}
}
}
private TipRect GetTip(Point p) {
TipRect value = null;
foreach (TipRect tr in _Tips) {
if (tr.Rect.Contains(p))
value = tr;
}
return value;
}
}
Here is the TipRect class I created to simulate whatever your PlannedItem class is:
public class TipRect {
public Rectangle Rect;
public string Text;
public TipRect(Rectangle r, string text) {
Rect = r;
Text = text;
}
}
I imagine your mouse does move a little when you think it is still. I suggest you do some kind of caching here - only call _tooltip.SetToolTip if the plannedItem has changed.
For the visitors of this thread, here is what I did, following suggestions above (VB.NET):
Dim LastToolTip As String
Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
Dim NewToolTip = CalculateTooltipText(e.X, e.Y)
If LastToolTip <> NewToolTip Then
ToolTip1.SetToolTip(PictureBox1, NewToolTip)
LastToolTip = NewToolTip
End If
End Sub
It stopped the flickering.
c# (works on tooltip chart):
Point mem = new Point();
private void xxx_MouseMove(MouseEventArgs e){
// start
Point pos = e.Location;
if (pos == mem) { return; }
// your code here
// end
mem = pos
}

Really strange ToolStripButton event problem

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.

Categories

Resources