Click here to Skip to main content
14,768,479 members
Articles » Desktop Development » Miscellaneous » Windows Forms


19 bookmarked

The More Library

Rate me:
Please Sign up or sign in to vote.
4.92/5 (8 votes)
22 Feb 2021MIT
A growing collection of (MIT licensed) professional Windows Forms Controls for .NET Core.
More.Windows.Forms is a growing collection of MIT licensed Windows Forms Controls for .NET Core. In this post, you will learn about about the library.

Controls (Alphabetically)

  • DocumentPreview: Show document preview and paint it in native units
  • Frame: Structure and draw on panel without affecting the content
  • Hierarchy: Draw and manipulate trees
  • LabelEx: Label that can rotate and supports transparency
  • Line: Use custom line as a separator or a decorator
  • Monitors: Show multi-monitor setup and allow selecting one
  • SecurityMatrix: Classic permissions and roles grid
  • SpriteGrid: Use sprite grid control to build a sprite editor


The DocumentPreview control paints the document background (the ornaments) and allows painting inside it, using native units. When the document needs to be painted, it triggers the paint event and your job is to paint into provided graphics context using native units.

Image 1 Image 2


Set the document size in native units through the DocumentSize property. Default value is 210 x 297, which is the size of standard DIN A4 document (in millimeters).

When your document is an image, you can choose pixel as your unit and set the document size to bitmap size.

After setting the document size, subscribe to the OnDocumentDraw event.

When the document needs to be painted, the control will raise this event and pass it the Graphics. Use it to draw the document in native units, inside rectangle (0,0,document width, document height). The control will automatically perform scale and size to fit operations for you.

The following example shows how to draw grid lines and red margin.

private void _doc_OnDocumentDraw(object sender, DocumentDrawEventArgs e)
    using (Pen gridPen = new Pen(Color.FromArgb(192, 192, 192)))
    using (Pen gridPenHigh=new Pen(Color.FromArgb(128, 128, 128)))
        for (int x = 0; x < _doc.DocumentSize.Width; x+=10) 
            (x%30==0?gridPenHigh:gridPen, x, 0, x, _doc.DocumentSize.Height);
        for (int y = 0; y < _doc.DocumentSize.Height; y += 10)
            e.Graphics.DrawLine(y%30 == 0 ? gridPenHigh : gridPen, 
                                0, y, _doc.DocumentSize.Width,y);
    using(Pen p=new Pen(Color.Red,2)) <span class="pl-c">// Draw margin.</span>
        e.Graphics.DrawRectangle(p, new Rectangle
        (30, 30, _doc.DocumentSize.Width - 60, _doc.DocumentSize.Height - 60));

And the result is:

Image 3

For a nicer effect, you can also set the Shadow property, the PaperColor property and the BorderColor property. The background is drawn in BackColor property.

Document Folds

Document can have multiple folds. You can choose to display or hide folds by setting the Fold property (top left, top right, bottom left, bottom right, or none). The size of the fold is set via the FoldPercent property. A 50% value means fold will span half of the document.

The Fold property is a flag. You may set more than one and all will be shown.

Image 4

Document content will be clipped so that fold appears above it.


To understand the Frame control, you first need to understand its base control - the PanelEx. This control enables you to create non-client border around the PanelEx, and have WinForms respect it, for example, when docking children inside the PanelEx.

Image 5

Background: The PanelEx Control

To create a new container control with a non-client area, derive it from PanelEx, and set the Margin. Area outside will be non-client area and area inside the margins the client area.

For example, if you set all margins to 10 pixels, then a 10 pixels wide border around the control will be non-client area, and the rest client area.

You can use the client area just as you would use the standardPanel control, i.e., you put controls inside, dock them, etc. If you want to paint on non-client, override the Decorate() function.

The functionality of PanelEx provides glue, required to implement various container controls. Here are just a few possibilities:

  • Frame controls
  • Collapsible controls
  • Prompt controls...

How to Derive from PanelEx

Here is the skeleton of panel, derived from the PanelEx. Margin is set in the constructor, and drawing on non-client area should happen inside the Decorate(). This function prepares everything for you: it creates the graphics and calculates all rectangles that form the non-client border.

public class MyPanel : PanelEx
    public class MyPanel() {
        <span class="pl-c">   // Create a 5 pixel unified non client </span>
        <span class="pl-c">   // area around the panel.</span>
        Margin=new Padding(5,5,5,5);

    protected override void Decorate(
        Graphics g, 
        Rectangle lt, <span class="pl-c">  // Left top rectangle of NC area.</span>
        Rectangle rt, <span class="pl-c">  // Right top rectangle of NC area.</span>
        Rectangle lb, <span class="pl-c">  // Left bottom rectangle of NC area.</span>
        Rectangle rb, <span class="pl-c">  // Right bottom rectangle of NC area.</span>
        Rectangle l, <span class="pl-c">   // Left rectangle of NC area.</span>
        Rectangle t, <span class="pl-c">   // Top rectangle of NC area.  </span>
        Rectangle r, <span class="pl-c">   // Right rectangle of NC area.</span>
        Rectangle b) <span class="pl-c">   // Left top rectangle of NC area.</span>
        <span class="pl-c">// Here you draw in nonclient area.</span>

Frame Control: Usage

Back to our Frame control.

It has four areas:

  1. The first (top) area is the title. You can set it via the Title property. It uses the Font property. You can set values of the TitleBackColor and TitleFrontColor. The title is adjusted by TitleAlignment. If aligned left or right, the TitleOffset is the identation. Last but not least, you can increase or reduce (even hide!) title height by changing the value of TitleHeight.
  2. The second area is the outer border. This border has OuterBorderThickness, OuterBorderDarkColor and OuterBorderLightColor. By convention, the dark color is used for top and left edge, and the light color for the bottom and right edge. For the inset effect, simply swap these colors.
  3. The third area is the inner border. This border has InnerBorderThickness, InnerBorderDarkColor and InnerBorderLightColor. By default, the thickness is zero, so no inner border is shown.
  4. The fourth area is the BorderThickness (in pixels). This is simply the space between outer border and inner border. It can be zero but then, it is very hard to differentiate between inner and outer border.


<span class="pl-c">// First the title.</span>
_frame.Title = <span class="pl-pds">"Hello!"</span>;
_frame.TitleAlignment = StringAlignment.Center;
_frame.TitleHeight = 16;
_frame.TitleBackColor = BackColor;
_frame.TitleForeColor = ForeColor;

<span class="pl-c">// Outer border.</span>
_frame.OuterBorderDarkColor = Color.FromKnownColor(KnownColor.ControlDark);
_frame.OuterBorderLightColor = Color.FromKnownColor(KnownColor.ControlLight);
_frame.OuterBorderThickness = 1;

<span class="pl-c">// Inner border (replace dark and light).</span>
_frame.InnerBorderDarkColor = Color.FromKnownColor(KnownColor.ControlLight);
_frame.InnerBorderLightColor = Color.FromKnownColor(KnownColor.ControlDark);
_frame.InnerBorderThickness = 1;

<span class="pl-c">// Pixels between inner and outer color.</span>
_frame.BorderThickness = 2;

The code above creates the frame below:

Image 6


You can use the Hierarchy control to visualise trees. The control only does the layouting; it expects your code to draw content inside events.

Image 7


You set the tree direction by manipulating the Direction property. The control can draw left to right, right to left, top to bottom, and bottom to top trees.

Basic general node properties (shared between all nodes!) are: NodeWidth and NodeHeight. The minimal space in pixels between two nodes is determined by the NodeHorzSpacing and NodeVertSpacing properties.

You feed the data into the control by implementing a simple IHierarchyFeed interface, and then passing it to the Hierarchy via the SetFeed() method.

Here is the interface.

public interface IHierarchyFeed
    IEnumerable<string> Query(string key=null);

It only has one function which returns a collection of node keys (node identifiers).

Since your code is responsible for drawing nodes and edges, the control really does not need to know more about the node. When it needs to draw it, it passes the node key and rectangle in an event and expects your code to do the rest.

The Query() function accepts a parent key parameter. If this parameter is null, the function returns all root node keys (usually just one?), otherwise it returns child nodes of provided parent node.

You can capture all standard control mouse events, and inside the mouse events translate mouse coordinates to nodes key by calling NodeAt() function.


File System Feed

Here's a simple feed implementation for the file system.

public class FileSystemHierarchyFeed : IHierarchyFeed
    private string _rootDir;

    public FileSystemHierarchyFeed(string rootDir) { _rootDir = rootDir; }

    public IEnumerable<string> Query(string key = null)
        if (key == null) return new string[] { _rootDir };
        else return Directory.EnumerateDirectories(key + <span class="pl-pds">@"\"</span>);

In the example above, the full path is used as a node key. If you wanted to draw organigram, you'd probably use database identifier of a person as the key.

Disclaimer: Letting the above file feed scan your c: drive is a very bad idea. Just sayin'.

Drawing Functions

There are two events that you can subscribe to: the DrawEdge event to an edge, i.e., a line connecting two nodes. And the DrawNode event to draw a node. Both events will pass you node key, node rectangle, and an instance of the Graphics to use for drawing.

...but drawing nodes and edges is your job.

This sample demonstrates drawing inside both events.

private void _hierarchy_DrawEdge(object sender, DrawEdgeEventArgs e)
    <span class="pl-c">   // Calculate node centers.</span>
        start = new Point(
            e.ParentRectangle.Left + e.ParentRectangle.Width / 2,
            e.ParentRectangle.Top + e.ParentRectangle.Height / 2),
        end = new Point(
            e.ChildRectangle.Left + e.ChildRectangle.Width / 2,
            e.ChildRectangle.Top + e.ChildRectangle.Height / 2);
    <span class="pl-c">   // And draw the line.</span>
    using (Pen p = new Pen(ForeColor)) 

private void _hierarchy_DrawNode(object sender, DrawNodeEventArgs e)
    <span class="pl-c">  // Extract directory name from the path.</span>
    string dir= Path.GetFileName
                (Path.GetDirectoryName(e.Key+<span class="pl-pds">@"\"</span>));

    <span class="pl-c">  // Draw the node.</span>
    Graphics g = e.Graphics;
    using (Pen forePen = new Pen(ForeColor))
    using (Brush backBrush = new SolidBrush(BackColor),
        foreBrush = new SolidBrush(ForeColor))
    using(StringFormat sf=new StringFormat() { 
        g.FillRectangle(backBrush, e.Rectangle); <span class="pl-c"> // Border.</span>
        g.DrawRectangle(forePen, e.Rectangle); <span class="pl-c">   // Rectangle.</span>
        g.DrawString(dir, Font, foreBrush, e.Rectangle, sf); 
                    <span class="pl-c">// Text.</span>

Mouse Input

You can subscribe to standard mouse events (clicks, moves, etc.) and use the NodeAt() function to find out which node was clicked. For example, if you'd like to highlight node on click, subscribe to the MouseUp event, find out which node was clicked, store its key, and call Refresh() to repaint the control.

private string _highlightedNodeKey;
private void _hierarchy_MouseUp(object sender, MouseEventArgs e)
    _highlightedNodeKey = _hierarchy.NodeAt(e.Location);

Then, in the DrawNode event, check the node key against the _highlightedNodeKey and paint it accordingly.

Styling Edges

Because the DrawEdge event gives you both ends of the edge - the parent node and the child node (with their coordinates), you can choose how to draw your edge. It can be a line, a curve, etc. You may also start your edge at end of the parent node (instead of node center) and draw it to start of the other node. The following code does just that, see image for the result.

private void _hierarchy_DrawEdge(object sender, DrawEdgeEventArgs e)
    <span class="pl-c">// Change start and end location of an edge.</span>
        start = new Point(
            e.ParentRectangle.Top + e.ParentRectangle.Height / 2),
        end = new Point(
            e.ChildRectangle.Top + e.ChildRectangle.Height / 2);
    using (Pen p = new Pen(ForeColor))
        e.Graphics.DrawLine(p, start, end);

Image 8


Label that can rotate and supports transparency.

Image 9


Transparency is a pain in Windows Forms. This feature works well at runtime, but behaves naughty in the designer. The reason is that it avoids double buffering to implement transparency. To make the label background transparent, set the Opacity property from 0% to 100% (for fully transparent label).

Rotate the label by setting the Angle. The unit is in degrees, and the rotation is clockwise.

Finally, you can align the rotated label to the bounding rectangle by assigning values to properties HorzAlignment and VertAlignment.


_label.Text = <span class="pl-pds">"Oh, what a night\nLate December back in sixty - 
              three\nWhat a very special time for me\nAs I remember what a night."</span>;
_label.Angle = 45;
_label.HorzAlignment = StringAlignment.Center;
_label.VertAlignment = StringAlignment.Center;

And the result...

Image 10


Vertical or horizontal line control, used as a separator or a decorator.

Image 11


Set the Orientation property to Horizontal or Vertical. Use line Thickness to set the pen thickness. Set line Text, Font, and ForeColor properties to control appearance of title. If empty, no title is shown. TextAlignment tells where the title is shown (at beginning, end or in the middle of line). If at beginning or end, then TextOffset (in pixels) is used to move title away from begin/end point. BackColor controls line control background, and LineColor is used for line color. DashValues is an array of floats that tells size of pixels and spaces. Default value of {1,0} means solid line (i.e., one pixel, followed by zero spaces). A value of {1,1} is interpreted as a pixel followed by a space. The pattern can be of arbitrary length, i.e., a value of {3,1,1,1} would be interpreted three pixels, followed by one space, followed by one pixel, followed by one space.


_line.Orientation = Orientation.Horizontal;
_line.Text = string.Empty; <span class="pl-c">// Remove text.</span>
_line.LineColor = Color.Khaki;
_line.BackColor = Color.DarkSeaGreen;
_line.Thickness = 6;
_line.DashValues = new float[] { 3,1,1,1 };

Image 12


With the Monitors control, you can show user his or her multi-monitor configuration and enable him or her to select one. This is useful for creating multi-monitor apps that open multiple windows. You can let user configure target monitors for these windows.

The control will automatically detect size and placement of connected monitors.

Image 13


Margin and Padding

Place the control on your window. All your monitors will be selected and drawn in default colors. You can configure the Padding property to create space between monitors. You can also set the Margin property for this control.

Monitor Number

If you set the ShowNumber property - numbers will be displayed inside monitors. You can toggle font for displaying numbers by setting the Font property.

Numbers are displayed as outline text. Border color for each number is configured by setting MonitorTextForeColor, and the inner color of text is configured via the MonitorTextBackColor.

Monitor Edge

Each monitor can be a square or it can have a 3D border like real life monitors do. If you'd like a border, then you must set the ShowEdge property. You can configure 3D colors by setting the EdgeLightColor and EdgeDarkColor. The space between the outer border and the inner border is configured by setting the EdgeThickness value.

Active Monitor

When moving (hovering) the mouse over monitors will be highlighted. To set the colors of monitor under the mouse, use the ActiveMonitorBackColor and ActiveMonitorBackColor properties.

You can manually activate a monitor (without mouse over it) by setting the Activate property to monitor number.

Select Monitor

When clicking on a monitor, you select it and an event called MonitorSelected is raised.

You can also capture the MonitorUnselected event to detect when a monitor is unselected.

You can set the selected monitor manually via the Selected numeric property.

The visual effects for selected monitor are configured by properties SelectedMonitorBackColor and SelectedMonitorForeColor.

Monitor Color

Monitor only uses one color, called the MonitorBackColor. Set it to whatever the standard back color for the monitor should be.


A highly configurable classic permissions and roles grid editor. It accepts a feed interface which must provide a list of roles, permission categories, and permissions. A demo feed implementation is part of the control.

Image 14

You can configure fonts and colors of the control, or implement custom drawing. Custom drawing is implemented by overriding the control's paint functions.


Place the SecurityMatrix on your window. It will show a demo matrix enabling you to customize its' appearance.

Image 15

Call the SetFeed() function to pass the data to it. Set angle for roles by setting the RolesAngle property. Reserve space on top and on left for roles and categories by setting the RolesHeight and CategoriesWidth properties. Set the tick cell size by setting property CellSize.

You can configure fore and front colors, and fonts for matrix titles on the top and left. You can also configure cells' fore and front colors. Finally, when the mouse hovers over the cell, it will change color if you set the PermissionActiveCellBackColor property.

Implementing the Feed

To implement the feed interface, you need to implement four functions.

public interface ISecurityMatrixFeed
    IEnumerable<SecurityRole> QueryRoles();
    IEnumerable<SecurityCategory> QueryCategories();
    IEnumerable<SecurityPermission> QueryPermissions(SecurityCategory category);
    bool this[SecurityRole r, SecurityCategory c, SecurityPermission p] { get; set; } 

Functions accept parameters of types SecurityRole, SecurityPermission, and/or SecurityCategory which are all derived from KeyNamePair and must contain two values:

  • a display name, and
  • a unique identifier.
public class KeyNamePair
    public string Id { get; set; }
    public string DisplayName { get; set; }
public class SecurityRole : KeyNamePair {}
public class SecurityCategory : KeyNamePair {}
public class SecurityPermission : KeyNamePair {}

Function QueryRoles() should return all roles that you'd like to have displayed on top. Function QueryCategories() should return all categories into which you would like to group permissions. Function QueryPermissions() takes a category object as a parameter and returns all permissions pertaining to this category. And finally, indexer this[SecurityRole, SecurityCategory, SecurityPermission]is used to access the ticks.

Binding Ticks to Database

The indexer inside the feed can be connected to the database, update it on set and read from it on get. Following is a naive implementation of indexer as a list.

private class Check
    public SecurityRole SecurityRole { get; set; }
    public SecurityCategory SecurityCategory { get; set; }
    public SecurityPermission SecurityPermission { get; set; }
    public bool Value { get; set; }
<span class="pl-c">  // ...code omitted...</span>
private List<Check> _checks;
<span class="pl-c">  // ...code omitted...</span>
public bool this[SecurityRole r, SecurityCategory c, SecurityPermission p] { 
    get {
        var chk = _checks.FirstOrDefault(chk =>
            && chk.SecurityCategory.Id.Equals(c.Id)
            && chk.SecurityPermission.Id.Equals(p.Id));
        if (chk == null) return false;
        else return chk.Value;
        var chk = _checks.FirstOrDefault(chk =>
            && chk.SecurityCategory.Id.Equals(c.Id)
            && chk.SecurityPermission.Id.Equals(p.Id));
        if (chk != null)
            chk.Value = value;
            _checks.Add(new Check() { 
                SecurityRole=r, SecurityCategory=c, SecurityPermission=p, Value=value 

Custom Paint

You can derive your own control from the SecurityMatrix and implement custom paint handlers for every aspect of the grid.

public class SecurityMatrixEx : SecurityMatrix
    protected override void DrawTick(Graphics g, Rectangle rect)
        rect.Inflate(-12, -12);
        using (Pen tickPen = new Pen(Color.Black, 2))
            g.DrawLine(tickPen, rect.Location, new Point(rect.Right, rect.Bottom));
            g.DrawLine(tickPen, new Point(rect.Right, rect.Top), 
                       new Point(rect.Left, rect.Bottom));

    protected override void DrawPermissionCellBackground
    (Graphics g, Rectangle rect, SecurityRole r, SecurityCategory c, SecurityPermission p)
        rect.Inflate(-8, -8);
        g.DrawRectangle(Pens.Gray, rect);

    protected override void DrawPermissionCellForeground
    (Graphics g, Rectangle rect, SecurityRole r, SecurityCategory c, SecurityPermission p)
    { <span class="pl-c">// Don't draw rectangle around it!</span>

This code above changes ticks to squares, and produces the following output:

Image 16

You can override the following paint functions:

void DrawRoleBackground(Graphics g, Point[] pts, SecurityRole role)
void DrawRoleForeground(Graphics g, Rectangle r, SecurityRole role)
void DrawPermissionBackground(
            Graphics g,
            Rectangle r,
            SecurityCategory category,
            SecurityPermission permission)
void DrawPermissionForeground(
            Graphics g,
            Rectangle r,
            SecurityCategory category,
            SecurityPermission permission)
void DrawTick(Graphics g, Rectangle rect)
void DrawPermissionCellBackground(
            Graphics g,
            Rectangle rect,
            SecurityRole r,
            SecurityCategory c,
            SecurityPermission p)
void DrawPermissionCellForeground(
            Graphics g,
            Rectangle rect,
            SecurityRole r,
            SecurityCategory c,
            SecurityPermission p)
void DrawCategoryForeground(Graphics g, Rectangle r, SecurityCategory category)
void DrawCategoryBackground(Graphics g, Rectangle r, SecurityCategory c)
void DrawCategoryCellBackground(Graphics g, Rectangle rect, SecurityRole r, SecurityCategory c)
void DrawCategoryCellForeground(Graphics g, Rectangle rect, SecurityRole r, SecurityCategory c)

All the sizing and rotation operations are implemented by the control so you don't have to worry about it, i.e., you don't need to draw rotated text for the role, it is rotated and placed to the correct rectangle for you by the control.


Keyboard and focus handling.


Raster image viewer (with mouse events and zoom), a basis for a sprite editor.

Image 17


Place the SpriteGrid control on your window. Set its SourceImage property to the Bitmap you'd like to view or edit and you're done.

You can show or hide rulers using the ShowHorzRuler and ShowVertRuler properties. You can customize rulers by manipulating properties: RulerHeight, RulerWidth, RulesBackgroundColor, MinorTickSize, MajorTickSize, and 'MinorTicksPerMajorTick'.

You can customize grid appearance by manipulating properties GridEdgeLineColor,GridEdgeLineDashPattern, GridTickLineColor, GridTickLineDashPattern.

BackColor is used to draw empty grid, and ForeColor is used for all text (currently just the ruler content).


Passing Image

You pass image to SpriteGrid by assigning the image to the SourceImage property.

_spriteGrid.SourceImage = Image.FromFile(<span class="pl-pds">"pacman.png"</span>);

Responding to Events

SpriteGrid exposes basic mouse events and translates physical coordinates to logical coordinates inside the sprite (i.e., row and column). To manipulate the sprite, simply manipulate the underlying image and call the Refresh() function on SpriteGrid.

public partial class MainWnd : Form
    <span class="pl-c">// ... code omitted ...</span>
    private Bitmap _sprite;

    private void MainWnd_Load(object sender, System.EventArgs e)
        <span class="pl-c">// Create new 16x16 sprite.</span>
        _spriteGrid.SourceImage = _sprite = new Bitmap(16, 16);

    private void _spriteGrid_CellMouseDown(object sender, CellMouseButtonArgs e)
        <span class="pl-c">// Set image pixel.</span>
        _sprite.SetPixel(e.Column, e.Row, Color.Black);
    <span class="pl-c">// ... code omitted ...</span>

Implementing Zoom

SpriteGrid uses mouse wheel for two purposes. If wheel is used without any control key, then sprite is scrolled up and down. If, while using the mouse wheel, you hold down Ctlr key, then the ZoomIn and ZoomOut events are triggered. You can use this to implement zoom.

The simplest implementation (which would not consider the current mouse position), would be increasing and decreasing values or properties CellWidth and CellHeight.

private void _spriteGrid_ZoomIn(object sender, ZoomInArgs e)
    if (_spriteGrid.CellWidth < 32) _spriteGrid.CellWidth++;
    if (_spriteGrid.CellHeight < 32) _spriteGrid.CellHeight++;

private void _spriteGrid_ZoomOut(object sender, ZoomOutArgs e)
    if (_spriteGrid.CellWidth > 1) _spriteGrid.CellWidth--;
    if (_spriteGrid.CellHeight > 1) _spriteGrid.CellHeight--;

And the result.

Image 18

Visible Margins

Sometimes, it makes sense to set a visible sprite margin (for example, to show where image will be cropped). Six properties controlling sprite margin: LeftMargin, RightMargin, TopMargin, BottomMargin, MarginLineThickness, and MarginColor.

Sprite margin is not the same as control margin. Sprite margin is an area of sprite that is visibly marked.

_spriteGrid.SourceImage = Image.FromFile(<span class="pl-pds">"art.png"</span>);
_spriteGrid.LeftMargin = 40;
_spriteGrid.RightMargin = 40;
_spriteGrid.TopMargin = 30;
_spriteGrid.BottomMargin = 40;
_spriteGrid.MarginLineThickness = 4;
_spriteGrid.MarginColor = Color.Red;

Image 19


By responding to mouse events CellMouseDown, CellMouseUp, CellMouseMove, you can detect select operation.

To differentiate between a select and a pixel click, you need to compare mouse coordinates at CellMouseDown event with that of CellMouseUp. If coordinates are the same and there were no CellMouseMove events out of the cell, then it is a click. Otherwise, it is a select.

To make SpriteEdit draw a selection, you set the property GridSelection. GridSelection is a polygon and can be of any shape.

_spriteGrid.SourceImage = Image.FromFile(<span class="pl-pds">"jetpac.png"</span>);
_spriteGrid.SetGridSelection(new GridSelection()
    LineColor = Color.White,
    LineWidth = 3,
    Poly = new Point[] { 
        new Point(200,200), 
        new Point(400, 200), 
        new Point(400,400), 
        new Point(200,400)}

Image 20


This article, along with any associated source code and files, is licensed under The MIT License


About the Author

Tomaž Štih
Founder Wischner Ltd
United Kingdom United Kingdom
Writing code since 1982 and still enjoying it.

Comments and Discussions

GeneralMy vote of 4 Pin
Vincent Radio23-Feb-21 18:28
professionalVincent Radio23-Feb-21 18:28 
GeneralMy vote of 5 Pin
John Dovey23-Feb-21 8:04
MemberJohn Dovey23-Feb-21 8:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.