Show tooltip for part of user control - c#

I'm building custom control that will display tiles, like colored grid.
I've managed to do drawing, scrolling and basic logic, but I have problem with creating tooltip for each tile.
Each tile color depends on data that is "bound" to that tile.
I'll try to describe my idea:
Above image shows my control, I have 4 squares drawn there, I'd like to show different tooltip when user hovers different parts of my control.
Below is my tooltip (small red rectangle) and lower is standard WinForms Chart component.
I'd like to get this kind of behavior in my control (tooltip is outside of control, so long text is displayed properly).
Below is code of my control with just basic functionality:
public sealed class UC1 : UserControl
{
private bool _showTooltip;
private string _tooltipText;
private Point _mousePosition;
public UC1()
{
MinimumSize = new Size(100, 100);
Size = new Size(100, 100);
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(Brushes.LightGoldenrodYellow, 0, 0, Width/2, Height/2);
e.Graphics.FillRectangle(Brushes.LightGray, Width/2, 0, Width, Height/2);
e.Graphics.FillRectangle(Brushes.LightSlateGray, 0, Height/2, Width/2, Height);
e.Graphics.FillRectangle(Brushes.LightSteelBlue, Width/2, Height/2, Width, Height);
using (var p = new Pen(Color.Black, 2))
{
e.Graphics.DrawLine(p, Width/2, 0, Width/2, Height);
e.Graphics.DrawLine(p, 0, Height/2, Width, Height/2);
}
if (_showTooltip)
{
SizeF c = e.Graphics.MeasureString(_tooltipText, DefaultFont);
int width = (int) c.Width;
int height = (int) c.Height;
const int offset = 12;
var x = _mousePosition.X + width + offset > Width ? _mousePosition.X - width : _mousePosition.X + offset;
var y = _mousePosition.Y + height + offset > Height ? _mousePosition.Y - height : _mousePosition.Y + offset;
e.Graphics.FillRectangle(Brushes.Red, x, y, width, height);
e.Graphics.DrawString(_tooltipText, DefaultFont, Brushes.Black, x, y);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.X > 0 && e.X < Width/2 && e.Y > 0 && e.Y < Height/2)
{
Debug.WriteLine("1,1 square");
_tooltipText = "1,1";
}
else if (e.X > Width/2 && e.X < Width && e.Y > 0 && e.Y < Height/2)
{
Debug.WriteLine("1,2 square");
_tooltipText = "1,2";
}
else if (e.X > 0 && e.X < Width/2 && e.Y > Height/2 && e.Y < Height)
{
Debug.WriteLine("2,1 square");
_tooltipText = "2,1";
}
else if (e.X > Width/2 && e.X < Width && e.Y > Height/2 && e.Y < Height)
{
Debug.WriteLine("2,2 square");
_tooltipText = "2,2";
}
_mousePosition = e.Location;
_showTooltip = true;
Refresh();
}
protected override void OnMouseLeave(EventArgs e)
{
_showTooltip = false;
Refresh();
base.OnMouseLeave(e);
}
}
I've found similar question: How to make a floating (tooltip) control in Windows.Forms? but I'd like to avoid creating custom tooltip and instead use standard one.
How can I show standard tooltip that will change it text when hovering on different tiles. I'd like to add everything to my user control, so I won't have to add any code to form hosting my control.

Why do you try to draw the ToolTip yourself instead of using the system one?
Just add one to the UC class
// private bool _showTooltip; ?? probably not needed any more..
private string _tooltipText;
// private Point _mousePosition; ??..
ToolTip ttip = new ToolTip();
and set it like this:
// _mousePosition = e.Location; ??..
// _showTooltip = true; ??..
ttip.SetToolTip(this, _tooltipText); // use this in the mousemove
Refresh();
Of course now you can skip the whole Painting part..
If you want to control the location where the ToolTip is shown use one of the ShowToolTip() overloads instead of SetToolTip() ..!
While both are still there this is the result, going over the UC's border and displaying with a nice drop shadow..:
If you really want your ToolTip to look different from the usual ones, you can set its OwnerDraw to true and code its Draw event just like any other control with GDI+ graphics methods..
Update:
There is an inherent flicker problem; for an explanation see Hans' answer here; his recommendation #2 and one of the answers is helpful:
Remember last mouse position and set the tooltip only when the mouse
position changes.
So we need to add a last ToolTip location:
Point tipLoc = Point.Empty;
Which we test and set in the mouse move:
if (tipLoc != e.Location )
{
tipLoc = e.Location;
ttip.SetToolTip(this, _tooltipText);
}

Related

c# how to add resize button squares?

Hi i want to make a square to resize button like from normal button to this: https://prnt.sc/ESy2d63JMgRv if you know how to do resizing squares to resize top, left, right and bottom of button by dragging mouse please let me know
I could not fully understand whether you want to make such a change while the application is running or in the designer, neither the link you shared above nor the question is fully explanatory, but I tried to write something about how this can be done while the application is running.
I'm writing this to give you some idea of ​​how to do it, obviously the code below doesn't solve your specific problem.
private int squareSize = 5;
private bool isDragging = false;
private bool onlyX = false;
private bool onlyY = false;
private bool bothXY = false;
You need to draw squares on the corners and edges of the button.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
Rectangle rect1 = new Rectangle(new Point(button1.Left - squareSize, button1.Top - squareSize), new Size(squareSize, squareSize));
Rectangle rect2 = new Rectangle(new Point(button1.Right - (button1.Right - button1.Left) / 2, button1.Top - squareSize), new Size(squareSize, squareSize));
Rectangle rect3 = new Rectangle(new Point(button1.Right, button1.Top), new Size(squareSize, squareSize));
Rectangle rect4 = new Rectangle(new Point(button1.Left - squareSize, button1.Bottom - (button1.Bottom - button1.Top) / 2), new Size(squareSize, squareSize));
Rectangle rect5 = new Rectangle(new Point(button1.Right, button1.Bottom - (button1.Bottom - button1.Top) / 2), new Size(squareSize, squareSize));
Rectangle rect6 = new Rectangle(new Point(button1.Left - squareSize, button1.Bottom - squareSize), new Size(squareSize, squareSize));
Rectangle rect7 = new Rectangle(new Point(button1.Left + (button1.Right - button1.Left) / 2, button1.Bottom), new Size(squareSize, squareSize));
Rectangle rect8 = new Rectangle(new Point(button1.Right, button1.Bottom - squareSize), new Size(squareSize, squareSize));
// copy the left, right, top and bottom coordinates of the rectangles above.
// draw the rectangles.
}
You need to write the mouse down event to determine which corner or edge it was grabbed and dragged
(For example, the first and eighth squares act on the x=-y line, while the third and sixth squares act on the x=y line, with that in mind I suggest you write different boolean values so you can differentiate when resizing the button.), something like this:
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
// Dragging first rectangle, changes on both axis
if (e.Location.X < rect1Right && e.Location.X > rect1Left && e.Location.Y < rect1Bottom && e.Location.Y > rect1Top && e.Button == MouseButtons.Left)
{
bothXY = true;
isDragging = true;
}
// Dragging second rectangle, changes on only y-axis
if (e.Location.X < rect2Right && e.Location.X > rect2Left && e.Location.Y < rect2Bottom && e.Location.Y > rect2Top && e.Button == MouseButtons.Left)
{
onlyY = true;
isDragging = true;
}
}
You should write a mouse move event function to determine the direction and amount of movement on each axis. (I'm not sure if you should do the resizing inside that function, give it a try.)
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging && bothXY)
{
resizeX = e.X - button1.Right;
resizeY = e.Y - button1.Bottom;
}
if(isDragging && onlyY)
{
resizeY = e.Y - button1.Bottom;
}
button1.Width += resizeX;
button1.Height += resizeY;
this.Invalidate();
}
And finally you should write a mouse up event where you reset all boolean values to initials to stop resizing the button.
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
isDragging = false;
onlyY = false;
onlyX = false;
bothXY = false;
}

C# - How to Draw a rectangle with pre-determined size after button click + retangle follow move movements + get coords

Sorry for my eng. Im quite new to C#, and i need this:
After clicking a button, a rectangle form with pre-determined size will appear;
This rectangle must move all around the form + move on screen even if the form is minimized;
The retangle must follow the mouse movements;
When i make a mouse click on the place of screen i want, i need that all coordinates of the area of the rectangle are stored in variables.
This coordinates of rectangle will later be checked by a read-pixel code that i already have, so i really need that the rectangle area of where i clicked really be stored in variables.
EDIT:
Im really new to c# and what i have done so far is:
private void button5_Click(object sender, EventArgs e)
{
Graphics dc = this.CreateGraphics();
Pen Bluepen = new Pen(Color.Blue, 3);
dc.DrawRectangle(Bluepen, 0, 0, 50, 50);
}
And:
private void button5_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseDown == true)
{
rect.Location = e.Location;
if (rect.Right > pictureBox1.Width)
{
rect.X = pictureBox1.Width - rect.Width;
}
if (rect.Top < 0)
{
rect.Y = 0;
}
if (rect.Left < 0 )
{
rect.X = 0;
}
if (rect.Bottom > pictureBox1.Height)
{
rect.Y = pictureBox1.Height - rect.Height;
}
Refresh();
}
}
Thanks in advance!!

Custom Control to use Cursor Hand

I've made a custom control in C# and anytime that the user's cursor is hovering over the custom control I want the cursor to be displayed as the 'Hand'. Where do i place the code to do such a thing?
????.Cursor = Cursors.Hand;
in order to make it so the Hand Cursor is being displayed when hovering over this custom control?
namespace CustomRangeBar
{
public partial class RangeBar : UserControl
{
public RangeBar()
{
InitializeComponent();
label1.ForeColor = Color.Black;
this.ForeColor = SystemColors.Highlight; // set the default color the rangeBar
this.Click += new EventHandler(RangeBar_Click);
}
protected float percent = 0.0f; // Protected because we don't want this to be accessed from the outside
// Create a Value property for the rangeBar
public float Value
{
get
{
return percent;
}
set
{
// Maintain the value between 0 and 100
if (value < 0) value = 0;
else if (value > 100) value = 100;
percent = value;
label1.Text = value.ToString();
//redraw the rangeBar every time the value changes
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush b = new SolidBrush(this.ForeColor); //create brush that will draw the background of the range bar
// create a linear gradient that will be drawn over the background. FromArgb means you can use the Alpha value which is the transparency
LinearGradientBrush lb = new LinearGradientBrush(new Rectangle(0, 0, this.Width, this.Height), Color.FromArgb(255, Color.White), Color.FromArgb(50, Color.White), LinearGradientMode.Vertical);
// calculate how much has the rangeBar to be filled for 'x' %
int width = (int)((percent / 100) * this.Width);
e.Graphics.FillRectangle(b, 0, 0, width, this.Height);
e.Graphics.FillRectangle(lb, 0, 0, width, this.Height);
b.Dispose(); lb.Dispose();
}
private void RangeBar_SizeChanged(object sender, EventArgs e)
{
// maintain the label in the center of the rangeBar
label1.Location = new Point(this.Width / 2 - 21 / 2 - 4, this.Height / 2 - 15 / 2);
}
}
}
public void RangeBar_Click(object obj, EventArgs ea)
{
// This get executed if the pictureBox gets clicked
label1.text = "Increment 1";
}
UserControl derives from Control and therefore should already have a Cursor property inherited from that class. Do you not see a Cursor property in code/Properties?

How to use ScrollableControl with AutoScroll set to false

I have a custom control that zooms on a custom drawn document canvas.
I tried using AutoScroll but it was not giving satisfactory results. When I would set AutoScrollPosition and AutoScrollMinSize back to back (in any order) it would force a paint and cause jitter each time the zoom changes. I assume this was because it was calling an Update and not Invalidate when I modified both properties.
I am now manually setting the HorizontalScroll and VerticalScroll properties with AutoScroll set to false like so each time the Zoom level or the client size changes:
int canvasWidth = (int)Math.Ceiling(Image.Width * Zoom) + PageMargins.Horizontal;
int canvasHeight = (int)Math.Ceiling(Image.Height * Zoom) + PageMargins.Vertical;
HorizontalScroll.Maximum = canvasWidth;
HorizontalScroll.LargeChange = ClientSize.Width;
VerticalScroll.Maximum = canvasHeight;
VerticalScroll.LargeChange = ClientSize.Height;
if (canvasWidth > ClientSize.Width)
{
HorizontalScroll.Visible = true;
}
else
{
HorizontalScroll.Visible = false;
HorizontalScroll.Value = 0;
}
if (canvasHeight > ClientSize.Height)
{
VerticalScroll.Visible = true;
}
else
{
VerticalScroll.Visible = false;
VerticalScroll.Value = 0;
}
int focusX = (int)Math.Floor((FocusPoint.X * Zoom) + PageMargins.Left);
int focusY = (int)Math.Floor((FocusPoint.Y * Zoom) + PageMargins.Top);
focusX = focusX - ClientSize.Width / 2;
focusY = focusY - ClientSize.Height / 2;
if (focusX < 0)
focusX = 0;
if (focusX > canvasWidth - ClientSize.Width)
focusX = canvasWidth - ClientSize.Width;
if (focusY < 0)
focusY = 0;
if (focusY > canvasHeight - ClientSize.Height)
focusY = canvasHeight - ClientSize.Height;
if (HorizontalScroll.Visible)
HorizontalScroll.Value = focusX;
if (VerticalScroll.Visible)
VerticalScroll.Value = focusY;
In this case, FocusPoint is a PointF structure that holds the coordinates in the bitmap which the user is focused on (for example, when they mouse wheel to zoom in they are focusing on the current mouse location at that time). This functionality works for the most part.
What does not work is the scroll bars. If the user tries to manually scroll by clicking on either scroll bar, they both keep returning to 0. I do not set them anywhere else in my code. I have tried writing the following in the OnScroll() method:
if (se.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
VerticalScroll.Value = se.NewValue;
}
else
{
HorizontalScroll.Value = se.NewValue;
}
Invalidate();
But this causes some very erratic behavior including flicking and scrolling out of bounds.
How am I supposed to write the code for OnScroll? I've tried the base.OnScroll but it didn't do anything while AutoScroll is set to false.
I ended up implementing my own custom scrolling by creating 3 child controls: an HScrollBar, a VScrollBar, and a Panel.
I hide ClientSize and ClientRectangle like so:
public new Rectangle ClientRectangle
{
get
{
return new Rectangle(new Point(0, 0), ClientSize);
}
}
public new Size ClientSize
{
get
{
return new Size(
base.ClientSize.Width - VScrollBar.Width,
base.ClientSize.Height - HScrollBar.Height
);
}
}
The layout is done in OnClientSizeChanged:
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
HScrollBar.Location = new Point(0, base.ClientSize.Height - HScrollBar.Height);
HScrollBar.Width = base.ClientSize.Width - VScrollBar.Width;
VScrollBar.Location = new Point(base.ClientSize.Width - VScrollBar.Width, 0);
VScrollBar.Height = base.ClientSize.Height - HScrollBar.Height;
cornerPanel.Size = new Size(VScrollBar.Width, HScrollBar.Height);
cornerPanel.Location = new Point(base.ClientSize.Width - cornerPanel.Width, base.ClientSize.Height - cornerPanel.Height);
}
Each ScrollBar has their Scroll event subscribed to the following:
private void ScrollBar_Scroll(object sender, ScrollEventArgs e)
{
OnScroll(e);
}
And finally we can allow MouseWheel events to scroll with the following:
protected override void OnMouseWheel(MouseEventArgs e)
{
int xOldValue = VScrollBar.Value;
if (e.Delta > 0)
{
VScrollBar.Value = (int)Math.Max(VScrollBar.Value - (VScrollBar.SmallChange * e.Delta), 0);
OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, xOldValue, VScrollBar.Value, ScrollOrientation.VerticalScroll));
}
else
{
VScrollBar.Value = (int)Math.Min(VScrollBar.Value - (VScrollBar.SmallChange * e.Delta), VScrollBar.Maximum - (VScrollBar.LargeChange - 1));
OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, xOldValue, VScrollBar.Value, ScrollOrientation.VerticalScroll));
}
}
For custom painting, you would use the following statement:
e.Graphics.TranslateTransform(-HScrollBar.Value, -VScrollBar.Value);
This worked perfectly without the glitches present when using AutoScroll.

How to get the TopLeft screen-coordinate of a PictureBox on a Windows Forms

I have a a PictureBox on my Windows Forms.
I am drawing a Rectangle on the PictureBox, with ControlPaint.DrawReversibleFrame(), and want to code some boundaries, so I am only drawing on the PictureBox and not the whole screen.
How do I find the screen-coordinates of the topleft point of the PictureBox?
EDIT with solution: Here's my solution, if anybody need to code some PictureBox boundaries.
if (_isDragging) // If the mouse is being dragged, undraw and redraw the rectangle as the mouse moves.
{
pictureBoxMap.Refresh();
ControlPaint.DrawReversibleFrame(_theRectangleScreenCoords, BackColor, FrameStyle.Dashed); // Hide the previous rectangle by calling the DrawReversibleFrame method with the same parameters.
Point endPoint = ((Control)sender).PointToScreen(new Point(e.X, e.Y));
var topLeftPictureBoxMap = pictureBoxMap.PointToScreen(new Point(0, 0));
int width = endPoint.X - _startPointTheRectangleScreenCoords.X;
int height = endPoint.Y - _startPointTheRectangleScreenCoords.Y;
// limit rectangle in x-axis
var diff_x = pictureBoxMap.Width - (_startPointTheRectangleScreenCoords.X - topLeftPictureBoxMap.X);
var diff_x_2 = (pictureBoxMap.Width - (_startPointTheRectangleScreenCoords.X - topLeftPictureBoxMap.X)) - pictureBoxMap.Width;
if (width > diff_x)
{
width = diff_x;
}
else if(width < diff_x_2)
{
width = diff_x_2;
}
// limit rectangle i y-aksen
var diff_Y = pictureBoxMap.Height - (_startPointTheRectangleScreenCoords.Y - topLeftPictureBoxMap.Y);
var diff_Y_2 = (pictureBoxMap.Height - (_startPointTheRectangleScreenCoords.Y - topLeftPictureBoxMap.Y)) - pictureBoxMap.Height;
if (height > diff_Y)
{
height = diff_Y;
}
else if(height < diff_Y_2)
{
height = diff_Y_2;
}
_theRectangleScreenCoords = new Rectangle(
_startPointTheRectangleScreenCoords.X,
_startPointTheRectangleScreenCoords.Y,
width,
height);
ControlPaint.DrawReversibleFrame(_theRectangleScreenCoords, Color.Red, FrameStyle.Dashed); // Draw the new rectangle by calling DrawReversibleFrame again.
}
Use Control.PointToScreen( new Point(0, 0) ) where Control is your PictureBox.
See http://msdn.microsoft.com/en-us/library/system.windows.forms.control.pointtoscreen.aspx

Categories

Resources