Related
In windows 11, I have found that almost every Systray Icon's Context Menu has a round corner.
As there is no Systray Menu for WPF application, I have used Window's form NotiIcon and ContextMenuStrip.
I have fully customized ToolStripProfessionalRenderer with ProfessionalColorTable to support dark mode
as per clients' requirements. Now I'm looking for rounding the corner. I was used "RoundedEdges = true;"
but not worked. I need to know exactly which method need to override to round the corner of ContextMenuStrip.
Here below is my renderer sample code.
///Menu Renderer
public class MenuRenderer : ToolStripProfessionalRenderer
{
//Fields
private Color primaryColor;
private Color textColor;
private int arrowThickness;
private WindowsTheme systrayTheme;
[Browsable(false)]
public WindowsTheme SystrayTheme
{
get { return systrayTheme; }
set { systrayTheme = value; }
}
//Constructor
public MenuRenderer(bool isMainMenu, Color primaryColor, Color textColor, Color menuItemMouseOverColor, Color menuItemMouseOverBorderColor, WindowsTheme theme)
: base(new MenuColorTable(isMainMenu, primaryColor, menuItemMouseOverColor, menuItemMouseOverBorderColor, theme))
{
RoundedEdges = true;
this.primaryColor = primaryColor;
this.systrayTheme = theme;
if (isMainMenu)
{
arrowThickness = 2;
if (textColor == Color.Empty) //Set Default Color
this.textColor = Color.Gainsboro;
else//Set custom text color
this.textColor = textColor;
}
else
{
arrowThickness = 1;
if (textColor == Color.Empty) //Set Default Color
this.textColor = Color.DimGray;
else//Set custom text color
this.textColor = textColor;
}
}
//Overrides
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
base.OnRenderItemText(e);
e.Item.ForeColor = e.Item.Selected ? Color.White : textColor;
}
protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
{
//Fields
var graph = e.Graphics;
var arrowSize = new Size(5, 10);
var arrowColor = e.Item.Selected ? Color.White : primaryColor;
var rect = new Rectangle(e.ArrowRectangle.Location.X, (e.ArrowRectangle.Height - arrowSize.Height) / 2,
arrowSize.Width, arrowSize.Height);
using (GraphicsPath path = new GraphicsPath())
using (Pen pen = new Pen(arrowColor, arrowThickness))
{
//Drawing
graph.SmoothingMode = SmoothingMode.AntiAlias;
path.AddLine(rect.Left, rect.Top, rect.Right, rect.Top + rect.Height / 2);
path.AddLine(rect.Right, rect.Top + rect.Height / 2, rect.Left, rect.Top + rect.Height);
graph.DrawPath(pen, path);
}
}
protected override void OnRenderGrip(ToolStripGripRenderEventArgs e)
{
Rectangle rectangle = new Rectangle(e.AffectedBounds.X, e.AffectedBounds.Y, e.AffectedBounds.Width, e.AffectedBounds.Height);
GraphicsPath graphicsPath = RoundedRect(rectangle, 8);
using (Pen pen = new Pen(Color.Green,5))
{
e.Graphics.DrawPath(pen, graphicsPath);
//e.Graphics.FillPath(pen.Brush, graphicsPath);
}
}
protected override void OnRenderToolStripPanelBackground(ToolStripPanelRenderEventArgs e)
{
//base.OnRenderToolStripPanelBackground(e);
Rectangle rectangle = new Rectangle(e.ToolStripPanel.Location.X, e.ToolStripPanel.Location.Y,
e.ToolStripPanel.ClientRectangle.Width, e.ToolStripPanel.ClientRectangle.Height);
GraphicsPath graphicsPath = RoundedRect(rectangle, 8);
using (Pen pen = new Pen(Color.Green, 5))
{
//e.Graphics.DrawPath(pen, graphicsPath);
e.Graphics.FillPath(pen.Brush, graphicsPath);
}
}
protected override void OnRenderStatusStripSizingGrip(ToolStripRenderEventArgs e)
{
//base.OnRenderStatusStripSizingGrip(e);
Rectangle rectangle = new Rectangle(e.AffectedBounds.X, e.AffectedBounds.Y, e.AffectedBounds.Width, e.AffectedBounds.Height);
GraphicsPath graphicsPath = RoundedRect(rectangle, 8);
using (Pen pen = new Pen(Color.Green,5))
{
e.Graphics.DrawPath(pen, graphicsPath);
//e.Graphics.FillPath(pen.Brush, graphicsPath);
}
}
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
{
//Fields
//var graph = e.Graphics;
//var borderZise = new Size(e.AffectedBounds.Width + 10, e.AffectedBounds.Height + 10);
//var borderColor = Color.DeepPink;
//var rect = new Rectangle(e.AffectedBounds.X, (e.AffectedBounds.Height - borderZise.Height) / 2,
// borderZise.Width, borderZise.Height);
//using (GraphicsPath path = new GraphicsPath())
//using (Pen pen = new Pen(borderColor, arrowThickness))
//{
// //Drawing
// graph.SmoothingMode = SmoothingMode.AntiAlias;
// path.AddLine(rect.Left, rect.Top, rect.Right, rect.Top + rect.Height / 2);
// path.AddLine(rect.Right, rect.Top + rect.Height / 2, rect.Left, rect.Top + rect.Height);
// graph.DrawPath(pen, path);
//}
//DrawRoundedRectangle(e.Graphics, e.AffectedBounds.X, e.AffectedBounds.Y, e.AffectedBounds.Width - 5, e.AffectedBounds.Height, 15, Color.Red);
//Rectangle rectangle = new Rectangle(e.AffectedBounds.X, e.AffectedBounds.Y, e.AffectedBounds.Width, e.AffectedBounds.Height);
//GraphicsPath graphicsPath = RoundedRect(rectangle, 8);
//using (Pen pen = new Pen(Color.Green, 2))
//{
// //e.Graphics.DrawPath(pen, graphicsPath);
// e.Graphics.FillPath(pen.Brush, graphicsPath);
//}
}
public GraphicsPath RoundedRect(Rectangle bounds, int radius)
{
int diameter = radius * 2;
Size size = new Size(diameter, diameter);
Rectangle arc = new Rectangle(bounds.Location, size);
GraphicsPath path = new GraphicsPath();
if (radius == 0)
{
path.AddRectangle(bounds);
return path;
}
// top left arc
path.AddArc(arc, 180, 90);
// top right arc
arc.X = bounds.Right - diameter;
path.AddArc(arc, 270, 90);
// bottom right arc
arc.Y = bounds.Bottom - diameter;
path.AddArc(arc, 0, 90);
// bottom left arc
arc.X = bounds.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
return path;
}
}
////////Custom Menu
public class CustomContextMenu : ContextMenuStrip
{
//Fields
private bool isMainMenu;
private int menuItemHeight = 20;
private int menuItemWidth = 20;
private Color menuItemTextColor = Color.Empty;
private Color primaryColor = Color.Empty;
private Color MouseOverColor = Color.Empty;
private Color MouseOverBorderColor = Color.Empty;
private WindowsTheme systrayTheme = WindowsTheme.Light;
private Bitmap menuItemHeaderSize;
//Constructor
public CustomContextMenu()
{
}
//Properties
[Browsable(false)]
public bool IsMainMenu
{
get { return isMainMenu; }
set { isMainMenu = value; }
}
[Browsable(false)]
public int MenuItemHeight
{
get { return menuItemHeight; }
set { menuItemHeight = value; }
}
[Browsable(false)]
public int MenuItemWidth
{
get { return menuItemWidth; }
set { menuItemWidth = value; }
}
[Browsable(false)]
public Color MenuItemTextColor
{
get { return menuItemTextColor; }
set { menuItemTextColor = value; }
}
[Browsable(false)]
public Color PrimaryColor
{
get { return primaryColor; }
set { primaryColor = value; }
}
[Browsable(false)]
public Color MenuItemMouseOverColor
{
get { return MouseOverColor; }
set { MouseOverColor = value; }
}
[Browsable(false)]
public Color MenuItemMouseOverBorderColor
{
get { return MouseOverBorderColor; }
set { MouseOverBorderColor = value; }
}
[Browsable(false)]
public WindowsTheme SystrayTheme
{
get { return systrayTheme; }
set { systrayTheme = value; }
}
//Private methods
private void LoadMenuItemHeight()
{
if (isMainMenu)
menuItemHeaderSize = new Bitmap(menuItemWidth, menuItemHeight);
else menuItemHeaderSize = new Bitmap(menuItemWidth-5, menuItemHeight);
foreach (Forms.ToolStripMenuItem menuItemL1 in this.Items)
{
menuItemL1.ImageScaling = ToolStripItemImageScaling.None;
if (menuItemL1.Image == null) menuItemL1.Image = menuItemHeaderSize;
foreach (Forms.ToolStripMenuItem menuItemL2 in menuItemL1.DropDownItems)
{
menuItemL2.ImageScaling = ToolStripItemImageScaling.None;
if (menuItemL2.Image == null) menuItemL2.Image = menuItemHeaderSize;
foreach (Forms.ToolStripMenuItem menuItemL3 in menuItemL2.DropDownItems)
{
menuItemL3.ImageScaling = ToolStripItemImageScaling.None;
if (menuItemL3.Image == null) menuItemL3.Image = menuItemHeaderSize;
foreach (Forms.ToolStripMenuItem menuItemL4 in menuItemL3.DropDownItems)
{
menuItemL4.ImageScaling = ToolStripItemImageScaling.None;
if (menuItemL4.Image == null) menuItemL4.Image = menuItemHeaderSize;
///Level 5++
}
}
}
}
}
//Overrides
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (this.DesignMode == false)
{
switch (SystrayTheme)
{
case WindowsTheme.Light:
{
menuItemTextColor = Color.Black;
}
break;
case WindowsTheme.Dark:
{
menuItemTextColor = Color.White;
}
break;
case WindowsTheme.HighContrast:
{
menuItemTextColor = Utility.ToDrawingColor(System.Windows.SystemColors.MenuTextColor);
}
break;
}
this.Renderer = new MenuRenderer(isMainMenu, primaryColor, menuItemTextColor, MouseOverColor, MouseOverBorderColor, SystrayTheme);
LoadMenuItemHeight();
}
}
}
Update:
Recently I was tried to override OnPaint() method. But the round corner is not smooth and also sub-menu is not rounded.
private GraphicsPath GetGraphicsPath(RectangleF rect,float radious)
{
GraphicsPath path = new GraphicsPath();
path.StartFigure();
path.AddArc(rect.X, rect.Y, radious, radious, 180, 90);
path.AddArc(rect.Width - radious, rect.Y, radious, radious, 270, 90);
path.AddArc(rect.Width - radious, rect.Height - radious, radious, radious, 0, 90);
path.AddArc(rect.X, rect.Height - radious, radious, radious, 90, 90);
path.CloseFigure();
return path;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
RectangleF rectSurface = new RectangleF(0, 0, this.Width, this.Height);
RectangleF rectBorder = new RectangleF(1, 1, this.Width - 0.8F, this.Height - 1);
if (borderRadius > 2) //Rounder
{
using (GraphicsPath pathSurface = GetGraphicsPath(rectSurface, borderRadius))
using (GraphicsPath pathBorder = GetGraphicsPath(rectBorder, borderRadius - 1F))
using (Pen penSurface = new Pen(this.BackColor, 2))
using (Pen penBorder = new Pen(borderColor, borderSize))
{
penBorder.Alignment = PenAlignment.Inset;
//Menu surface
this.Region = new Region(pathSurface);
//Draw surface border for HD result
e.Graphics.DrawPath(penSurface, pathSurface);
//Menu Border
if (borderSize >= 1)
e.Graphics.DrawPath(penBorder, pathBorder);
}
}
else //Normal contex menu
{
//Menu surface
this.Region = new Region(rectSurface);
//Menu border
if (borderSize >= 1)
{
using (Pen penBorder = new Pen(borderColor, borderSize))
{
penBorder.Alignment = PenAlignment.Inset;
e.Graphics.DrawRectangle(penBorder, 0, 0, this.Width - 1, this.Height - 1);
}
}
}
}
//Overrides
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (this.DesignMode == false)
{
//this.Parent.BackColorChanged += Parent_BackColorChanged;
switch (SystrayTheme)
{
case WindowsTheme.Light:
{
menuItemTextColor = Color.Black;
}
break;
case WindowsTheme.Dark:
{
menuItemTextColor = Color.White;
}
break;
case WindowsTheme.HighContrast:
{
menuItemTextColor = Utility.ToDrawingColor(System.Windows.SystemColors.MenuTextColor);
}
break;
}
this.RenderMode = ToolStripRenderMode.Professional;
this.Renderer = new MenuRenderer(isMainMenu, primaryColor, menuItemTextColor, MouseOverColor, MouseOverBorderColor, SystrayTheme);
LoadMenuItemHeight();
}
}
private void Parent_BackColorChanged(object sender, EventArgs e)
{
if (this.DesignMode)
this.Invalidate();
}
Thanks in Advance. Any help will be appreciated.
You can find the solution on this page: https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-rounded-corners
I also include the solution here just in case the external link gets deleted.
// The enum flag for DwmSetWindowAttribute's second parameter, which tells the function what attribute to set.
public enum DWMWINDOWATTRIBUTE
{
DWMWA_WINDOW_CORNER_PREFERENCE = 33
}
// The DWM_WINDOW_CORNER_PREFERENCE enum for DwmSetWindowAttribute's third parameter, which tells the function
// what value of the enum to set.
public enum DWM_WINDOW_CORNER_PREFERENCE
{
DWMWCP_DEFAULT = 0,
DWMWCP_DONOTROUND = 1,
DWMWCP_ROUND = 2,
DWMWCP_ROUNDSMALL = 3
}
// Import dwmapi.dll and define DwmSetWindowAttribute in C# corresponding to the native function.
[DllImport("dwmapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern long DwmSetWindowAttribute(IntPtr hwnd,
DWMWINDOWATTRIBUTE attribute,
ref DWM_WINDOW_CORNER_PREFERENCE pvAttribute,
uint cbAttribute);
And to apply the rounded corners to the ContextMenuStrip:
ContextMenuStrip notifyIconMenu = new ContextMenuStrip();
...
var attribute = DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE;
var preference = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
DwmSetWindowAttribute(notifyIconMenu.Handle, attribute, ref preference, sizeof(uint));
UPD
Here is the implementation of the submenu:
ToolStripMenuItem submenu = new ToolStripMenuItem("submenu");
notifyIconMenu.Items.Add(submenu);
ToolStripMenuItem submenuItem1 = new ToolStripMenuItem("submenu1");
submenu.DropDownItems.Add(submenuItem1);
DwmSetWindowAttribute(submenu.DropDown.Handle, attribute, ref preference, sizeof(uint));
In case you want to go the proprietary implementation route for rounded corners, here's a method for creating "smooth" ones:
Graphics graphics = ...;
...
graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
Did you try to set different RendererMode in CustomContextMenu other than the default one?
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (this.DesignMode == false)
{
switch (SystrayTheme)
{
case WindowsTheme.Light:
{
menuItemTextColor = Color.Black;
}
break;
case WindowsTheme.Dark:
{
menuItemTextColor = Color.White;
}
break;
case WindowsTheme.HighContrast:
{
menuItemTextColor = Utility.ToDrawingColor(System.Windows.SystemColors.MenuTextColor);
}
break;
}
this.RenderMode = ToolStripRenderMode.Professional;
this.Renderer = new MenuRenderer(isMainMenu, primaryColor, menuItemTextColor, MouseOverColor, MouseOverBorderColor, SystrayTheme);
LoadMenuItemHeight();
}
}
I'm trying to create UX that is familiar to Bootstrap 4 Radio Button Group.
I know I can change Appearance=Button but this wont give me desired effect.
This is what I get:
and this is what I want:
I found similar question, but it uses WPF and I need WinForms.
Second one suggest using list view, but in that question list is vertical, but I need horizontal align.
I'm looking for a control that has first button rounded on left and last on right (just like on second image)
My question is: Does anyone know is such control exists? If yes I'll be grateful for anu links, if no then any tips regarding how to create such control are welcome (I know how to create custom controls and paint them so links on how to get started with user controls aren't needed)
EDIT
Because my question got down voted I'm adding code that I've written to create such control.
This is result I got:
and here is code:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace UserControls
{
public sealed class RadioGroupBox: UserControl
{
public RadioGroupBox()
{
DoubleBuffered = true;
ResizeRedraw = true;
Padding = new Padding(2);
CalculateItemWidth();
}
public event EventHandler SelectedIndexChanged;
protected override Size DefaultSize => new Size(200, 30);
private int _cornerRadius = 2;
public int CornerRadius
{
get => _cornerRadius;
set
{
if(value==_cornerRadius) return;
_cornerRadius = value;
Invalidate();
}
}
private string[] _items = {"A", "B", "C"};
[Category("Data")]
//[DefaultValue(null)]
[Description("Items")]
public string[] Items
{
get => _items;
set
{
if(value==_items) return;
_items = value;
CalculateItemWidth();
Invalidate();
}
}
private int _itemWidth;
private void CalculateItemWidth()
{
if (_items == null || _items.Length == 0)
{
_itemWidth = 0;
return;
}
var width = ClientRectangle.Width - Padding.Horizontal;
_itemWidth = width / _items.Length;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
CalculateItemWidth();
//Debug.WriteLine(_itemWidth);
CalculateSelectedItem(e.Location);
}
private int _selectedIndex = -1;
public int SelectedIndex
{
get => _selectedIndex;
set
{
if(value==_selectedIndex) return;
_selectedIndex = value;
Invalidate();
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
_click = true;
if (_hoverPos > -1)
{
if (_selectedIndex != _hoverPos)
{
_selectedIndex = _hoverPos;
SelectedIndexChanged?.Invoke(this, e);
}
}
Invalidate();
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
_click = false;
Invalidate();
}
private int _hoverPos = -1;
private bool _click;
private void CalculateSelectedItem(Point mouseLocation)
{
var clientRect = GetPaddedRectangle();
int pos;
if (!clientRect.Contains(mouseLocation))
{
pos = -1;
}
else
{
pos = mouseLocation.X / _itemWidth;
if (pos > _items.Length - 1)
{
pos = -1;
}
}
if (pos != _hoverPos)
{
_hoverPos = pos;
Invalidate();
}
//Debug.WriteLine(_hoverPos);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
CalculateItemWidth();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var rect = GetPaddedRectangle();
LinearGradientBrush normalBrush = new LinearGradientBrush(rect,
Color.White,
Color.LightBlue,
LinearGradientMode.Vertical);
LinearGradientBrush hoverBlush = new LinearGradientBrush(rect,
Color.RoyalBlue,
Color.MediumBlue,
LinearGradientMode.Vertical);
LinearGradientBrush selectedBrush = new LinearGradientBrush(rect,
Color.DodgerBlue,
Color.Blue,
LinearGradientMode.Vertical);
//e.Graphics.FillRectangle(Brushes.Aqua, this.ClientRectangle);
using (GraphicsPath path = RoundedRect(rect, _cornerRadius))
{
e.Graphics.FillPath(normalBrush, path);
e.Graphics.DrawPath(Pens.DodgerBlue, path);
}
if (_items == null || _items.Length == 0) return;
if (_hoverPos > -1 || _selectedIndex>-1)
{
var flags = RectangleCorners.None;
if (_hoverPos == 0) flags = RectangleCorners.TopLeft | RectangleCorners.BottomLeft;
if(_hoverPos==_items.Length-1) flags = RectangleCorners.TopRight | RectangleCorners.BottomRight;
var rect2 = new Rectangle(rect.X + _hoverPos * _itemWidth, rect.Y, _itemWidth, rect.Height);
if (_hoverPos == _items.Length - 1)
{
rect2 = new Rectangle(rect.X + _hoverPos * _itemWidth, rect.Y, rect.Width-_hoverPos*_itemWidth, rect.Height);
}
if (_hoverPos > -1 && _selectedIndex != _hoverPos)
{
using (GraphicsPath path = RoundedRect(rect2, _cornerRadius, flags))
{
e.Graphics.FillPath(_click ? Brushes.SteelBlue : hoverBlush, path);
}
}
if (_selectedIndex > -1)
{
rect2 = new Rectangle(rect.X + _selectedIndex * _itemWidth, rect.Y, _itemWidth, rect.Height);
if (_selectedIndex == _items.Length - 1)
{
rect2 = new Rectangle(rect.X + _selectedIndex * _itemWidth, rect.Y, rect.Width - _selectedIndex * _itemWidth, rect.Height);
}
flags = RectangleCorners.None;
if (_selectedIndex == 0) flags = RectangleCorners.TopLeft | RectangleCorners.BottomLeft;
if (_selectedIndex == _items.Length - 1) flags = RectangleCorners.TopRight | RectangleCorners.BottomRight;
using (GraphicsPath path = RoundedRect(rect2, _cornerRadius, flags))
{
e.Graphics.FillPath(selectedBrush, path);
}
}
}
//pionowe linie
for (int i = 1; i <= _items.Length-1; i++)
{
e.Graphics.DrawLine(Pens.DodgerBlue,rect.X+i*_itemWidth,rect.Y, rect.X + i * _itemWidth, rect.Y+rect.Height);
}
StringFormat sf = new StringFormat
{
LineAlignment = StringAlignment.Center,
Alignment = StringAlignment.Center
};
for (var i = 0; i < _items.Length; i++)
{
string item = _items[i];
e.Graphics.DrawString(item, Font, i==_hoverPos?Brushes.White:Brushes.DodgerBlue, new Rectangle(rect.X+i* _itemWidth, rect.Y, _itemWidth, rect.Height), sf);
}
//Debug.WriteLine(MousePosition);
}
private Rectangle GetPaddedRectangle()
{
var rect = ClientRectangle;
var pad = Padding;
return new Rectangle(rect.X + pad.Left,
rect.Y + pad.Top,
rect.Width - pad.Horizontal,
rect.Height - pad.Vertical);
}
[Flags]
public enum RectangleCorners
{
None = 0, TopLeft = 1, TopRight = 2, BottomLeft = 4, BottomRight = 8,
All = TopLeft | TopRight | BottomLeft | BottomRight
}
public static GraphicsPath RoundedRect(Rectangle bounds, int radius, RectangleCorners corners = RectangleCorners.All)
{
int diameter = radius * 2;
GraphicsPath path = new GraphicsPath();
if (radius == 0)
{
path.AddRectangle(bounds);
return path;
}
// Make a GraphicsPath to draw the rectangle.
PointF point1, point2;
// Upper left corner.
if ((RectangleCorners.TopLeft & corners) == RectangleCorners.TopLeft)
{
RectangleF corner = new RectangleF(bounds.X, bounds.Y,diameter, diameter);
path.AddArc(corner, 180, 90);
point1 = new PointF(bounds.X + radius, bounds.Y);
}
else point1 = new PointF(bounds.X, bounds.Y);
// Top side.
if ((RectangleCorners.TopRight & corners) == RectangleCorners.TopRight)
point2 = new PointF(bounds.Right - radius, bounds.Y);
else
point2 = new PointF(bounds.Right, bounds.Y);
path.AddLine(point1, point2);
// Upper right corner.
if ((RectangleCorners.TopRight & corners) == RectangleCorners.TopRight)
{
RectangleF corner = new RectangleF(bounds.Right - diameter, bounds.Y,diameter, diameter);
path.AddArc(corner, 270, 90);
point1 = new PointF(bounds.Right, bounds.Y + radius);
}
else point1 = new PointF(bounds.Right, bounds.Y);
// Right side.
if ((RectangleCorners.BottomRight & corners) == RectangleCorners.BottomRight)
point2 = new PointF(bounds.Right, bounds.Bottom - radius);
else
point2 = new PointF(bounds.Right, bounds.Bottom);
path.AddLine(point1, point2);
// Lower right corner.
if ((RectangleCorners.BottomRight & corners) == RectangleCorners.BottomRight)
{
RectangleF corner = new RectangleF(bounds.Right - diameter,bounds.Bottom - diameter,diameter, diameter);
path.AddArc(corner, 0, 90);
point1 = new PointF(bounds.Right - radius, bounds.Bottom);
}
else point1 = new PointF(bounds.Right, bounds.Bottom);
// Bottom side.
if ((RectangleCorners.BottomLeft & corners) == RectangleCorners.BottomLeft)
point2 = new PointF(bounds.X + radius, bounds.Bottom);
else
point2 = new PointF(bounds.X, bounds.Bottom);
path.AddLine(point1, point2);
// Lower left corner.
if ((RectangleCorners.BottomLeft & corners) == RectangleCorners.BottomLeft)
{
RectangleF corner = new RectangleF(bounds.X, bounds.Bottom - diameter,diameter, diameter);
path.AddArc(corner, 90, 90);
point1 = new PointF(bounds.X, bounds.Bottom - radius);
}
else point1 = new PointF(bounds.X, bounds.Bottom);
// Left side.
if ((RectangleCorners.TopLeft & corners) == RectangleCorners.TopLeft)
point2 = new PointF(bounds.X, bounds.Y + radius);
else
point2 = new PointF(bounds.X, bounds.Y);
path.AddLine(point1, point2);
// Join with the start point.
path.CloseFigure();
return path;
}
}
}
it isn't optimized, it's just prove of concept.
Whole point of this question is to find existing control without need to reinvent the wheel.
If I won't be able to find such control I'll create one and share it.
How can I make a free-form selection (like in paint or photoshop) in a picture box and then crop that selection and save it to a folder?
I already did a rectangle crop but I want that free-form selection..
Here is my rectangle crop:
Image img;
bool mouseClicked;
Point startPoint = new Point();
Point endPoint = new Point();
Rectangle rectCropArea;
private void Button1_Click(System.Object sender, System.EventArgs e)
{
}
private void OnLoad(System.Object sender, System.EventArgs e)
{
loadPrimaryImage();
}
private void loadPrimaryImage()
{
img = Image.FromFile("..\\..\\images.jpg");
PictureBox1.Image = img;
}
private void PicBox_MouseUp(System.Object sender, System.Windows.Forms.MouseEventArgs e)
{
mouseClicked = false;
if ((endPoint.X != -1)) {
Point currentPoint = new Point(e.X, e.Y);
Y1.Text = e.X.ToString();
Y2.Text = e.Y.ToString();
}
endPoint.X = -1;
endPoint.Y = -1;
startPoint.X = -1;
startPoint.Y = -1;
}
private void PicBox_MouseDown(System.Object sender, System.Windows.Forms.MouseEventArgs e)
{
mouseClicked = true;
startPoint.X = e.X;
startPoint.Y = e.Y;
//Display coordinates
X1.Text = startPoint.X.ToString();
Y1.Text = startPoint.Y.ToString();
endPoint.X = -1;
endPoint.Y = -1;
rectCropArea = new Rectangle(new Point(e.X, e.Y), new Size());
}
private void PicBox_MouseMove(System.Object sender, System.Windows.Forms.MouseEventArgs e)
{
Point ptCurrent = new Point(e.X, e.Y);
if ((mouseClicked)) {
if ((endPoint.X != -1)) {
//Display Coordinates
X1.Text = startPoint.X.ToString();
Y1.Text = startPoint.Y.ToString();
X2.Text = e.X.ToString();
Y2.Text = e.Y.ToString();
}
endPoint = ptCurrent;
if ((e.X > startPoint.X & e.Y > startPoint.Y)) {
rectCropArea.Width = e.X - startPoint.X;
rectCropArea.Height = e.Y - startPoint.Y;
} else if ((e.X < startPoint.X & e.Y > startPoint.Y)) {
rectCropArea.Width = startPoint.X - e.X;
rectCropArea.Height = e.Y - startPoint.Y;
rectCropArea.X = e.X;
rectCropArea.Y = startPoint.Y;
} else if ((e.X > startPoint.X & e.Y < startPoint.Y)) {
rectCropArea.Width = e.X - startPoint.X;
rectCropArea.Height = startPoint.Y - e.Y;
rectCropArea.X = startPoint.X;
rectCropArea.Y = e.Y;
} else {
rectCropArea.Width = startPoint.X - e.X;
rectCropArea.Height = startPoint.Y - e.Y;
rectCropArea.X = e.X;
rectCropArea.Y = e.Y;
}
PictureBox1.Refresh();
}
}
private void PicBox_Paint(System.Object sender, System.Windows.Forms.PaintEventArgs e)
{
Pen drawLine = new Pen(Color.Red);
drawLine.DashStyle = DashStyle.Dash;
e.Graphics.DrawRectangle(drawLine, rectCropArea);
}
private void btnCrop_Click(System.Object sender, System.EventArgs e)
{
PictureBox2.Refresh();
Bitmap sourceBitmap = new Bitmap(PictureBox1.Image, PictureBox1.Width, PictureBox1.Height);
Graphics g = PictureBox2.CreateGraphics();
if (!(CheckBox1.Checked)) {
g.DrawImage(sourceBitmap, new Rectangle(0, 0, PictureBox2.Width, PictureBox2.Height), rectCropArea, GraphicsUnit.Pixel);
sourceBitmap.Dispose();
} else {
int x1 = 0;
int x2 = 0;
int y1 = 0;
int y2 = 0;
try {
x1 = Convert.ToInt32(CX1.Text);
x2 = Convert.ToInt32(CX2.Text);
y1 = Convert.ToInt32(CY1.Text);
y2 = Convert.ToInt32(CY2.Text);
} catch (Exception ex) {
MessageBox.Show("Enter valid Coordinates (only Integer values)");
}
if (((x1 < x2 & y1 < y2))) {
rectCropArea = new Rectangle(x1, y1, x2 - x1, y2 - y1);
} else if ((x2 < x1 & y2 > y1)) {
rectCropArea = new Rectangle(x2, y1, x1 - x2, y2 - y1);
} else if ((x2 > x1 & y2 < y1)) {
rectCropArea = new Rectangle(x1, y2, x2 - x1, y1 - y2);
} else {
rectCropArea = new Rectangle(x2, y2, x1 - x2, y1 - y2);
}
PictureBox1.Refresh();
//This repositions the dashed box to new location as per coordinates entered.
g.DrawImage(sourceBitmap, new Rectangle(0, 0, PictureBox2.Width, PictureBox2.Height), rectCropArea, GraphicsUnit.Pixel);
sourceBitmap.Dispose();
}
}
private void pictureBox1_MouseClick(System.Object sender, System.Windows.Forms.MouseEventArgs e)
{
PictureBox1.Refresh();
}
private void CheckBox1_CheckedChanged(System.Object sender, System.EventArgs e)
{
if ((CheckBox1.Checked)) {
CX1.Visible = true;
Label10.Visible = true;
CY1.Visible = true;
Label9.Visible = true;
CX2.Visible = true;
Label8.Visible = true;
CY2.Visible = true;
Label7.Visible = true;
X1.Text = "0";
X2.Text = "0";
Y1.Text = "0";
Y2.Text = "0";
} else {
CX1.Visible = false;
Label10.Visible = false;
CY1.Visible = false;
Label9.Visible = false;
CX2.Visible = false;
Label8.Visible = false;
CY2.Visible = false;
Label7.Visible = false;
}
}
public Form1()
{
Load += OnLoad;
}
}
To copy a free-form selection you need to work with polygons.
Here is a complete example. Just paste this into a new solution and try it out (just change the path to the images).
It will create 2 pictureboxes and load an image into the first one and also create an image. Then you can click on the first image and when you have clicked 2 times it will start to show a selection, when you are finished just press the button and it will copy the selection to the other pictureBox and then save it as an png image.
What it does is to create a brush from the first image and then paint the polygon onto another image and set the other pixels in the rectangle to a background color of your choice, in this case the color: Color.Transparent.
Example:
public partial class Form1 : Form {
private List<Point> _points = new List<Point>();
private PictureBox _pictureBox1;
private PictureBox _pictureBox2;
private Button _button1;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
Size = new Size(1366, 675);
_pictureBox1 = new PictureBox {
Location = new Point(12, 51),
Size = new Size(651, 474),
BorderStyle = BorderStyle.FixedSingle
};
_pictureBox2 = new PictureBox
{
Location = new Point(669, 51),
Size = new Size(651, 474),
BorderStyle = BorderStyle.FixedSingle
};
_button1 = new Button {
Text = #"Copy selected area",
Location = new Point(13, 13),
Size = new Size(175, 23)
};
Controls.AddRange(new Control[] { _pictureBox1, _pictureBox2, _button1 });
_pictureBox1.Image = Image.FromFile(#"d:\temp\Hopetoun_falls.jpg");
_points = new List<Point>();
_pictureBox1.MouseDown += delegate(object o, MouseEventArgs args) { _points.Add(args.Location); _pictureBox1.Refresh(); };
_pictureBox1.Paint += pictureBox1_Paint;
_button1.Click += button_Click;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e) {
if (_points.Count < 2) {
return;
}
var max = _points.Count;
for (int i = 1; i < max; i++) {
e.Graphics.DrawLine(Pens.Red, _points[i-1].X, _points[i-1].Y, _points[i].X, _points[i].Y);
}
e.Graphics.DrawLine(Pens.Red, _points[max - 1].X, _points[max - 1].Y, _points[0].X, _points[0].Y);
}
private static Bitmap GetSelectedArea(Image source, Color bgColor, List<Point> points) {
var bigBm = new Bitmap(source);
using (var gr = Graphics.FromImage(bigBm)) {
// Set the background color.
gr.Clear(bgColor);
// Make a brush out of the original image.
using (var br = new TextureBrush(source)) {
// Fill the selected area with the brush.
gr.FillPolygon(br, points.ToArray());
// Find the bounds of the selected area.
var sourceRect = GetPointListBounds(points);
// Make a bitmap that only holds the selected area.
var result = new Bitmap(sourceRect.Width, sourceRect.Height);
// Copy the selected area to the result bitmap.
using (var resultGr = Graphics.FromImage(result)) {
var destRect = new Rectangle(0, 0, sourceRect.Width, sourceRect.Height);
resultGr.DrawImage(bigBm, destRect, sourceRect, GraphicsUnit.Pixel);
}
// Return the result.
return result;
}
}
}
private static Rectangle GetPointListBounds(List<Point> points) {
int xmin = points[0].X;
int xmax = xmin;
int ymin = points[0].Y;
int ymax = ymin;
for (int i = 1; i < points.Count; i++) {
if (xmin > points[i].X) xmin = points[i].X;
if (xmax < points[i].X) xmax = points[i].X;
if (ymin > points[i].Y) ymin = points[i].Y;
if (ymax < points[i].Y) ymax = points[i].Y;
}
return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
}
private void button_Click(object sender, EventArgs e) {
if (_points.Count < 3) {
return;
}
var img = GetSelectedArea(_pictureBox1.Image, Color.Transparent, _points);
_pictureBox2.Image = img;
_pictureBox2.Image.Save(#"d:\temp\sample.png", ImageFormat.Png);
}
}
I have a Canvas is WPF which includes a lot of shapes and lines. I have included Zooming and Panning feature in the canvas. I have a search like feature which takes X and Y as input and when I request for search the canvas should zoom into that point and translate the canvas in such a way that (X,Y) position is at the center. The zooming works fine but I'm not able to implement the Pan.
I used the following code for PanBorder.cs with the help of this post. Here, I want use PanToPosition(double x , double y) to translate such that the center is at (X,Y).
class PanBorder : Border
{
private UIElement child = null;
private Point origin;
private Point start;private TranslateTransform GetTranslateTransform(UIElement element)
{
return (TranslateTransform)((TransformGroup)element.RenderTransform)
.Children.First(tr => tr is TranslateTransform);
}
private ScaleTransform GetScaleTransform(UIElement element)
{
return (ScaleTransform)((TransformGroup)element.RenderTransform)
.Children.First(tr => tr is ScaleTransform);
}
public override UIElement Child
{
get { return base.Child; }
set
{
if (value != null && value != this.Child)
this.Initialize(value);
base.Child = value;
}
}
public void Initialize(UIElement element)
{
this.child = element;
if (child != null)
{
TransformGroup group = new TransformGroup();
ScaleTransform st = new ScaleTransform();
group.Children.Add(st);
TranslateTransform tt = new TranslateTransform();
group.Children.Add(tt);
child.RenderTransform = group;
child.RenderTransformOrigin = new Point(0.0, 0.0);
this.MouseLeftButtonDown += child_MouseLeftButtonDown;
this.MouseLeftButtonUp += child_MouseLeftButtonUp;
this.MouseMove += child_MouseMove;
}
}
public void Reset()
{
if (child != null)
{
// reset pan
var tt = GetTranslateTransform(child);
tt.X = 0.0;
tt.Y = 0.0;
}
}
public void PanToPosition(double x, double y) {
if (child != null)
{
var tt = GetTranslateTransform(child);
//Pan such that center is at (X,Y)
}
}
#region Child Events
private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (child != null)
{
var tt = GetTranslateTransform(child);
start = e.GetPosition(this);
origin = new Point(tt.X, tt.Y);
this.Cursor = Cursors.Hand;
child.CaptureMouse();
}
}
private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (child != null)
{
child.ReleaseMouseCapture();
this.Cursor = Cursors.Arrow;
}
}
private void child_MouseMove(object sender, MouseEventArgs e)
{
if (child != null)
{
if (child.IsMouseCaptured)
{
var tt = GetTranslateTransform(child);
Vector v = start - e.GetPosition(this);
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
}
}
#endregion
}`
I have the following code to zoom into the canvas:
//for zooming control on the window
void window_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point p = e.MouseDevice.GetPosition(canvasWaSNA);
Matrix m = canvasWaSNA.RenderTransform.Value;
if (e.Delta > 0)
m.ScaleAtPrepend(1.1, 1.1, p.X, p.Y);
else
m.ScaleAtPrepend(1 / 1.1, 1 / 1.1, p.X, p.Y);
canvasWaSNA.RenderTransform = new MatrixTransform(m);
}
And when I input the Point (X,Y) I call the following function:
public void moveToPosition(double x, double y) {
resize();
border.PanToPosition(x, y);
Point p = new Point(x, y);
Matrix m = canvasWaSNA.RenderTransform.Value;
m.ScaleAtPrepend(6, 6, p.X, p.Y);
canvasWaSNA.RenderTransform = new MatrixTransform(m);
}
To resize the canvas I have following code:
private void resize()
{
Matrix m = canvasWaSNA.RenderTransform.Value;
m.SetIdentity();
canvasWaSNA.RenderTransform = new MatrixTransform(m);
border.Reset();
}
I need help. Thanx in advance.
Well, I finally did it. I applied the simple concept of translating a point, as my problem was to translate Point(X,Y) to the center of the screen and apply the zoom. Here is the updated PanToPosition function:
public void PanToPosition(double x, double y, Point center) {
if (child != null)
{
var tt = GetTranslateTransform(child);
start = new Point(x,y);
Point p = new Point(center.X, center.Y);
origin = new Point(tt.X, tt.Y);
Vector v = start - p;
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
}
And I called this function as:
public void moveToPosition(double x, double y) {
resize();
Point center = new Point(this.Width / 2, this.Height / 2);
border.PanToPosition(x, y, center);
Point p = new Point(x, y);
Matrix m = canvasWaSNA.RenderTransform.Value;
m.ScaleAtPrepend(6, 6, p.X, p.Y);
canvasWaSNA.RenderTransform = new MatrixTransform(m);
}
Here, I passed the center point of the screen and the point to be translated to the center. The start point is the point to be translated and p is the destination. Thus, I calculated the translation scale factor.
I am messing with this for the first time. I am going for something like Gyazo does. I have the code below but it won't actually capture the image. It is a bit late and I am a bit tired so could be missing something awfully easy. What it does now is just prints an empty picture at some random size.
private Point start = Point.Empty;
private Point end = Point.Empty;
private void Form2_MouseDown(object sender, MouseEventArgs e)
{
start.X = e.X;
start.Y = e.Y;
}
private void Form2_MouseMove(object sender, MouseEventArgs e)
{
Point p1;
Point p2;
if (((e.Button & MouseButtons.Left) != 0) && (start != Point.Empty))
{
using (Graphics g = this.CreateGraphics())
{
p1 = PointToScreen(start);
end.X = e.X;
end.Y = e.Y;
p2 = PointToScreen(end);
Console.WriteLine(end);
}
}
}
private void Form2_MouseUp(object sender, MouseEventArgs e)
{
Point p1;
Point p2;
Console.WriteLine("Mouse Up");
if ((end != Point.Empty) && (start != Point.Empty))
{
using (Graphics g = this.CreateGraphics())
{
p1 = PointToScreen(start);
p2 = PointToScreen(end);
int x1 = p1.X;
int y1 = p1.Y;
int x2 = p2.X;
int y2 = p2.Y;
using (Bitmap bmpScreenCapture = new Bitmap(x1,y1))
{
using (Graphics gra = Graphics.FromImage(bmpScreenCapture))
{
gra.CopyFromScreen(x1, y1, x2, y2, bmpScreenCapture.Size, CopyPixelOperation.SourceCopy);
bmpScreenCapture.Save("test.png", ImageFormat.Png);
Console.WriteLine("Image Saved");
}
}
}
}
start = Point.Empty;
end = Point.Empty;
Console.WriteLine("Image Saved1");
}
The pictures: http://imgur.com/zcDMbgk,wH1k0uP,PIsiQjw#1 OF course there is nothing there but that shows its printing something at some size.
I have made a slight mistake in calculations.
New Code that works wonders.
#region ============================Dragging============================
private Point start = Point.Empty;
private Point end = Point.Empty;
private void Form2_MouseDown(object sender, MouseEventArgs e)
{
start.X = e.X;
start.Y = e.Y;
}
private void Form2_MouseMove(object sender, MouseEventArgs e)
{
Point p1;
Point p2;
if (((e.Button & MouseButtons.Left) != 0) && (start != Point.Empty))
{
using (Graphics g = this.CreateGraphics())
{
p1 = PointToScreen(start);
end.X = e.X;
end.Y = e.Y;
p2 = PointToScreen(end);
Console.WriteLine(end);
}
}
}
private void Form2_MouseUp(object sender, MouseEventArgs e)
{
Point p1;
Point p2;
Console.WriteLine("Mouse Up");
if ((end != Point.Empty) && (start != Point.Empty))
{
using (Graphics g = this.CreateGraphics())
{
p1 = PointToScreen(start);
p2 = PointToScreen(end);
int x1 = p1.X;
int y1 = p1.Y;
int x2 = p2.X;
int y2 = p2.Y;
int x = x1 - x2;
int y = y1 - y2;
Console.WriteLine(x);
Console.WriteLine(y);
string[] xsp = x.ToString().Split('-');
string[] ysp = y.ToString().Split('-');
int rx = Convert.ToInt32(xsp[1]);
int ry = Convert.ToInt32(ysp[1]);
using (Bitmap bmpScreenCapture = new Bitmap(rx, ry, g))
{
using (Graphics gra = Graphics.FromImage(bmpScreenCapture))
{
gra.CopyFromScreen(x1, y1, 0, 0, bmpScreenCapture.Size, CopyPixelOperation.SourceCopy);
string filename = GenerateRandomString(20) + ".png";
bmpScreenCapture.Save(Path.GetTempPath() + "" + filename, ImageFormat.Png);
Upload(Path.GetTempPath() + "" + filename, filename);
}
}
}
}
start = Point.Empty;
end = Point.Empty;
}
#endregion