{"id":13763800,"url":"https://github.com/tstih/more","last_synced_at":"2025-05-10T17:31:07.039Z","repository":{"id":149424350,"uuid":"323088239","full_name":"tstih/more","owner":"tstih","description":"A growing collection  of (MIT licensed) professional Windows Forms Controls for .NET Core. ","archived":false,"fork":false,"pushed_at":"2023-01-28T12:34:22.000Z","size":2602,"stargazers_count":28,"open_issues_count":0,"forks_count":5,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-11-16T23:32:22.595Z","etag":null,"topics":["controls","csharp","dotnet-core","graphics","user-controls","winforms"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tstih.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-20T14:16:35.000Z","updated_at":"2024-03-08T07:30:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"839016f3-5c3e-4e66-b9fa-313fc830d830","html_url":"https://github.com/tstih/more","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tstih%2Fmore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tstih%2Fmore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tstih%2Fmore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tstih%2Fmore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tstih","download_url":"https://codeload.github.com/tstih/more/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253453213,"owners_count":21911061,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["controls","csharp","dotnet-core","graphics","user-controls","winforms"],"created_at":"2024-08-03T15:00:59.672Z","updated_at":"2025-05-10T17:31:02.032Z","avatar_url":"https://github.com/tstih.png","language":"C#","funding_links":[],"categories":["Input Controls / Forms"],"sub_categories":[],"readme":"![status.badge] [![language.badge]][language.url] [![uilib.badge]][uilib.url] [![standard.badge]][standard.url] [![license.badge]][license.url]\n\n# More\n\n![](Images/email.jpg)\n\nMore is a growing collection of (MIT licensed) Windows Forms Controls for .NET Core.\n\n# What's New?\n~~~\n**2022-05-21** Documented the Navigator and the Listing.\n**2022-02-06** Added the Navigator control.\n**2022-02-05** Added the Listing control for displaying source code. \n**2022-02-04** Created this Journal.\n~~~\n\n# What's Planned?\n\n**2022-07-01** Demo project, final version of More.\n**2022-06-01** Style templates and implementation of all TODO items.\n\n# Controls (Alphabetically)\n\n * [DocumentPreview](#documentpreview) Show document preview and paint it in native units.\n * [Frame](#frame) Structure and draw on the panel without affecting the content.\n * [Hierarchy](#hierarchy) Draw and manipulate trees.\n * [LabelEx](#labelex) The label that can rotate text and supports transparency.\n * [Line](#line) Use a custom line as a separator or a decorator.\n * [Listing](#listing) Program code listing. You can use it for emulators, disassemblers, etc.\n * [Monitors](#monitors) Show multi-monitor setup and allow selecting one.\n * [Navigator](#navigator) Navigator is a vertical navigation bar with two levels.\n * [Prompt](#prompt) The Prompt control is a panel with a label. You can use it for forms.\n * [SecurityMatrix](#securitymatrix) SecurityMatrix is a classic *permissions and roles* grid.\n * [SpriteGrid](#spritegrid) Use sprite grid control to build a sprite editor.\n\n---\n\n# DocumentPreview\n\nThe `DocumentPreview` control paints the document background (*the ornaments*) and \nallows painting inside it, using native units. When the document needs to be painted,\nit triggers the paint event, and your job is to paint into provided graphics context\nusing native units. \n\n![](Images/document-preview-1.jpg)\n![](Images/document-preview-2.jpg)\n\n## Usage\n\nSet the document size in native units through the `DocumentSize` property. \nThe default value is 210 x 297, which is the size of a standard DIN A4 document \n(in millimetres).  \n\n \u003e When your document is an image, you can choose pixel as your unit and \n \u003e set the document size to bitmap size.\n\nAfter setting the size of the document, subscribe to the `OnDocumentDraw` event.\n\nWhen the document needs to be painted, the control will raise this event \nand pass it to the `Graphics`. Use it to draw the document in native units,\ninside rectangle `(0,0,document width, document height)`. The control will\nautomatically perform scale and size to fit operations for you.\n\nThe following example shows how to draw grid lines and a red margin.\n\n~~~cs\nprivate void _doc_OnDocumentDraw(object sender, DocumentDrawEventArgs e)\n{\n    using (Pen gridPen = new Pen(Color.FromArgb(192, 192, 192)))\n    using (Pen gridPenHigh=new Pen(Color.FromArgb(128, 128, 128)))\n    {\n        for (int x = 0; x \u003c _doc.DocumentSize.Width; x+=10) \n            e.Graphics.DrawLine(x%30==0?gridPenHigh:gridPen, x, 0, x, _doc.DocumentSize.Height);\n        for (int y = 0; y \u003c _doc.DocumentSize.Height; y += 10)\n            e.Graphics.DrawLine(y%30 == 0 ? gridPenHigh : gridPen, 0, y, _doc.DocumentSize.Width,y);\n    }\n    using(Pen p=new Pen(Color.Red,2)) // Draw margin.\n        e.Graphics.DrawRectangle(p, new Rectangle(30, 30, _doc.DocumentSize.Width - 60, _doc.DocumentSize.Height - 60));\n}\n~~~\n\nAnd the result is:\n\n![](Images/document-preview-4.jpg)\n\nYou can also set the `Shadow` property, the `PaperColor` property, and \nthe `BorderColor` property for a more admirable effect. Finally, the background uses the `BackColor` property.\n\n### Document folds\n\nThe document can have multiple folds. You can choose to display or hide folds by setting the\n`Fold` property (top left, top right, bottom left, bottom right, or none). The size of the\nfold is set via the `FoldPercent` property. A 50% value means the fold will span half of\nthe document.\n\n \u003e  The `Fold` property is a flag. You may set more than one, and the control will show all.\n\n![](Images/document-preview-3.jpg)\n\nThe control will clip document content so that fold appears above it.\n\n---\n\n# Frame\n\nTo understand the `Frame` control, you first need to understand its base control - the\n`PanelEx`. This control enables you to create a non-client border around the `PanelEx` \nand have WinForms respect it, for example, when docking children inside the `PanelEx`.\n\n![](Images/frame-1.jpg)\n\n## Background: The PanelEx Control\n\nTo create a new container control with a non-client area, derive it from `PanelEx`, \nand set the `Margin`. The area outside will be the non-client area, and the area inside the margins \nwill be the client area. \n\n \u003e For example, if you set all margins to 10 pixels, then a 10 pixels wide border\n \u003e around the control will be the non-client area and the rest client area.\n\nYou can use the client area just as you would use the standard panel control i.e. \nyou put controls inside, dock them, etc. If you want to paint on a non-client, override \nthe `Decorate()` function.\n\nThe functionality of `PanelEx` provides the glue required to implement various container \ncontrols. Here are just a few possibilities:\n * Frame controls,\n * Collapsible controls,\n * Prompt controls.\n\n### How to derive from PanelEx\n\nHere is the skeleton of the panel, derived from the `PanelEx`. Margin is set\nin the constructor, and drawing on the non-client area should happen inside the\n`Decorate()`. This function prepares everything for you: it creates the graphics \nand calculates all rectangles that form the non-client border. \n\n~~~cs\npublic class MyPanel : PanelEx\n{\n    public class MyPanel() {\n        // Create a 5 pixel unified non client \n        // area around the panel.\n        Margin=new Padding(5,5,5,5);\n    }\n\n    protected override void Decorate(\n        Graphics g, \n        Rectangle lt, // Left top rectangle of NC area.\n        Rectangle rt, // Right top rectangle of NC area.\n        Rectangle lb, // Left bottom rectangle of NC area.\n        Rectangle rb, // Right bottom rectangle of NC area.\n        Rectangle l, // Left rectangle of NC area.\n        Rectangle t, // Top rectangle of NC area.  \n        Rectangle r, // Right rectangle of NC area.\n        Rectangle b) // Left top rectangle of NC area.\n    {\n        // Here you draw in nonclient area.\n    }\n}\n~~~\n\n## Frame Control: Usage\n\nThe Frame control has four areas. \n * First (top) area is the title. You can set it via\n   the `Title` property. It uses the `Font` property. You can set \n   values of the `TitleBackColor` and `TitleFrontColor`. The title\n   is adjusted by `TitleAlignment`. If aligned left or right, the \n   `TitleOffset` is the indentation. Last but not least, you\n   can increase or reduce (even hide!) title height by changing the \n   value of `TitleHeight`.\n * the Second area is the outer border. This border has `OuterBorderThickness`,\n   `OuterBorderDarkColor` and `OuterBorderLightColor`. By convention, the\n   dark colour is used for the top and left edge, and the light colour for the bottom\n   and right edge. For the inset effect, swap these colours.\n * the Third area is the inner border. This border has `InnerBorderThickness`,\n   `InnerBorderDarkColor` and `InnerBorderLightColor`. By default, the\n   thickness is zero, so no inner border is shown.\n * Fourth area is the `BorderThickness` (in pixels). This is simply the space\n   between the outer border and the inner border. It can be zero, but it is tough\n   to differentiate between inner and outer borders.\n \n\n## Examples\n\n~~~cs\n// First the title.\n_frame.Title = \"Hello!\";\n_frame.TitleAlignment = StringAlignment.Center;\n_frame.TitleHeight = 16;\n_frame.TitleBackColor = BackColor;\n_frame.TitleForeColor = ForeColor;\n\n// Outer border.\n_frame.OuterBorderDarkColor = Color.FromKnownColor(KnownColor.ControlDark);\n_frame.OuterBorderLightColor = Color.FromKnownColor(KnownColor.ControlLight);\n_frame.OuterBorderThickness = 1;\n\n// Inner border (replace dark and light).\n_frame.InnerBorderDarkColor = Color.FromKnownColor(KnownColor.ControlLight);\n_frame.InnerBorderLightColor = Color.FromKnownColor(KnownColor.ControlDark);\n_frame.InnerBorderThickness = 1;\n\n// Pixels between inner and outer color.\n_frame.BorderThickness = 2;\n~~~\n\nThe code above creates the frame below.\n\n![](Images/frame-2.jpg)\n\n---\n\n# Hierarchy\n\nYou can use the `Hierarchy` control to visualise trees. The control only manages the \nlayout; it expects your code to draw content inside events.\n\n![](Images/hierarchy-1.jpg)\n\n## Usage\n\n\nYou set the tree direction by manipulating the `Direction` property.\nThe control can draw *left to right*, *right to left*, *top to bottom*, and *bottom\nto top* trees. \n\nBasic general node properties (shared between all nodes!) are: `NodeWidth` \nand `NodeHeight`. The minimal space in pixels between two nodes is determined \nby the `NodeHorzSpacing` and `NodeVertSpacing` properties.\n\nYou feed the data into the control by implementing a simple `IHierarchyFeed`\ninterface, and then passing it to the `Hierarchy` via the `SetFeed()` method.\n\nHere is the interface.\n\n~~~cs\npublic interface IHierarchyFeed\n{\n    IEnumerable\u003cstring\u003e Query(string key=null);\n}\n~~~\n\nIt only has one function, which returns a collection of node keys (node identifiers).\n \n \u003e Since your code is responsible for drawing nodes and edges, the control \n \u003e does not need to know more about the node. When it needs to draw the node it passes the \n \u003e node key and rectangle in an event and expect your code to do the rest. \n\nThe `Query()` function accepts a *parent key* parameter. If this parameter is null, \nthe function returns all root node keys (usually just one?); otherwise, it returns the child\nnodes of provided *parent node*.\n\nYou can capture all standard control mouse events, and inside the mouse events translate \nmouse coordinates to nodes key by calling `NodeAt()` function.\n\n## Examples\n\n### File system feed\n\nHere's a simple feed implementation for the file system. \n\n~~~cs\npublic class FileSystemHierarchyFeed : IHierarchyFeed\n{\n    private string _rootDir;\n\n    public FileSystemHierarchyFeed(string rootDir) { _rootDir = rootDir; }\n\n    public IEnumerable\u003cstring\u003e Query(string key = null)\n    {\n        if (key == null) return new string[] { _rootDir };\n        else return Directory.EnumerateDirectories(key + @\"\\\");\n    }\n}\n~~~\n\nThe full path is used as a node key in the example above. If you wanted to draw\norganigram, you'd probably use the database identifier of a person as the key.\n\n  \u003e Disclaimer: Letting the above file feed scan your `c:` drive is bad.\n    Just sayin'. \n\n### Drawing functions\n\nYou can subscribe to two events: the `DrawEdge` event to an edge, i.e. a line \nconnecting two nodes. And the `DrawNode` event to draw a node. Both events will pass you\nthe node key, node rectangle, and an instance of the `Graphics` for drawing.\n\n \u003e But drawing nodes and edges is your job.\n\nThis sample demonstrates drawing inside both events.\n\n\n~~~cs\nprivate void _hierarchy_DrawEdge(object sender, DrawEdgeEventArgs e)\n{\n    // Calculate node centers.\n    Point\n        start = new Point(\n            e.ParentRectangle.Left + e.ParentRectangle.Width / 2,\n            e.ParentRectangle.Top + e.ParentRectangle.Height / 2),\n        end = new Point(\n            e.ChildRectangle.Left + e.ChildRectangle.Width / 2,\n            e.ChildRectangle.Top + e.ChildRectangle.Height / 2);\n    // And draw the line.\n    using (Pen p = new Pen(ForeColor)) \n        e.Graphics.DrawLine(p,start,end);\n}\n\nprivate void _hierarchy_DrawNode(object sender, DrawNodeEventArgs e)\n{\n    // Extract directory name from the path.\n    string dir= Path.GetFileName(Path.GetDirectoryName(e.Key+@\"\\\"));\n\n    // Draw the node.\n    Graphics g = e.Graphics;\n    using (Pen forePen = new Pen(ForeColor))\n    using (Brush backBrush = new SolidBrush(BackColor),\n        foreBrush = new SolidBrush(ForeColor))\n    using(StringFormat sf=new StringFormat() { \n        LineAlignment=StringAlignment.Center, \n        Alignment=StringAlignment.Center})\n    {\n        g.FillRectangle(backBrush, e.Rectangle); // Border.\n        g.DrawRectangle(forePen, e.Rectangle); // Rectangle.\n        g.DrawString(dir, Font, foreBrush, e.Rectangle, sf); // Text.\n    }\n}\n~~~\n\n### Mouse input\n\nYou can subscribe to standard mouse events (clicks, moves, etc.) and use the `NodeAt()` \nfunction to find out which node was clicked. For example, if you'd like to \nhighlight a node on click, subscribe to the `MouseUp` event, find out which node was\nclicked, store its key, and call `Refresh()` to repaint the control.\n\n~~~cs\nprivate string _highlightedNodeKey;\nprivate void _hierarchy_MouseUp(object sender, MouseEventArgs e)\n{\n    _highlightedNodeKey = _hierarchy.NodeAt(e.Location);\n    _hierarchy.Refresh();\n}\n~~~\n\nThen, in the `DrawNode` event, check the node key against the `_highlightedNodeKey` and\npaint it accordingly.\n\n### Styling edges\n\nBecause the `DrawEdge` event gives you both ends of the edge - the parent node and \nYou can choose how to draw your edge with the child node (with their coordinates). \nIt can be a line, a curve, etc. You may also start your edge at the end of the parent node. \n(instead of the node centre) and draw it to the start of the other node. The following code does\njust that; see the image for the result.\n\n~~~cs\nprivate void _hierarchy_DrawEdge(object sender, DrawEdgeEventArgs e)\n{\n    // Change start and end location of an edge.\n    Point\n        start = new Point(\n            e.ParentRectangle.Right,\n            e.ParentRectangle.Top + e.ParentRectangle.Height / 2),\n        end = new Point(\n            e.ChildRectangle.Left,\n            e.ChildRectangle.Top + e.ChildRectangle.Height / 2);\n    using (Pen p = new Pen(ForeColor))\n        e.Graphics.DrawLine(p, start, end);\n}\n~~~\n\n![](Images/hierarchy-2.jpg)\n\n---\n\n# LabelEx\n\nLabelEx is a label control with roration and transparency features.\n\n![](Images/labelex-1.jpg)\n\n## Usage\n\nTransparency is a pain in Windows Forms. This feature works well at runtime but behaves \nnaughty in the designer. The reason is that it avoids double buffering to implement \ntransparency. To make the label background transparent, set the `Opacity` property \nfrom 0% to 100% (for a fully transparent label). \n\nRotate the label by setting the `Angle`. The unit is in degrees, and the rotation is clockwise.\n\nFinally, you can align the rotated label to the bounding rectangle by assigning values to \nproperties `HorzAlignment` and `VertAlignment`.\n\n## Examples\n\n~~~cs\n_label.Text = \"Oh, what a night\\nLate December back in sixty - three\\nWhat a very special time for me\\nAs I remember what a night.\";\n_label.Angle = 45;\n_label.HorzAlignment = StringAlignment.Center;\n_label.VertAlignment = StringAlignment.Center;\n~~~\n\nAnd the result...\n\n![](Images/labelex-2.jpg)\n\n---\n\n# Line\n\nUse a custom line as a separator or a decorator. \n\n![](Images/line-1.jpg)\n\n## Usage\n\nSet the `Orientation` property to `Horizontal` or `Vertical`. Use line \n`Thickness` to set the pen thickness. Set line `Text`,`Font`, and `ForeColor` \nproperties to control the appearance of the title. If empty, no title is shown.\n`TextAlignment` tells where the title is shown (at the beginning, end or in the\nmiddle of the line). If at the beginning or end, then `TextOffset` (in pixels) is\nused to move the title away from the beginning/endpoint. `BackColor` controls line control \nbackground, and `LineColor` is used for line color. `DashValues` is an array\nof floats that tells the size of pixels and spaces. The default value of `{1,0}` means\na solid line (i.e. one pixel, followed by zero spaces). A value of `{1,1}` is\ninterpreted as a pixel followed by a space. The pattern can be of arbitrary\nlength, i.e. a value of `{3,1,1,1}` would be interpreted as three pixels, \nfollowed by one space, followed by one pixel, followed by one space.\n\n## Examples\n\n~~~cs\n_line.Orientation = Orientation.Horizontal;\n_line.Text = string.Empty; // Remove text.\n_line.LineColor = Color.Khaki;\n_line.BackColor = Color.DarkSeaGreen;\n_line.Thickness = 6;\n_line.DashValues = new float[] { 3,1,1,1 };\n~~~\n\n![](Images/line-2.jpg)\n\n---\n\n# Listing\n\nProgram code listing. You can use it for emulators, disassemblers, etc.\n\n![](Images/listing-2.jpg)\n\n## Usage\n\n### Appearance\n\nSet the standard properties for the listing, i.e. `Font`, `BackColor`, and `ForeColor` to whatever you wish. Then set the feeder using the `SetFeed()` method, and the control will draw the code and formatting provided by the feeder. \n\n### Feed\n\nThe Listing workspace is divided into rows, and each row is divided into cells. The height of each row can be configured using the `RowHeight` property and the width of each cell using the `CellWidth` property.\nEach cell can have its background and foreground colour.\nIf not set on the lower level, colours are inherited, i.e. if you do not set colour for the row, the row inherits it from the control. \n\nCell content is obtained from the `IListingFeed`. This interface returns all rows.  \n\n~~~cs\npublic interface IListingFeed\n{\n    ListingRow QueryRow(int rowNo);\n    int RowCount();\n}\n~~~\n\nYou can set the fore and back colours for each row. Each row also contains a collection of `ListingCell` objects.\n\n~~~cs\npublic class ListingRow\n{\n    public ListingRow(string text, Color? foreColor = null, Color? backColor = null) {\n        /* Create columns. */\n        List\u003cListingCell\u003e defaultRow = new List\u003cListingCell\u003e();\n        foreach(char character in text)\n            defaultRow.Add(new ListingCell(character));\n        Row = defaultRow.ToArray();\n        /* Remember fore and back color. */\n        ForeColor = foreColor;\n        BackColor = backColor;\n    }\n\n    public ListingCell[] Row { get; set;  }\n\n    public Color? BackColor { get; set; }\n    public Color? ForeColor { get; set; }\n}\n~~~\n\nThe `ListingCell` contains background and foreground colour for the cell as well as character. If 'ForeColor` or `BackColor` properties are `null`, values are inherited from the row.\n\n~~~cs\npublic class ListingCell\n{\n    public ListingCell(char character, Color? foreColor=null, Color? backColor=null)\n    {\n        Character = character;\n        ForeColor = foreColor;\n        BackColor = backColor;\n    }\n\n    public char Character { get; set; }\n    public Color? BackColor { get; set; }\n    public Color? ForeColor { get; set; }\n}\n~~~\n\n### Feeder Example\n\nThis example of a feeder reads a text file and feeds it to the control. \n\n~~~cs\npublic class TextFileFeed : IListingFeed\n{\n    ListingRow[] _rows;\n    public TextFileFeed(string fname)\n    {\n        List\u003cListingRow\u003e  rows = new List\u003cListingRow\u003e();\n        foreach(string row in File.ReadAllLines(fname))\n            rows.Add(new ListingRow(row));\n        _rows = rows.ToArray();\n    }\n    public ListingRow QueryRow(int rowNo) { return _rows[rowNo]; }\n    public int RowCount() { return _rows.Length; }\n}\n~~~\n\n### Character alignment\n\nYou can align cells' content by setting the `CellAlign` and `CellLineAlign` properties.\n\n### Scrolling\n\nThere is no standard scroll bar. Instead, you must implement scrolling yourself using a property called `TopRow`. This property tells the system which row is the first row on the screen. While scrollbars would be somehow inflexible, the top row approach enables synchronization of position inside multiple windows.\nTo implement a scrollbar, add it to your window, set its' min and max to the number of rows, and handle its `Scroll` event like this.\n\n~~~cs\nprivate void _scroll_Scroll(object sender, ScrollEventArgs e)\n{\n    _listing.TopRow = e.NewValue;\n}\n~~~\n\n![](Images/listing-1.jpg)\n\n---\n\n# Monitors\n\nWith the Monitors control, you can show the user their multi-monitor configuration\nand enable them to select one. This feature is helpful in creating multi-monitor \napplications. You can let the user configure target monitors for\nthese windows.\n\n \u003e The control will automatically detect the size and placement of connected monitors.\n\n![](Images/monitors-1.jpg)\n\n## Usage\n\n### Margin and padding\n\nPlace the control on your window. All your monitors will be selected and drawn\nin default colours. In addition, you can configure the `Padding` property to create space between\nmonitors. You can also set the `Margin` property for this control.\n\n### Monitor number\n\nIf you set the `ShowNumber` property - you will display numbers inside\nmonitors. You can toggle font for displaying numbers by setting the `Font` \nproperty.\n\nNumbers are displayed as outline text. The border colour for each number is configured\nby setting `MonitorTextForeColor`, and the inner colour of the text is configured \nvia the `MonitorTextBackColor`.\n\n### Monitor edge\n\nEach monitor can be a square, or it can have a 3D border *as real-life monitors\ndo*. You must set the `ShowEdge` property if you'd like a border. You can\nconfigure 3D colours by setting the `EdgeLightColor` and `EdgeDarkColor`.\nThe space between the outer border and the inner border is configured by setting\nthe `EdgeThickness` value.\n\n### Active monitor\n\nWhen moving (hovering), the mouse over the monitors will be highlighted. To set the \ncolours of monitor under the mouse, use the `ActiveMonitorBackColor` and\n`ActiveMonitorBackColor` properties.\n\nYou can manually activate a monitor (without a mouse over it) by setting the \n`Activate` property to monitor number.\n\n### Select monitor\n\nWhen clicking on a monitor, you select it, and an event called `MonitorSelected` \nis raised.\n\n \u003e You can also capture the `MonitorUnselected` event to detect when a monitor is\n \u003e unselected.\n\nYou can set the selected monitor manually via the `Selected` numeric property. \n\nProperties` SelectedMonitorBackColor and `SelectedMonitorForeColor` configure the \nvisual effects for selected monitor`\n\n\n### Monitor colour\n\nMonitor only uses one colour, called the `MonitorBackColor`. Please set it to \nwhatever the standard back colour for the monitor should be.\n\n---\n\n\n# Navigator\n\nNavigator is a vertical navigation bar with two levels.\n\n![](Images/navigator-1.jpg)\n\n## Usage\n\n### Appearance\n\nConfigure the navigator items by changing the standard `ForeColor`, `BackColor`,\nand `Font` properties. Additional behaviour properties are\u003e `HoverBackColor`, \n`HoverForeColor`, `SelectedBackColor`, and `SelectedForeColor`.\n\nYou can configure item size and indent by setting the `ItemHeight` and\n`ItemIndent` properties. Item layout also respects the `Padding` property.\n\nAdd the title by setting the `Text` property. Customize it by changing\n`TitleHeight`, `TitleFont`, `TitleSeparatorColor` and the `Title` colour.\nThe title always uses the background colour of the control.\n\n![](Images/navigator-2.jpg)\n\n### Navigation\n\nThe navigator supports two levels of navigation and uses a feeder. You can\nset the feeder by calling the `SetRoot()` method and passing it the \n`IEnumerable\u003cNavigatorItemBase\u003e` collection.\n\nYou can pass any derivate class too. The `Navigator` supports the following \nderivates:\n\n~~~cs\npublic class NavigatorItemBase\n{\n    public string Text { get; set; }\n    public Action Callback { get; set; }\n    public string Key { get; set; }\n    public bool Disabled { get; set; }\n}\n\npublic class NavigatorItem : NavigatorItemBase\n{\n    public Image Glyph { get; set; }\n}\n\npublic class NavigatorCategory : NavigatorItemBase\n{\n    public NavigatorCategory()\n    { Children=new List\u003cNavigatorItem\u003e(); }\n    public List\u003cNavigatorItem\u003e Children { get; }\n}\n~~~\n\n## Fluent Feeder\n\nTo make your life easier, we have created a fluent interface\nthat you can use to create quick navigators. First, you need to call\nthe `NavigatorBuilder.Fluent()` function and fluently\nadd item texts, callback functions, glyphs and shortcut keys.\n\n~~~cs\n// Place the control on a window and call it _navigator.\n// Then inside OnLoad Window event do this --\n_navigator.SetRoot(\n    NavigatorBuilder.Fluent()\n        .Add(\"Create State\", ()=\u003eCreateState(), null, \"Alt+C\") // No image\n        .Add(\"Fork State\")\n        .Begin(\"Navigate to...\")\n            .Add(\"More github\")\n        .End()\n        .GetRoot()\n);\n~~~\n\n## ToDo\n\n * Finish keyboard shortcuts, and \n * add flexibility to the title separator.\n\n---\n\n# Prompt\n\nThe `Prompt` control helps you consistently align your controls on the form. It adds\na prompt and a glyph in front of them in a consistent way. \n\n![](Images/prompt-2.jpg)\n\n## Usage\n\nAdd the `Prompt` control to your window. You can now place the content, i.e. the \ncontrol you'd like to host (such as a `TextBox`) on top of the `Prompt` control, \nand dock it. \n\nAs the `Prompt` control derives from the `Panel` it will treat your `TextBox`\nas a child, move and resize it correctly whenever you move the `Prompt` control. \n\nYou can customize the layout and the appearance of your `Prompt` control.\n\n![](Images/prompt-3.jpg)\n\n### Borders and Margins\n\nYou may manage border thickness with four properties: `LeftBorderThickness`, \n`RightBorderThickness`, `TopBorderThickness`, and `BottomBorderThickness`, and \nset the border colour via the `BorderColor` property. \n\n \u003e A value of 0 for border thickness means no border.\n\n### Content\n\nStandard control properties `Font` and `BackColor` are applied to the hosted \ncontrol (i.e. to the control you place on top of the `Prompt` control). \nIn addition, you can set the `ContentMargin` to create an extra margin inside \nthe content space. \n\n \u003e This is useful when you want to separate your control and the prompt\n \u003e text with a few pixels.\n\n### The Prompt\n\nPrompt text is controleld by the `Text`, `PromptTextWidth`, `PromptForeColor`, \n`PromptBackColor`, `PromptTextAlignment`, `PromptTextLineAlignment`,and\n`PromptTextEdge` properties. All properties are straightforward, except\nthe `PromptTextEdge`. This is used in combination with `PromptTextAlignment`.\nIf the latter is `Left`, then the `PromptTextEdge` is the number of pixels from \nthe edge of control (the absolute left) to the prompt text. If the `PromptTextAlignment` property\nis right, it defines the number of pixels from the right prompt edge.\n\n \u003e You can also set `PromptTooltip`, which will be shown if you hover over the\n \u003e prompt text and is useful for showing field instructions to the user. \n \u003e An empty value will disable the tooltip.\n\n### Glyph\n\nYou can set a prompt glyph. It will be shown right of your prompt text. You may\nuse this feature for validation alerts, etc. \n\n \u003e Recommended glyph size is 16 x 16 pixels.\n\nAs long as the `Glyph` property is null, the space for glyph \n**will not be reserved** within the control. Once you set the `Glyph` property, \nit will not be shown until you set the `GlyphVisible` property to true. \nThis is how you can toggle validation alerts. \n\nThe glyph has its own back colour controlled by the `GlyphBackColor`. It also\nhas custom `GlyphWidth` (default:16), while the height equals `Prompt` control\nheight. Within this rectangle, you can align glyphs by setting the `GlyphAlignment`. \n\nLast but not least, the glyph can also have an independent tooltip. You could use\nthis for explaining a validation error. You can toggle this on by setting the\n`GlyphTooltip` to a non-empty value.\n\n![](Images/prompt-1.jpg)\n\n---\n\n# SecurityMatrix\n\nA highly configurable classic *permissions and roles* grid editor. It accepts \na feed interface which must provide a list of roles, permission categories, \nand permissions. A demo feed implementation is part of the control. \n\n![](Images/security-matrix-2.jpg)\n\nYou can configure fonts and colours of the control or implement custom drawing.\nCustom drawing is implemented by overriding the control's paint functions.\n\n## Usage\n\nPlace the `SecurityMatrix` on your window. It will show a demo matrix enabling you\nto customize its' appearance.\n\n![](Images/security-matrix-1.jpg)\n\nCall the `SetFeed()` function to pass the data to it. Set angle for roles by setting\nthe `RolesAngle` property. Reserve space on top and the left for roles and categories\nby setting the `RolesHeight` and `CategoriesWidth` properties. Set the tick cell \nsize by setting the property `CellSize`. \n\nYou can configure fore and front colours and fonts for matrix titles on the top\nand left. You can also configure cells' fore and front colours. Finally, when the\nmouse hovers over the cell, it will change colour if you set the `PermissionActiveCellBackColor`\nproperty.\n\n## Implementing the feed\n\nTo implement the feed interface, you need to implement four functions.\n\n~~~cs\npublic interface ISecurityMatrixFeed\n{\n    IEnumerable\u003cSecurityRole\u003e QueryRoles();\n    IEnumerable\u003cSecurityCategory\u003e QueryCategories();\n    IEnumerable\u003cSecurityPermission\u003e QueryPermissions(SecurityCategory category);\n    bool this[SecurityRole r, SecurityCategory c, SecurityPermission p] { get; set; } \n}\n~~~\n\nFunctions accept parameters of types SecurityRole, SecurityPermission, and SecurityCategory\nwhich are all derived from `KeyNamePair` and must contain two values: \n * a display name, and \n * a unique identifier.\n\n~~~cs\npublic class KeyNamePair\n{\n    public string Id { get; set; }\n    public string DisplayName { get; set; }\n}\npublic class SecurityRole : KeyNamePair {}\npublic class SecurityCategory : KeyNamePair {}\npublic class SecurityPermission : KeyNamePair {}\n~~~\n\nFunction `QueryRoles()` should return all roles that you'd like to have displayed\non top. Function `QueryCategories()` should return all categories into which you would\nlike to group permissions. Function `QueryPermissions()` takes a category object as a\nparameter and returns all permissions of this category. And finally,\nthe indexer `this[SecurityRole, SecurityCategory, SecurityPermission]`is used to \naccess the ticks. \n\n### Binding ticks to database\n\nThe indexer inside the feed can be connected to the database, updated on set and\nread from it on getting. Following is a naive implementation of the indexer as a list.\n\n~~~cs\nprivate class Check\n{\n    public SecurityRole SecurityRole { get; set; }\n    public SecurityCategory SecurityCategory { get; set; }\n    public SecurityPermission SecurityPermission { get; set; }\n    public bool Value { get; set; }\n}\n// ...code omitted...\nprivate List\u003cCheck\u003e _checks;\n// ...code omitted...\npublic bool this[SecurityRole r, SecurityCategory c, SecurityPermission p] { \n    get {\n        var chk = _checks.FirstOrDefault(chk =\u003e\n            chk.SecurityRole.Id.Equals(r.Id)\n            \u0026\u0026 chk.SecurityCategory.Id.Equals(c.Id)\n            \u0026\u0026 chk.SecurityPermission.Id.Equals(p.Id));\n        if (chk == null) return false;\n        else return chk.Value;\n    }\n    set\n    {\n        var chk = _checks.FirstOrDefault(chk =\u003e\n            chk.SecurityRole.Id.Equals(r.Id)\n            \u0026\u0026 chk.SecurityCategory.Id.Equals(c.Id)\n            \u0026\u0026 chk.SecurityPermission.Id.Equals(p.Id));\n        if (chk != null)\n            chk.Value = value;\n        else\n            _checks.Add(new Check() { \n                SecurityRole=r, SecurityCategory=c, SecurityPermission=p, Value=value \n            });\n    }\n} \n~~~\n\n### Custom paint\n\nYou can derive your control from the `SecurityMatrix` and implement custom\npaint handlers for every aspect of the grid.\n\n~~~cs\npublic class SecurityMatrixEx : SecurityMatrix\n{\n    protected override void DrawTick(Graphics g, Rectangle rect)\n    {\n        rect.Inflate(-12, -12);\n        using (Pen tickPen = new Pen(Color.Black, 2))\n        {\n            g.DrawLine(tickPen, rect.Location, new Point(rect.Right, rect.Bottom));\n            g.DrawLine(tickPen, new Point(rect.Right, rect.Top), new Point(rect.Left, rect.Bottom));\n        }\n    }\n\n    protected override void DrawPermissionCellBackground(Graphics g, Rectangle rect, SecurityRole r, SecurityCategory c, SecurityPermission p)\n    {\n        rect.Inflate(-8, -8);\n        g.DrawRectangle(Pens.Gray, rect);\n    }\n\n    protected override void DrawPermissionCellForeground(Graphics g, Rectangle rect, SecurityRole r, SecurityCategory c, SecurityPermission p)\n    { // Don't draw rectangle around it!\n    }\n}\n~~~\n\nThis code above changes ticks to squares, and produces the following output:\n\n![](Images/security-matrix-3.jpg)\n\nYou can override the following paint functions.\n\n~~~\nvoid DrawRoleBackground(Graphics g, Point[] pts, SecurityRole role)\nvoid DrawRoleForeground(Graphics g, Rectangle r, SecurityRole role)\nvoid DrawPermissionBackground(\n            Graphics g,\n            Rectangle r,\n            SecurityCategory category,\n            SecurityPermission permission)\nvoid DrawPermissionForeground(\n            Graphics g,\n            Rectangle r,\n            SecurityCategory category,\n            SecurityPermission permission)\nvoid DrawTick(Graphics g, Rectangle rect)\nvoid DrawPermissionCellBackground(\n            Graphics g,\n            Rectangle rect,\n            SecurityRole r,\n            SecurityCategory c,\n            SecurityPermission p)\nvoid DrawPermissionCellForeground(\n            Graphics g,\n            Rectangle rect,\n            SecurityRole r,\n            SecurityCategory c,\n            SecurityPermission p)\nvoid DrawCategoryForeground(Graphics g, Rectangle r, SecurityCategory category)\nvoid DrawCategoryBackground(Graphics g, Rectangle r, SecurityCategory c)\nvoid DrawCategoryCellBackground(Graphics g, Rectangle rect, SecurityRole r, SecurityCategory c)\nvoid DrawCategoryCellForeground(Graphics g, Rectangle rect, SecurityRole r, SecurityCategory c)\n~~~\n\nThe control implements all the sizing and rotation operations, so you don't have\nto worry about it, i.e. you don't need to draw rotated text for the role, and it is rotated and placed to\nthe correct rectangle for you by the control.\n\n## ToDo\n\nKeyboard and focus handling.\n\n---\n\n# SpriteGrid\n\nUse sprite grid control to build a sprite editor.\n\n![](Images/spritegrid-1.jpg)\n\n## Usage\n\nPlace the `SpriteGrid` control on your window. Set its `SourceImage` property to\nthe `Bitmap` you'd like to view or edit.\n\nYou can show or hide rulers using the `ShowHorzRuler` and `ShowVertRuler` properties.\nYou can customize rulers by manipulating properties: `RulerHeight`, `RulerWidth`, \n`RulesBackgroundColor`, `MinorTickSize`, `MajorTickSize`, and 'MinorTicksPerMajorTick'.\n\nYou can customize grid appearance by manipulating properties `GridEdgeLineColor`,`GridEdgeLineDashPattern`,\n`GridTickLineColor`, `GridTickLineDashPattern`.\n\n`BackColor` is used to draw an empty grid, and `ForeColor` is used for all text (currently just\nthe ruler content).\n\n## Examples\n\n### Passing image\n\nYou can pass the image to SpriteGrid by assigning the image to the `SourceImage` property.\n\n~~~cs\n_spriteGrid.SourceImage = Image.FromFile(\"pacman.png\");\n~~~\n\n### Responding to events\n\nSpriteGrid exposes basic mouse events and translates physical coordinates to logical\ncoordinates inside the sprite (i.e. row and column). To manipulate the sprite, \nsimply manipulate the underlying image and call the `Refresh()` function on\nSpriteGrid.\n\n~~~cs\npublic partial class MainWnd : Form\n{\n    // ... code omitted ...\n    private Bitmap _sprite;\n\n    private void MainWnd_Load(object sender, System.EventArgs e)\n    {\n        // Create new 16x16 sprite.\n        _spriteGrid.SourceImage = _sprite = new Bitmap(16, 16);\n    }\n\n    private void _spriteGrid_CellMouseDown(object sender, CellMouseButtonArgs e)\n    {\n        // Set image pixel.\n        _sprite.SetPixel(e.Column, e.Row, Color.Black);\n        _spriteGrid.Refresh();\n    }\n    // ... code omitted ...\n}\n~~~\n\n### Implementing zoom\n\nSpriteGrid uses a mouse wheel for two purposes. Sprite is scrolled up and down \nif the wheel is used without any control key. If, while using the mouse wheel, \nyou hold down the Ctrl key, then the `ZoomIn` and `ZoomOut` events are triggered. \n\nYou can use this to implement zoom.\n\nThe simplest implementation (which would not consider the current mouse position),\nwould be increasing and decreasing values or properties `CellWidth` and `CellHeight`.\n\n~~~cs\nprivate void _spriteGrid_ZoomIn(object sender, ZoomInArgs e)\n{\n    if (_spriteGrid.CellWidth \u003c 32) _spriteGrid.CellWidth++;\n    if (_spriteGrid.CellHeight \u003c 32) _spriteGrid.CellHeight++;\n}\n\nprivate void _spriteGrid_ZoomOut(object sender, ZoomOutArgs e)\n{\n    if (_spriteGrid.CellWidth \u003e 1) _spriteGrid.CellWidth--;\n    if (_spriteGrid.CellHeight \u003e 1) _spriteGrid.CellHeight--;\n}\n~~~\n\nAnd here is the result.\n\n![](Images/spritegrid-2.gif)\n\n### Visible margins\n\nSometimes it makes sense to set a visible sprite margin (for example, to show where\nthe image will be cropped). Six properties controlling sprite margin: `LeftMargin`,\n`RightMargin`, `TopMargin`, `BottomMargin`, `MarginLineThickness`, and\n`MarginColor`.\n\n \u003e Sprite margin is not the same as the control margin. Sprite margin is an area of the sprite\n \u003e that is visibly marked.\n\n~~~cs\n_spriteGrid.SourceImage = Image.FromFile(\"art.png\");\n_spriteGrid.LeftMargin = 40;\n_spriteGrid.RightMargin = 40;\n_spriteGrid.TopMargin = 30;\n_spriteGrid.BottomMargin = 40;\n_spriteGrid.MarginLineThickness = 4;\n_spriteGrid.MarginColor = Color.Red;\n~~~\n\n![](Images/spritegrid-3.jpg)\n\n### Selections\n\nBy responding to mouse events `CellMouseDown`, `CellMouseUp, `CellMouseMove` you \ncan detect select operation.\n\n \u003e To differentiate between a select and a pixel click, you need to compare the mouse\n \u003e coordinates at `CellMouseDown` event with that of `CellMouseUp`. If \n \u003e coordinates are the same, and there were no `CellMouseMove` events out o the\n \u003e cell, then it is a click. Otherwise, it is selected.\n\nTo make SpriteEdit draw a selection, you set the property GridSelection. \nGridSelection is a polygon and can be of any shape.\n\n~~~cs\n_spriteGrid.SourceImage = Image.FromFile(\"jetpac.png\");\n_spriteGrid.SetGridSelection(new GridSelection()\n{\n    LineColor = Color.White,\n    LineWidth = 3,\n    Poly = new Point[] { \n        new Point(200,200), \n        new Point(400, 200), \n        new Point(400,400), \n        new Point(200,400)}\n});\n~~~\n\n![](Images/spritegrid-4.jpg)\n\n\n[language.url]:   https://docs.microsoft.com/en-us/dotnet/standard/net-standard\n[language.badge]: https://img.shields.io/badge/language-csharp-blue.svg\n\n[uilib.url]:   https://docs.microsoft.com/en-us/dotnet/desktop/winforms/?view=netdesktop-5.0\n[uilib.badge]: https://img.shields.io/badge/ui%20library-winforms-blue.svg\n\n[standard.url]:   https://dotnet.microsoft.com/download/dotnet/3.1\n[standard.badge]: https://img.shields.io/badge/dotnet-core%203.1-blue.svg\n\n[license.url]:    https://github.com/tstih/more/blob/master/LICENSE\n[license.badge]:  https://img.shields.io/badge/license-MIT-blue.svg\n\n[status.badge]:  https://img.shields.io/badge/status-stable-dkgreen.svg\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftstih%2Fmore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftstih%2Fmore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftstih%2Fmore/lists"}