So I have a resizable control which I'm using WM_NCHITTEST to resize. I have double buffered the control and I'm drawing a border. However, when resizing, I get medium flickering issues.
Here's the code for drawing:
if (_cachedPen == null)
{
_cachedPen = new Pen(BorderColor, VisibleBorderWidth);
}
Rectangle clientRectangle = ClientRectangle;
if ((BorderSides & BorderSides.Left) == BorderSides.Left)
{
e.Graphics.DrawLine(_cachedPen, clientRectangle.X, clientRectangle.Y, clientRectangle.X, clientRectangle.Height);
}
if ((BorderSides & BorderSides.Right) == BorderSides.Right)
{
e.Graphics.DrawLine(_cachedPen, clientRectangle.Width - VisibleBorderWidth, clientRectangle.Y, clientRectangle.Width - VisibleBorderWidth, clientRectangle.Height);
}
if ((BorderSides & BorderSides.Bottom) == BorderSides.Bottom)
{
e.Graphics.DrawLine(_cachedPen, clientRectangle.X, clientRectangle.Height - VisibleBorderWidth, clientRectangle.Width, clientRectangle.Height - VisibleBorderWidth);
}
if ((BorderSides & BorderSides.Top) == BorderSides.Top)
{
e.Graphics.DrawLine(_cachedPen, clientRectangle.X, clientRectangle.Y, clientRectangle.Width, clientRectangle.Y);
}
And for resizing:
case WM_NCHITTEST:
{
Point pos = PointToClient(new Point(m.LParam.ToInt32()));
if ((BorderSides & BorderSides.Left) == BorderSides.Left && pos.X < PhysicalBorderWidth)
{
m.Result = new IntPtr(HTLEFT);
}
if ((BorderSides & BorderSides.Bottom) == BorderSides.Bottom && pos.Y > Height - PhysicalBorderWidth)
{
m.Result = new IntPtr(HTBOTTOM);
}
if ((BorderSides & BorderSides.Right) == BorderSides.Right && pos.X > Width - PhysicalBorderWidth)
{
m.Result = new IntPtr(HTRIGHT);
}
if ((BorderSides & BorderSides.Top) == BorderSides.Top && pos.Y < PhysicalBorderWidth)
{
m.Result = new IntPtr(HTTOP);
}
return;
}
case 0x0014:
return;
default:
base.WndProc(ref m);
break;
Note that SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true); is on. However, when resizing, I get light-medium flickering. Is there any way to solve this?
Just to note, it happens even when I don't cache the Pen objects.
The painting is just called from OnPaint. The custom control just inherits from Control.
The definition of BorderSides is as follows:
[Flags]
internal enum BorderSides
{
None = 0,
Left = 1,
Right = 2,
Top = 4,
Bottom = 8
}
And here are the constants:
private const int WM_NCHITTEST = 0x0084;
private const int HTBOTTOM = 15;
private const int HTLEFT = 10;
private const int HTRIGHT = 11;
private const int HTTOP = 12;
This is for a docking control. It doesn't have a graphic background, and I intend to inherit from it. It will host controls. I'm testing all 4 border sides, and all have slight flickering issues.
Thanks
Related
I'm using the following code to draw a selection rectangle over a picturebox and allow the user to select and drag it to the desired position.
What I intend to achieve is to allow the user to adjust the rectangle size by implementing option to adjust the rectangle size. Currently I have managed to achieve the following.
How to solve this issue?
public class DraggablePictureBox : PictureBox
{
Boolean hit1 = false, hit2 = false;
public Boolean notagimg = true;
public Boolean editedflag = false;
public Boolean notext = false;
public Boolean tdrawflag = false, tdrawflag2 = false;
Bitmap l;
public Form1 LaunchOrigin2 { get; set; }
public Point point = new Point(0, 0);
public Point point2 = new Point(0, 0);
public int sizemode = 1;
public DraggablePictureBox()
{
this.Invalidate();
}
protected override void OnMouseMove(MouseEventArgs e)
{
this.Cursor = Cursors.Arrow;
if (e.Button == MouseButtons.Left)
{
point = e.Location;
base.OnMouseDown(e);
this.Invalidate();
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
PointF x = new PointF(e.X, e.Y);
Pen p = new Pen(Brushes.Red, 2f);
RectangleF rect2 = new RectangleF(1, 1, 1, 1);
RectangleF rect = new RectangleF(1, 1, 1, 1);
if (e.Button == MouseButtons.Left)
{
this.Cursor = Cursors.Hand;
//Creating Rectangles to check to find the selected object
if (notext == false)
{
rect = new RectangleF(point, new Size(400,400));
}
if (rect.Contains(x) && notext == false)
{
hit1 = true;
}
if (hit1 == true )
{
this.Invalidate();
}
this.Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
tdrawflag = false;
}
if (e.Button == MouseButtons.Left)
{
point = e.Location;
base.OnMouseDown(e);
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Pen p = new Pen(Brushes.Red, 5);
p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
Pen p2 = new Pen(Brushes.LightYellow, 5);
p2.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
if (!hit1)
{
pe.Graphics.DrawRectangle(p, new Rectangle(point, new Size(400, 400)));
}
else
{
pe.Graphics.DrawRectangle(p2, new Rectangle(point, new Size(400, 400)));
hit1 = false;
}
}
}
You have different options:
You can draw a resizable frame on the picture box
You can create a resizable control and add it to picture box
In this answer, I've taken the second option to be able to use built-in sizing features of the controls. Here is a screen capture which shows how it looks like in action:
Example - Creating a Frame Control
As an example, I'll create a resizable control and will add it to the picture box.
using System;
using System.Drawing;
using System.Windows.Forms;
public class FrameControl : Control
{
public FrameControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
DoubleBuffered = true;
ResizeRedraw = true;
BackColor = Color.Transparent;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (var p = new Pen(Color.Black, 4))
{
p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
e.Graphics.DrawRectangle(p, 0, 0, Width - 1, Height - 1);
}
}
const int WM_NCHITTEST = 0x84;
const int WM_SETCURSOR = 0x20;
const int WM_NCLBUTTONDBLCLK = 0xA3;
protected override void WndProc(ref Message m)
{
int borderWidth = 10;
if (m.Msg == WM_SETCURSOR) /*Setting cursor to SizeAll*/
{
if ((m.LParam.ToInt32() & 0xffff) == 0x2 /*Move*/)
{
Cursor.Current = Cursors.SizeAll;
m.Result = (IntPtr)1;
return;
}
}
if ((m.Msg == WM_NCLBUTTONDBLCLK)) /*Disable Mazimiz on Double click*/
{
m.Result = (IntPtr)1;
return;
}
base.WndProc(ref m);
if (m.Msg == WM_NCHITTEST)
{
var pos = PointToClient(new Point(m.LParam.ToInt32() & 0xffff,
m.LParam.ToInt32() >> 16));
if (pos.X <= ClientRectangle.Left + borderWidth &&
pos.Y <= ClientRectangle.Top + borderWidth)
m.Result = new IntPtr(13); //TOPLEFT
else if (pos.X >= ClientRectangle.Right - borderWidth &&
pos.Y <= ClientRectangle.Top + borderWidth)
m.Result = new IntPtr(14); //TOPRIGHT
else if (pos.X <= ClientRectangle.Left + borderWidth &&
pos.Y >= ClientRectangle.Bottom - borderWidth)
m.Result = new IntPtr(16); //BOTTOMLEFT
else if (pos.X >= ClientRectangle.Right - borderWidth &&
pos.Y >= ClientRectangle.Bottom - borderWidth)
m.Result = new IntPtr(17); //BOTTOMRIGHT
else if (pos.X <= ClientRectangle.Left + borderWidth)
m.Result = new IntPtr(10); //LEFT
else if (pos.Y <= ClientRectangle.Top + borderWidth)
m.Result = new IntPtr(12); //TOP
else if (pos.X >= ClientRectangle.Right - borderWidth)
m.Result = new IntPtr(11); //RIGHT
else if (pos.Y >= ClientRectangle.Bottom - borderWidth)
m.Result = new IntPtr(15); //Bottom
else
m.Result = new IntPtr(2); //Move
}
}
}
Then add the control to the picture box:
var s = 100;
var c = new FrameControl();
c.Size = new Size(s, s);
c.Location = new Point((pictureBox1.Width - s) / 2, (pictureBox1.Height - s) / 2);
pictureBox1.Controls.Add(c);
To add a fancy effect of filling outside of the frame with semi-transparent color:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.ExcludeClip(pictureBox1.Controls[0].Bounds);
using (var b = new SolidBrush(Color.FromArgb(100, Color.Black)))
e.Graphics.FillRectangle(b, pictureBox1.ClientRectangle);
}
As you can see in the paint event, you can find the FrameControl using pictureBox1.Controls[0]. So you can find its location and size.
You can encapsulate all the logic of the picture box in a derived picture box.
Note: Flicker-free rendering
If you experience flickering when moving the frame, use the following code in your form:
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
return cp;
}
}
I'm building custom user control that is based on ScrollableControl.
Right now I'm trying to add border around my control (similar to border that DataGridView has)
I'm able to draw border using:
e.Graphics.TranslateTransform(AutoScrollPosition.X*-1, AutoScrollPosition.Y*-1);
ControlPaint.DrawBorder(e.Graphics, ClientRectangle, Color.DarkBlue, ButtonBorderStyle.Dashed);
but this draws border around ClientRectangle, not around whole control:
As you can see in the above picture, border isn't surrounding scrollbars as it does in DataGridView.
Can I draw border around entire control so that scrollbars get included in area surrounded by border?
EDIT:
Based on Textbox custom onPaint I am able to draw custom border, by overriding WndProc but I get this weird looking border flickering:
Here is full code I have so far:
internal class TestControl : ScrollableControl
{
private int _tileWidth = 100;
private int _tileHeight = 100;
private int _tilesX = 20;
private int _tilesY = 20;
public TestControl()
{
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.Opaque, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
ResizeRedraw = true;
AutoScrollMinSize = new Size(_tilesX*_tileWidth, _tilesY*_tileHeight);
}
private bool _test = true;
[DefaultValue(true)]
public bool Test
{
get { return _test; }
set
{
if(_test==value) return;
_test = value;
Update();
}
}
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
struct RECT
{
public int left, top, right, bottom;
}
struct NCCALSIZE_PARAMS
{
public RECT newWindow;
public RECT oldWindow;
public RECT clientWindow;
IntPtr windowPos;
}
int clientPadding = 1;
int actualBorderWidth = 1;
Color borderColor = Color.Black;
protected override void WndProc(ref Message m)
{
//We have to change the clientsize to make room for borders
//if not, the border is limited in how thick it is.
if (m.Msg == 0x83 && _test) //WM_NCCALCSIZE
{
if (m.WParam == IntPtr.Zero)
{
RECT rect = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
rect.left += clientPadding;
rect.right -= clientPadding;
rect.top += clientPadding;
rect.bottom -= clientPadding;
Marshal.StructureToPtr(rect, m.LParam, false);
}
else
{
NCCALSIZE_PARAMS rects = (NCCALSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALSIZE_PARAMS));
rects.newWindow.left += clientPadding;
rects.newWindow.right -= clientPadding;
rects.newWindow.top += clientPadding;
rects.newWindow.bottom -= clientPadding;
Marshal.StructureToPtr(rects, m.LParam, false);
}
}
if (m.Msg == 0x85 && _test) //WM_NCPAINT
{
base.WndProc(ref m);
IntPtr wDC = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(wDC))
{
ControlPaint.DrawBorder(g, new Rectangle(0, 0, Size.Width, Size.Height), borderColor, actualBorderWidth, ButtonBorderStyle.Solid,
borderColor, actualBorderWidth, ButtonBorderStyle.Solid, borderColor, actualBorderWidth, ButtonBorderStyle.Solid,
borderColor, actualBorderWidth, ButtonBorderStyle.Solid);
}
return;
}
base.WndProc(ref m);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle);
e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var offsetX = (AutoScrollPosition.X*-1)/_tileWidth;
var offsetY = (AutoScrollPosition.Y*-1)/_tileHeight;
var visibleX = Width/_tileWidth + 2;
var visibleY = Height/_tileHeight + 2;
var x = Math.Min(visibleX + offsetX, _tilesX);
var y = Math.Min(visibleY + offsetY, _tilesY);
for (var i = offsetX; i < x; i++)
{
for (var j = offsetY; j < y; j++)
{
e.Graphics.FillRectangle(Brushes.Beige, new Rectangle(i*_tileWidth, j*_tileHeight, _tileWidth, _tileHeight));
e.Graphics.DrawString(string.Format("{0}:{1}", i, j), Font, Brushes.Black, new Rectangle(i*_tileWidth, j*_tileHeight, _tileWidth, _tileHeight));
}
}
using (var p = new Pen(Color.Black))
{
for (var i = offsetX + 1; i < x; i++)
{
e.Graphics.DrawLine(p, i*_tileWidth, 0, i*_tileWidth, y*_tileHeight);
}
for (var i = offsetY + 1; i < y; i++)
{
e.Graphics.DrawLine(p, 0, i*_tileHeight, x*_tileWidth, i*_tileHeight);
}
}
e.Graphics.FillRectangle(Brushes.White, AutoScrollPosition.X*-1 + 10, AutoScrollPosition.Y*-1 + 10, 35, 14);
e.Graphics.DrawString("TEST", DefaultFont, new SolidBrush(Color.Red), AutoScrollPosition.X*-1 + 10, AutoScrollPosition.Y*-1 + 10);
e.Graphics.TranslateTransform(AutoScrollPosition.X*-1, AutoScrollPosition.Y*-1);
ControlPaint.DrawBorder(e.Graphics, ClientRectangle, Color.Red, actualBorderWidth, ButtonBorderStyle.None,
Color.Red, actualBorderWidth, ButtonBorderStyle.None, Color.Red, actualBorderWidth, ButtonBorderStyle.Solid,
Color.Red, actualBorderWidth, ButtonBorderStyle.Solid);
}
protected override void OnScroll(ScrollEventArgs e)
{
if (DesignMode)
{
base.OnScroll(e);
return;
}
if (e.Type == ScrollEventType.First)
{
LockWindowUpdate(Handle);
}
else
{
LockWindowUpdate(IntPtr.Zero);
Update();
if (e.Type != ScrollEventType.Last) LockWindowUpdate(Handle);
}
}
protected override void OnMouseWheel(MouseEventArgs e)
{
if (VScroll && (ModifierKeys & Keys.Shift) == Keys.Shift)
{
VScroll = false;
LockWindowUpdate(Handle);
base.OnMouseWheel(e);
LockWindowUpdate(IntPtr.Zero);
Update();
VScroll = true;
}
else
{
LockWindowUpdate(Handle);
base.OnMouseWheel(e);
LockWindowUpdate(IntPtr.Zero);
Update();
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool LockWindowUpdate(IntPtr hWnd);
}
Can this flickering be fixed?
I was able to solve my problem by overriding CreateParams:
protected override CreateParams CreateParams
{
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= NativeMethods.WS_EX_CONTROLPARENT;
cp.ExStyle &= (~NativeMethods.WS_EX_CLIENTEDGE);
cp.Style &= (~NativeMethods.WS_BORDER);
cp.Style |= NativeMethods.WS_BORDER;
return cp;
}
}
and here is required NativeMethods class:
internal static class NativeMethods
{
public const int WS_EX_CONTROLPARENT = 0x00010000;
public const int WS_EX_CLIENTEDGE = 0x00000200;
public const int WS_BORDER = 0x00800000;
}
below is the result:
You can simply derive from UserControl. It has built-in support for showing scrollbars and also has built-in support for showing borders.
All of built-in features of UserControl can be added to your control which is derived from ScrollableControl but with some additional try and error effort or taking look at source code of UserControl. But using UserControl class you can simply have those features.
Show Border
To show border, it's enough to set its BorderStyle to FixedSingle to get desired feature:
Show Scrollbars
To gain scroll feature, it's enough to set its AutoScroll to true and also set a suitable AutoScrollMinSize for control. Then when the width or height of the control is less than width or height of given size, the suitable scrollbar will be shown.
Custom Border Color
I also suppose you want to have different border color for the control, then it's enough to override WndProc and handle WM_NCPAINT and draw custom border for the control like this:
In above example, I've used the same technique which I used for Changing BorderColor of TextBox with a small change, here I checked if the BorderStyle equals to FixedSingle the I drew the border with desired color.
Enable the designer to act like a Parent Control at design time
If you want to enable it's designer to be able to drop some controls onto your UserControl, it's enough to decorate it with [Designer(typeof(ParentControlDesigner))]. This way, when you drop your UserControl on form, it can host other controls like a panel control. If you don't like this feature, just don't decorate it with that attribute and it will use Control designer by default which doesn't act like a parent control.
I'm new to Xamarin Android and currently working on a floating action button, I implemented View.IOnTouchListener and normal button click event (faButton.Click += floatButtonPressed;) to carry out my actions. But for the case MotionEventActions.Move, it doesn't work as I wanted. Moving left and right it works fine but for top and bottom it will move downwards a little bit whenever I start moving it. Besides, when I move the button to screen border it will be able to exceed the screen. Hence, I tried detect screen size and restrict it but it still not good enough, is there any other available solution or settings?
public bool OnTouch(View v, MotionEvent e)
{
switch (e.Action)
{
case MotionEventActions.Down:
oldXvalue = e.GetX();
oldYvalue = e.GetY();
if (oldXvalue == e.GetX() && oldYvalue == e.GetY())
{
return false;
}
break;
case MotionEventActions.Up:
if (oldXvalue == e.GetX() && oldYvalue == e.GetY())
{
return false;
}
break;
case MotionEventActions.Move:
var xleft = (int)(e.RawX - oldXvalue);
var xright = xleft + v.Width;
var ytop = (int)(e.RawY - oldYvalue);
var ybtm = (ytop + v.Height);
if (xleft + v.Width >= intWidth)
{
break;
}
if (xleft <= 0)
{
break;
}
if (ytop + v.Height >= intHeight)
{
break;
}
if (ytop <= 0)
{
break;
}
v.Layout(xleft, ytop, xright, ybtm);
break;
}
return true;
}
You can get the screen height and screen width first. And when the button exceed the screen you need to reset the button position.
Try the following code:
public class MainActivity : Activity, IOnTouchListener
{
Button dragAbleBt;
int screenWidth = 0;
int screenHeight = 0;
int lastX = 0, lastY = 0;
public bool OnTouch(View v, MotionEvent e)
{
MotionEventActions ea = e.Action;
switch (ea) {
case MotionEventActions.Down:
lastX = (int)e.RawX;
lastY = (int)e.RawY;
break;
case MotionEventActions.Move:
int dx = (int)e.RawX - lastX;
int dy = (int)e.RawY - lastY;
int left = v.Left + dx;
int right = v.Right + dx;
int top = v.Top + dy;
int bottom = v.Bottom + dy;
if (left < 0)
{
left = 0;
right = left + v.Width;
}
if (right > screenWidth)
{
right = screenWidth;
left = right - v.Width;
}
if (top < 0)
{
top = 0;
bottom = top + v.Height;
}
if (bottom > screenHeight)
{
bottom = screenHeight;
top = bottom - v.Height;
}
v.Layout(left, top, right, bottom);
lastX = (int) e.RawX;
lastY = (int) e.RawY;
v.PostInvalidate();
break;
case MotionEventActions.Up:
break;
}
return false;
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView (Resource.Layout.Main);
//DisplayMetrics dm = Resources.DisplayMetrics;
//screenWidth = dm.WidthPixels;
//screenHeight = dm.HeightPixels;
dragAbleBt = FindViewById<Button>(Resource.Id.button1);
dragAbleBt.SetOnTouchListener(this);
}
public override void OnWindowFocusChanged(bool hasFocus)
{
base.OnWindowFocusChanged(hasFocus);
if (hasFocus)
{
Rect outRect = new Rect();
this.Window.FindViewById(Window.IdAndroidContent).GetDrawingRect(outRect);
screenWidth = outRect.Width();
screenHeight = outRect.Height();
}
}
}
I tried detect screen size and restrict it but it still not good enough, is there any other available solution or settings?
You may got the whole screen width and height, the button will exceed the screen height, In this kind of situation you need get the view drawing area by this.Window.FindViewById(Window.IdAndroidContent).GetDrawingRect(outRect)
Screen Shot:
Note: I am using the emulator that is slow, If you are using the real device it will be faster.
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);
}
I would like to implement a feature in my application, the problem is that I don't know where to search because I don't know the name/term of this feature.
In some applications, when you move the Form near a border/corner of the screen, the application automatically self adhering to that border until you drag the form far the border to un-adherit.
I don't have any application example to show using this feature, sorry about that.
Someone could explain me which is the name/term of this feature, and where I can find a source to examine the techniques used to implement this feature (in WinForms)?.
That feature is sometimes called 'snapping', 'sticky' or 'magnetic' windows, like used in WinAmp. An example implementation can be found at CodeProject: A .NET Snap To Screen Form.
The C# version comes down to this:
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
public partial class Form1 : Form
{
private const int SnapOffset = 35;
private const int WM_WINDOWPOSCHANGING = 70;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_WINDOWPOSCHANGING)
{
SnapToDesktopBorder(this, m.LParam, 0);
}
base.WndProc(ref m);
}
private void SnapToDesktopBorder(Form clientForm, IntPtr intPtr, int widthAdjustment)
{
var newPosition = new WINDOWPOS();
newPosition = (WINDOWPOS)System.Runtime.InteropServices.Marshal.PtrToStructure(intPtr, typeof(WINDOWPOS));
if (newPosition.y == 0 || newPosition.x == 0)
{
return;
// Nothing to do!
}
// Adjust the client size for borders and caption bar
Rectangle ClientRect = clientForm.RectangleToScreen(clientForm.ClientRectangle);
ClientRect.Width += (SystemInformation.FrameBorderSize.Width * 2) - widthAdjustment;
ClientRect.Height += (SystemInformation.FrameBorderSize.Height * 2) + SystemInformation.CaptionHeight;
// Now get the screen working area (without taskbar)
Rectangle WorkingRect = Screen.FromControl(clientForm).WorkingArea;
// Left border
if (newPosition.x >= WorkingRect.X - SnapOffset && newPosition.x <= WorkingRect.X + SnapOffset)
{
newPosition.x = WorkingRect.X;
}
// Get screen bounds and taskbar height
// (when taskbar is horizontal)
Rectangle ScreenRect = Screen.FromControl(clientForm).Bounds;
int TaskbarHeight = ScreenRect.Height - WorkingRect.Height;
// Top border (check if taskbar is on top
// or bottom via WorkingRect.Y)
if (newPosition.y >= -SnapOffset && (WorkingRect.Y > 0 && newPosition.y <= (TaskbarHeight + SnapOffset)) || (WorkingRect.Y <= 0 && newPosition.y <= (SnapOffset)))
{
if (TaskbarHeight > 0)
{
newPosition.y = WorkingRect.Y;
// Horizontal Taskbar
}
else
{
newPosition.y = 0;
// Vertical Taskbar
}
}
// Right border
if (newPosition.x + ClientRect.Width <= WorkingRect.Right + SnapOffset && newPosition.x + ClientRect.Width >= WorkingRect.Right - SnapOffset)
{
newPosition.x = WorkingRect.Right - (ClientRect.Width + SystemInformation.FrameBorderSize.Width);
}
// Bottom border
if (newPosition.y + ClientRect.Height <= WorkingRect.Bottom + SnapOffset && newPosition.y + ClientRect.Height >= WorkingRect.Bottom - SnapOffset)
{
newPosition.y = WorkingRect.Bottom - (ClientRect.Height + SystemInformation.FrameBorderSize.Height);
}
// Marshal it back
System.Runtime.InteropServices.Marshal.StructureToPtr(newPosition, intPtr, true);
}
}
But the code seems to be a bit bloated, I think it can be greatly simplified. It also only works on desktop borders, not other windows.
See also Anyone familiar with a good “sticky windows” library for Winforms?, both answers linking to other CodeProject solutions: SnapFormExtender - a magnet for your MDI child forms (2004) and Sticky Windows - How to make your (top-level) forms to stick one to the other or to the screen, also from 2004.