Tab height does not reflect high DPI on custom/userpaint TabControl - c#

I have written a custom TabControl class, but I'm unable to make the tabs adapt their heights on high DPI screens. On a screen with 200% scaling, the tabs are halfway covered by the actual tab page and its controls, like this:
Apparently the TabControl doesn't adapt the tab heights to fit the larger font, and as a result, the top of the actual page is too high up and covers my tabs. What can I do to enforce the tabs to adapt?
The form has AutoScaleMode set to Dpi, and everything else looks fine, except for this. I'm targeting .NET 4.5.2, and the dpiAware setting is set to true in the manifest file.
Here is my code for the custom TabControl:
/// <summary>
/// A TabControl without 3D borders and other annoyances. Taken from
/// https://stackoverflow.com/questions/27469886/change-color-of-unused-space-of-tabcontrol/27472230
/// and modified.
/// </summary>
public class CleanTabControl : TabControl
{
private class NativeMethods
{
[DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
}
private const int WM_SETFONT = 0x30;
private const int WM_FONTCHANGE = 0x1d;
private int hoverTab = -1;
public event MouseEventHandler CloseClick;
public CleanTabControl()
{
// Take over the painting completely, we want transparency and double-buffering
SetStyle(ControlStyles.UserPaint | ControlStyles.SupportsTransparentBackColor, true);
DoubleBuffered = ResizeRedraw = true;
}
protected override void OnCreateControl()
{
// Necessary to give tabs the correct width
base.OnCreateControl();
OnFontChanged(EventArgs.Empty);
}
protected override void OnFontChanged(EventArgs e)
{
// Necessary to give tabs the correct width
base.OnFontChanged(e);
IntPtr hFont = Font.ToHfont();
NativeMethods.SendMessage(Handle, WM_SETFONT, hFont, (IntPtr)(-1));
NativeMethods.SendMessage(Handle, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
UpdateStyles();
}
public override Color BackColor
{
// Override TabControl.BackColor, we need transparency
get { return Color.Transparent; }
set { base.BackColor = Color.Transparent; }
}
protected override void OnPaint(PaintEventArgs e)
{
// ... lot of painting code
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (SelectedTab != null)
{
if (GetImageRectangle(SelectedIndex).Contains(e.Location))
CloseClick(this, e);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
hoverTab = -1;
for (int i = 0; i < TabCount; i++)
{
if (GetTabRect(i).Contains(e.Location))
{
if (GetImageRectangle(i).Contains(e.Location))
TabPages[i].ImageIndex = 1;
else
{
hoverTab = i;
TabPages[i].ImageIndex = 0;
}
}
else
TabPages[i].ImageIndex = 0;
}
Invalidate();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
hoverTab = -1;
for (int i = 0; i < TabCount; i++)
{
TabPages[i].ImageIndex = 0;
}
Invalidate();
}
private Rectangle GetImageRectangle(int index)
{
Rectangle r = GetTabRect(index);
int width = ImageList.ImageSize.Width;
int height = ImageList.ImageSize.Height;
int x = r.Right - width - Padding.X;
int y = (r.Top + r.Height - height) / 2 + 1;
if (index != SelectedIndex)
y += 1;
return new Rectangle(x, y, width, height);
}
}
}

I found the solution. In OnCreateControl(), add:
ItemSize = new Size(ItemSize.Width, ItemSize.Height * DpiRatio);
where DpiRatio is the scale factor (e.g. 2 for 200% scaling).

Related

Can't save forms with my custom userControl in it

After I created a userControl and established it in a Windows form, this form could no longer be saved. When I press Ctrl + S or the Save button nothing happens, the small * on the form file name suggests that it was not saved. Also, my form contains a property for a color. When I change this, the color doesn't change and the property window doesn't seem to respond anymore.
Code of custom userControl
namespace FlowGui
{
public partial class FlowProgressBar: UserControl
{
// Public Variables with Setters
private int maxValue;
[Bindable(true), Category("Behavior")]
public int Maximum
{
get
{
return maxValue;
}
set
{
maxValue = value;
DrawBar();
}
}
private int minValue;
[Bindable(true), Category("Behavior")]
public int Minimum
{
get
{
return minValue;
}
set
{
minValue = value;
DrawBar();
}
}
private int currentValue;
[Bindable(true), Category("Behavior")]
public int Value
{
get
{
return currentValue;
}
set
{
currentValue = value;
DrawBar();
}
}
private Color barColor;
[Bindable(true), Category("Appearance")]
public Color BarColor
{
get
{
return barColor;
}
set
{
barColor = value;
CreateBarImage();
DrawBar();
}
}
// private Values for Drawing
private Image canvas;
private Graphics gfx;
private Bitmap barImage;
public FlowProgressBar()
{
InitializeComponent();
}
// Load
private void FlowProgressBar_Load(object sender, EventArgs e)
{
maxValue = 100;
minValue = 0;
currentValue = 0;
barColor = Color.LightBlue;
// Init Graphic
InitGraphics();
CreateBarImage();
DrawBar();
}
private void CreateBarImage()
{
barImage = new Bitmap(10, 50);
Bitmap currentBarImage = Properties.Resources.BarImage_Basic;
for(int x=0; x<10; x++)
{
for (int y = 0; y < 50; y++)
{
Color barPreColor = currentBarImage.GetPixel(x, y);
int r = barPreColor.R * barColor.R / 255;
int g = barPreColor.G * barColor.G / 255;
int b = barPreColor.B * barColor.B / 255;
Color pixelColor = Color.FromArgb(r,g,b);
currentBarImage.SetPixel(x, y, pixelColor);
}
}
}
private void InitGraphics()
{
canvas = new Bitmap(pictureBoxBar.Width, pictureBoxBar.Height);
gfx = Graphics.FromImage(canvas);
}
private void DrawBar()
{
int barWidth = pictureBoxBar.Width;
int barHeight = pictureBoxBar.Height;
Image bgImage = Properties.Resources.BarBG_Raised_Grey;
gfx.DrawImage(bgImage,-200,0,barWidth+400,barHeight);
gfx.DrawImage(barImage, 0, 0, 100, barHeight);
pictureBoxBar.Image = canvas;
}
// Behaviour
private void FlowProgressBar_SizeChanged(object sender, EventArgs e)
{
InitGraphics();
DrawBar();
}
private void pictureBoxBar_Paint(object sender, PaintEventArgs e)
{
InitGraphics();
DrawBar();
}
}
}
My first thought was that it could be because the userControll is in another project and only linked. But even after I implemented the userControll in the main project, my described problems occurred. So I undo this.
I have no idea what I'm doing wrong. Thank you very much for your help.
Solved!
The problem was the pictureBoxBar_Paint() event. After I removed it from the userControl everything runs.

TrackBar with Moving Percentage Label

I cobbled together a custom TrackBar with a percentage representing the current Slider position using the answers from here and here
My custom control is as follows:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace CustomControls
{
public partial class TrackBarPercent : TrackBar
{
/// <summary>
/// Initializes a new instance of <see cref="TrackBarPercent"/>.
/// </summary>
public TrackBarPercent() : base()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
}
private Font _percentFont = SystemFonts.DefaultFont;
private Color _percentColor = Color.Black;
private SolidBrush _drawBrush = new SolidBrush(Color.Black);
/// <summary>
/// Gets or sets the font of the percent text.
/// </summary>
[Description("Gets or sets the font of the percent text.")]
[Browsable(true)]
public Font PercentFont
{
get { return _percentFont; }
set { _percentFont = value; }
}
/// <summary>
/// Gets or sets the color of the percent text.
/// </summary>
[Description("Gets or sets the color of the percent text.")]
[Browsable(true)]
public Color PercentColor
{
get { return _percentColor; }
set { _percentColor = value; _drawBrush = new SolidBrush(_percentColor); }
}
private Rectangle Slider
{
get
{
try
{
RECT rc = new RECT();
SendMessageRect(this.Handle, TBM_GETTHUMBRECT, IntPtr.Zero, ref rc);
return new Rectangle(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
}
catch
{
return new Rectangle();
}
}
}
private const int TBM_GETTHUMBRECT = 0x400 + 25;
private struct RECT { public int left, top, right, bottom; }
[DllImport("user32.dll", EntryPoint = "SendMessageW")]
private static extern IntPtr SendMessageRect(IntPtr hWnd, int msg, IntPtr wp, ref RECT lp);
private int lastValue = 0;
protected override void WndProc(ref Message m)
{
try
{
base.WndProc(ref m);
if (m.Msg == 0x0F)
{
if (this.Value != lastValue)
{
lastValue = this.Value;
this.Invalidate();
}
using (Graphics graphics = Graphics.FromHwnd(Handle))
{
DrawPercentText(graphics);
}
}
}
catch { }
}
private void DrawPercentText(Graphics g)
{
try
{
Point pctTxtPoint;
if (this.Orientation == System.Windows.Forms.Orientation.Horizontal)
{
pctTxtPoint = new Point((Slider.Left + Slider.Right) / 2, Slider.Bottom);
}
else
{
pctTxtPoint = new Point(Slider.Right + 5, Slider.Top - 2);
}
double percent = (double)this.Value * 100.0 / (double)this.Maximum;
string pctStr = string.Format("{0:0.##}%", percent);
g.DrawString(pctStr, _percentFont, _drawBrush, pctTxtPoint);
}
catch { }
}
[DllImport("user32.dll")]
public extern static int SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
private static int MakeParam(int loWord, int hiWord)
{
return (hiWord << 16) | (loWord & 0xffff);
}
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
SendMessage(this.Handle, 0x0128, MakeParam(1, 0x1), 0);
}
}
}
This works, for the most part, but the text gets redrawn every time I hover my mouse over the slider causing the text to look bold. This effect goes away when another control gets focus. Additionally, I'm getting periodic exceptions thrown by the control that I do not understand.
I keep track of the last value that was set for this control so that it invalidates the view if the value has changed. I needed this to prevent the control from drawing the percentage text over the previous text. I don't think this approach was very elegant but I'm not sure how to go about doing it better.
How can I fix this code up so that the text doesn't become "bold" and fix the exceptions I'm getting?

Change combobox drop down list border color in C#

Is it possible to change the border color of a combobox dropdown list in c#?
I want to change the white border to a darker shade to match the dark scheme. I searched through the .net documentation and found the DropDownList.BorderStyle property. However, I'm not sure if this will work. I am using WinForms.
Here is the class I'm using for the combobox:
public class FlattenCombo : ComboBox
{
private Brush BorderBrush = new SolidBrush(SystemColors.WindowFrame);
private Brush ArrowBrush = new SolidBrush(SystemColors.ControlText);
private Brush DropButtonBrush = new SolidBrush(SystemColors.Control);
private Color _borderColor = Color.Black;
private ButtonBorderStyle _borderStyle = ButtonBorderStyle.Solid;
private static int WM_PAINT = 0x000F;
private Color _ButtonColor = SystemColors.Control;
public Color ButtonColor
{
get { return _ButtonColor; }
set
{
_ButtonColor = value;
DropButtonBrush = new SolidBrush(this.ButtonColor);
this.Invalidate();
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case 0xf:
Graphics g = this.CreateGraphics();
Pen p = new Pen(Color.Black);
g.FillRectangle(BorderBrush, this.ClientRectangle);
//Draw the background of the dropdown button
Rectangle rect = new Rectangle(this.Width - 17, 0, 17, this.Height);
g.FillRectangle(DropButtonBrush, rect);
//Create the path for the arrow
System.Drawing.Drawing2D.GraphicsPath pth = new System.Drawing.Drawing2D.GraphicsPath();
PointF TopLeft = new PointF(this.Width - 13, (this.Height - 5) / 2);
PointF TopRight = new PointF(this.Width - 6, (this.Height - 5) / 2);
PointF Bottom = new PointF(this.Width - 9, (this.Height + 2) / 2);
pth.AddLine(TopLeft, TopRight);
pth.AddLine(TopRight, Bottom);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//Determine the arrow's color.
if (this.DroppedDown)
{
ArrowBrush = new SolidBrush(SystemColors.HighlightText);
}
else
{
ArrowBrush = new SolidBrush(SystemColors.ControlText);
}
//Draw the arrow
g.FillPath(ArrowBrush, pth);
break;
default:
break;
}
}
[Category("Appearance")]
public Color BorderColor
{
get { return _borderColor; }
set
{
_borderColor = value;
Invalidate(); // causes control to be redrawn
}
}
[Category("Appearance")]
public ButtonBorderStyle BorderStyle
{
get { return _borderStyle; }
set
{
_borderStyle = value;
Invalidate();
}
}
protected override void OnLostFocus(System.EventArgs e)
{
base.OnLostFocus(e);
this.Invalidate();
}
protected override void OnGotFocus(System.EventArgs e)
{
base.OnGotFocus(e);
this.Invalidate();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.Invalidate();
}
}
While the FlatComboBoxAdapter is indeed not available. It is still possible to capture the WM_CTLCOLORLISTBOX windows message and apply a native GDI rectangle to the drop down border. It is a bit of work, but it does the job. (There's a link to an example at the bottom if you wish to skip this)
First off, if you're not familiar with the WM_CTLCOLORLISTBOX windows message, it is described as such:
"Sent to the parent window of a list box before the system draws the list box. By responding to this message, the parent window can set the text and background colors of the list box by using the specified display device context handle."
The message constant would be define like so:
const int WM_CTLCOLORLISTBOX = 0x0134;
Once the message constant is defined, condition for it within your custom ComboBox's overridden WndProc() event:
protected override void WndProc(ref Message m)
{
// Filter window messages
switch (m.Msg)
{
// Draw a custom color border around the drop down pop-up
case WM_CTLCOLORLISTBOX:
base.WndProc(ref m);
DrawNativeBorder(m.LParam);
break;
default: base.WndProc(ref m); break;
}
}
The DrawNativeBorder() method is where we are going to use the Win API to draw our rectangle. It accepts the handle to the drop down as an argument. Before we can do that however, we need to define the native methods, enumerations, and structs that are going to be used:
public enum PenStyles
{
PS_SOLID = 0,
PS_DASH = 1,
PS_DOT = 2,
PS_DASHDOT = 3,
PS_DASHDOTDOT = 4
}
public enum ComboBoxButtonState
{
STATE_SYSTEM_NONE = 0,
STATE_SYSTEM_INVISIBLE = 0x00008000,
STATE_SYSTEM_PRESSED = 0x00000008
}
[StructLayout(LayoutKind.Sequential)]
public struct COMBOBOXINFO
{
public Int32 cbSize;
public RECT rcItem;
public RECT rcButton;
public ComboBoxButtonState buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll")]
public static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[DllImport("gdi32.dll")]
public static extern int ExcludeClipRect(IntPtr hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
[DllImport("gdi32.dll")]
public static extern IntPtr CreatePen(PenStyles enPenStyle, int nWidth, int crColor);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern void Rectangle(IntPtr hdc, int X1, int Y1, int X2, int Y2);
public static int RGB(int R, int G, int B)
{
return (R | (G << 8) | (B << 16));
}
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left_, int top_, int right_, int bottom_)
{
Left = left_;
Top = top_;
Right = right_;
Bottom = bottom_;
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is RECT))
{
return false;
}
return this.Equals((RECT)obj);
}
public bool Equals(RECT value)
{
return this.Left == value.Left &&
this.Top == value.Top &&
this.Right == value.Right &&
this.Bottom == value.Bottom;
}
public int Height
{
get
{
return Bottom - Top + 1;
}
}
public int Width
{
get
{
return Right - Left + 1;
}
}
public Size Size { get { return new Size(Width, Height); } }
public Point Location { get { return new Point(Left, Top); } }
// Handy method for converting to a System.Drawing.Rectangle
public System.Drawing.Rectangle ToRectangle()
{
return System.Drawing.Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
public static RECT FromRectangle(Rectangle rectangle)
{
return new RECT(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom);
}
public void Inflate(int width, int height)
{
this.Left -= width;
this.Top -= height;
this.Right += width;
this.Bottom += height;
}
public override int GetHashCode()
{
return Left ^ ((Top << 13) | (Top >> 0x13))
^ ((Width << 0x1a) | (Width >> 6))
^ ((Height << 7) | (Height >> 0x19));
}
public static implicit operator Rectangle(RECT rect)
{
return System.Drawing.Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom);
}
public static implicit operator RECT(Rectangle rect)
{
return new RECT(rect.Left, rect.Top, rect.Right, rect.Bottom);
}
}
The DrawNativeBorder() method is defined as:
/// <summary>
/// Non client area border drawing
/// </summary>
/// <param name="handle">The handle to the control</param>
public static void DrawNativeBorder(IntPtr handle)
{
// Define the windows frame rectangle of the control
RECT controlRect;
GetWindowRect(handle, out controlRect);
controlRect.Right -= controlRect.Left; controlRect.Bottom -= controlRect.Top;
controlRect.Top = controlRect.Left = 0;
// Get the device context of the control
IntPtr dc = GetWindowDC(handle);
// Define the client area inside the control rect so it won't be filled when drawing the border
RECT clientRect = controlRect;
clientRect.Left += 1;
clientRect.Top += 1;
clientRect.Right -= 1;
clientRect.Bottom -= 1;
ExcludeClipRect(dc, clientRect.Left, clientRect.Top, clientRect.Right, clientRect.Bottom);
// Create a pen and select it
Color borderColor = Color.Magenta;
IntPtr border = WinAPI.CreatePen(WinAPI.PenStyles.PS_SOLID, 1, RGB(borderColor.R,
borderColor.G, borderColor.B));
// Draw the border rectangle
IntPtr borderPen = SelectObject(dc, border);
Rectangle(dc, controlRect.Left, controlRect.Top, controlRect.Right, controlRect.Bottom);
SelectObject(dc, borderPen);
DeleteObject(border);
// Release the device context
ReleaseDC(handle, dc);
SetFocus(handle);
}
I used the color Magenta to clearly show the painting. That will do it for the border painting. There is however, one more problem. When the drop down displays and the mouse hasn't moved over a drop down item, the default border still shows. To handle that issue, you'd have to determine when the drop down is fully open. Then send a WM_CTLCOLORLISTBOX message of our own to update the border.
I crudely check for the drop down display moment using a timer. I tried various other options but they didn't pan out. Honestly, if someone has a better solution that works, that would be great.
You'll need a timer to check when the drop down actually drops fully:
private Timer _dropDownCheck = new Timer();
The timer is a field in your custom combobox. Set it up in your custom combobox constructor after InitializeComponent():
_dropDownCheck.Interval = 100;
_dropDownCheck.Tick += new EventHandler(dropDownCheck_Tick);
Override the custom combobox's OnDropDown() event, and set up the timer tick event:
/// <summary>
/// On drop down
/// </summary>
protected override void OnDropDown(EventArgs e)
{
base.OnDropDown(e);
// Start checking for the dropdown visibility
_dropDownCheck.Start();
}
/// <summary>
/// Checks when the drop down is fully visible
/// </summary>
private void dropDownCheck_Tick(object sender, EventArgs e)
{
// If the drop down has been fully dropped
if (DroppedDown)
{
// Stop the time, send a listbox update
_dropDownCheck.Stop();
Message m = GetControlListBoxMessage(this.Handle);
WndProc(ref m);
}
}
Lastly, create the following methods to get the drop down handle and to create a WM_CTLCOLORLISTBOX message to send to the control:
/// <summary>
/// Creates a default WM_CTLCOLORLISTBOX message
/// </summary>
/// <param name="handle">The drop down handle</param>
/// <returns>A WM_CTLCOLORLISTBOX message</returns>
public Message GetControlListBoxMessage(IntPtr handle)
{
// Force non-client redraw for focus border
Message m = new Message();
m.HWnd = handle;
m.LParam = GetListHandle(handle);
m.WParam = IntPtr.Zero;
m.Msg = WM_CTLCOLORLISTBOX;
m.Result = IntPtr.Zero;
return m;
}
/// <summary>
/// Gets the list control of a combo box
/// </summary>
/// <param name="handle">Handle of the combo box itself</param>
/// <returns>A handle to the list</returns>
public static IntPtr GetListHandle(IntPtr handle)
{
COMBOBOXINFO info;
info = new COMBOBOXINFO();
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info);
return GetComboBoxInfo(handle, ref info) ? info.hwndList : IntPtr.Zero;
}
That about does that, if you're still confused, it's probably just easier to take a look at the control in this example VS 2010 custom combobox project I have provided:
http://www.pyxosoft.com/downloads/CustomComboBoxBorderColor.zip
I wrestled with this for far too long. I see from a previous question you asked that you've got code from: http://www.codeproject.com/Articles/2433/Flatten-that-Combobox and have set the BackColor:
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case 0xf:
base.BackColor = Color.Black;
Using the WndProc, this was the closest I got but it didn't color the dropdownlists' Popup/ItemSelection border:
...
if (this.DroppedDown)
{
ArrowBrush = new SolidBrush(SystemColors.HighlightText);
Rectangle dropDownBounds = new Rectangle(0, 0, Width,Height + DropDownHeight );
//ControlPaint.DrawBorder(g, dropDownBounds, _borderColor, _borderStyle);
ControlPaint.DrawBorder(g, dropDownBounds, _borderColor,1, ButtonBorderStyle.Dotted ,Color.GreenYellow,1,ButtonBorderStyle.Solid ,Color.Gold,1,ButtonBorderStyle.Dashed,Color.HotPink,1,ButtonBorderStyle.Solid);
}
It turns out that the class FlatComboBoxAdapter needed to do this is private and the recommendation is use WPF:
ComboBox DropDown-Area Border Color
More info: Combobox borderstyle - but even with LarsTech and Hans suggestions (using the non-client paint message) it still doesn't work and flickers horribly.
Other suggestion besides WPF, rewrite Combobox .Net Framework code: http://www.dotnetframework.org/default.aspx/FX-1434/FX-1434/1#0/untmp/whidbey/REDBITS/ndp/fx/src/WinForms/Managed/System/WinForms/ComboBox#cs/2/ComboBox#cs

How to restrict form movement to horizontal?

I have a standard form with standard title bar that user may grab and move the form around. In certain situations I want to restrict this movement to horizontal only, so no matter how the mouse actually moves, the form remains on same Y coordinate.
To do this, I catch move event and when I detect deviation from Y, I move the form back to the original Y. Like that:
private void TemplateSlide_Move(object sender, EventArgs e)
{
int y = SlideSettings.LastLocation.Y;
if (y != this.Location.Y)
{
SlideSettings.LastLocation = new Point(this.Location.X, y);
this.Location=Settings.LastLocation;
}
}
But this causes a lot of flicker. Also because form actually moves for a brief moment away from desired Y this causes other issues specific to my program.
Is there a way to prevent form from moving away from desired Y coordinate?
Trap WM_MOVING and modify the RECT structure in LPARAM accordingly.
Something like:
public partial class Form1 : Form
{
public const int WM_MOVING = 0x216;
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private int OriginalY = 0;
private int OriginalHeight = 0;
private bool HorizontalMovementOnly = true;
public Form1()
{
InitializeComponent();
this.Shown += new EventHandler(Form1_Shown);
this.SizeChanged += new EventHandler(Form1_SizeChanged);
this.Move += new EventHandler(Form1_Move);
}
void Form1_Move(object sender, EventArgs e)
{
this.SaveValues();
}
void Form1_SizeChanged(object sender, EventArgs e)
{
this.SaveValues();
}
void Form1_Shown(object sender, EventArgs e)
{
this.SaveValues();
}
private void SaveValues()
{
this.OriginalY = this.Location.Y;
this.OriginalHeight = this.Size.Height;
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_MOVING:
if (this.HorizontalMovementOnly)
{
RECT rect = (RECT)System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, typeof(RECT));
rect.Top = this.OriginalY;
rect.Bottom = rect.Top + this.OriginalHeight;
System.Runtime.InteropServices.Marshal.StructureToPtr(rect, m.LParam, false);
}
break;
}
base.WndProc(ref m);
}
}
When it's appropriate, just use the Mouse's Y coordinate, substituting the X coordinate with your static value. e.g.
... // e.g Mouse Down
originalX = Mouse.X; // Or whatever static X value you have.
... // e.g Mouse Move
// Y is dynamically updated while X remains static
YourObject.Location = new Point(originalX, Mouse.Y);

Round shaped buttons

How do I make a button in a round shape rather than the conventional rectangle.
I am using winforms(2.0)
First make a class. Give it name: "RoundButton".
Then write the code directly as this:
using System;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Linq;
using System.Text;
namespace WindowsFormsApplication1
{
public class RoundButton : Button
{
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
GraphicsPath grPath = new GraphicsPath();
grPath.AddEllipse(0, 0, ClientSize.Width, ClientSize.Height);
this.Region = new System.Drawing.Region(grPath);
base.OnPaint(e);
}
}
}
Then, build your application and close this.
Now go to the toolbox and you will see a control named RoundButton.
Then drag and drop this on your Windows form and test it.
GraphicsPath p = new GraphicsPath();
p.AddEllipse(1, 1, button1.Width - 4, button1.Height - 4);
button1.Region = new Region(p);
Code project has many articles about these kinds of things, especially the article RoundButton Windows Control - Ever Decreasing Circles might be of interest since it shows you have to do different kinds of round buttons.
This or this could help if you need to implement your own Button class based on the default Windows Forms button. You can also search online for more examples.
What about 'GDI'?
Implementation Example:
#region <Round Corners> : (Properties)
// [Use Round Corners]
private bool useRoundCorners = false;
[Category("Control Corners"), DisplayName("Round Corners")]
[Description("Set Round Corners.")]
[Browsable(true)]
public bool UseRoundBorders
{
get { return useRoundCorners; }
set { if (useRoundCorners != value) { useRoundCorners = value; Invalidate(); } }
}
// [Ellipse Radius]
private int ellipseRadius = 20;
[Category("Control Corners"), DisplayName("Radius")]
[Description("Set Corner (Ellipse) Radius")]
[Browsable(true)]
public int EllipseRadius
{
get { return ellipseRadius.FixedValue(0, 90); }
set { if (ellipseRadius != value.FixedValue(0, 90)) { ellipseRadius = value; Invalidate(); } }
}
#endregion
#region <Round Corners> : (Draw)
[DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
private static extern IntPtr CreateRoundRectRgn
(
int nLeftRect, // x-coordinate of upper-left corner
int nTopRect, // y-coordinate of upper-left corner
int nRightRect, // x-coordinate of lower-right corner-
int nBottomRect, // y-coordinate of lower-right corner
int nWidthEllipse, // width of ellipse
int nHeightEllipse // height of ellipse
);
/// <summary> Draw Corners (Round or Square). </summary>
/// <param name="e"></param>
private void DrawCorners()
{
if (useRoundCorners) { this.Region = Region.FromHrgn(CreateRoundRectRgn(0, 0, Width, Height, ellipseRadius, ellipseRadius)); }
else { this.Region = Region.FromHrgn(CreateRoundRectRgn(0, 0, Width, Height, 0, 0)); }
}
#endregion
/// <summary> Redraw (Update) the Control. </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
DrawCorners();
}
Extension to Limit Round Corner Radius (*Range: 0-90)
Author: Salvador
// Extension to Set Min. & Max. Values to Properties
public static class Extensions
{
public static int FixedValue(this int value, int min, int max)
{
if (value >= min && value <= max) { return value; }
else if (value > max) { return max; }
else if (value < min) { return min; }
else { return 1; }
}
}
This is what you want
public class RoundButton : Control
{
private readonly Label lbl;
public RoundButton() : base()
{
lbl = new Label
{
Text = Text,
ForeColor = ForeColor,
BackColor = BackColor,
Font = Font
};
CenterInParent();
}
private void CenterInParent()
{
lbl.Left = (Width - lbl.Width) / 2;
lbl.Top = (Height - lbl.Height) / 2;
}
protected override void OnPaint(PaintEventArgs e)
{
GraphicsPath grPath = new GraphicsPath();
grPath.AddEllipse(0, 0, ClientSize.Width, ClientSize.Height);
Region = new Region(grPath);
base.OnPaint(e);
}
protected override void OnMove(EventArgs e)
{
CenterInParent();
base.OnMove(e);
}
protected override void OnTextChanged(EventArgs e)
{
lbl.Text = Text;
base.OnTextChanged(e);
}
protected override void OnForeColorChanged(EventArgs e)
{
lbl.ForeColor = ForeColor;
base.OnForeColorChanged(e);
}
protected override void OnBackColorChanged(EventArgs e)
{
lbl.BackColor = BackColor;
base.OnBackColorChanged(e);
}
protected override void OnFontChanged(EventArgs e)
{
lbl.Font = Font;
base.OnFontChanged(e);
}
}
Pros:
No cuts
Round
Cons:
Wrong designer loader for round button
public class OptionsMenu : Button
{
public OptionsMenu(int NoOfOptions, Point Location, ControlCollection controls,
Size ButtonSize, int DistanceBetweenOptions)
{
Button[] buttons = new Button[NoOfOptions];
for (int i = 0; i < NoOfOptions; i++)
{
buttons[i] = new Button()
{
Size = ButtonSize,
};
GraphicsPath p = new GraphicsPath();
p.AddEllipse(1, 1, buttons[i].Width - 4, buttons[i].Height - 4);
buttons[i].Region = new Region(p);
buttons[i].Location = new Point(Location.X, Location.Y + DistanceBetweenOptions * i);
controls.Add(buttons[i]);
}
}
}
You can call it like this:
OptionsMenu menu = new OptionsMenu(4, new Point(50, 50), Controls, new Size(20, 20), 40);
Controls.AddRange(new Control[]
{
menu
});

Categories