Creating custom controls with C#
Lesson 1 - GDI+
- GCI+ = .net managed implementation of GDI
System.Drawing Namespace
- Summary
- System.Drawing - most of classes actually involved with rendering to screen
- System.Drawing.Design - extend design time UI
- System.Drawing.2D - advanced visual effects
- System.Drawing.Imaging - manipulation of image files
- System.Drawing.Printing - print support
- System.Drawing.Text - font manipulation
System.Drawing.Graphics Object
- Principle object in rendering graphics
- Represents drawing surface of visual element, e.g. form, control, Image, etc.
- Can not directly instantiate - call CreateGraphics method on class deriving from Control
- When working with Image can obtain from static Graphics.FromImage method
System.Drawing.Graphics myFormGraphics;
myFormGraphics = myForm.CreateGraphics();
Bitmap myImage = new Bitmap("C:\\myImage.bmp");
System.Drawing.Graphics myBmpGraphics;
myBmpGraphics = Graphics.FromImage(myImage);
Coordinates
-
Rendering occurs in region set by control bounds
-
Origin = upper left hand corner
-
Measured in screen pixels
-
Variety of structures describe location in region
- Point - single point with int values for X and Y
- PointF - single point with float values for X and Y
- Size - rectangular size consisting of paired height and width values as integers
- SizeF - rectangular size consisting of paired height and width values as float
- Rectangle - top, bottom, left and right edges specified by int values
- RectangleF - top, bottom, left and right edges specified by float values
-
Integral locations can be implicitly converted to floating-point counterparts
-
To convert floating-point to integer locations must explicitly convert each point
-
Note size structure indicate size, but not position
-
Create rectangle by supplying Size and Point (upper left hand corner)
Point myOrigin = new Point(10,10);
Size mySize = new Size(20,20);
Rectangle myRectangle = new Rectangle(myOrigin, mySize);
Drawing Shapes
- Graphics object contains many methods to render shapes on screen
- Those beginning Draw, e.g. DrawArc, DrawPie, draw line structures, outlines, etc.
- Those beginning Fill, e.g. FillRegion, FillRectangle, render solid shapes
- Methods take params specifying coordinates and location of shape to draw. Also require object to perform rendering - a Pen for Draw... and Brush for Fill...
Colors, Brushes, Pens
-
System.Drawing.Color represent single color. Specified by 4 values - alpha which specifies transparency and Red, Green and blue values. Each value in range 0 to 255
-
Can also specify named colours, e.g. Color.Tomato
-
All brushes derive from abstract Brush class
- System.Drawing.SolidBrush - single solid colour
- System.Drawing.TextureBrush - fills object with an image
- System.Drawing.Drawing2D.HatchBrush - fills with hatch pattern
- System.Drawing.Drawing2D.LinearGradientBrush - blends 2 colours along a gradient
- System.Drawing.Drawing2D.PathGradientBrush - render complex gradient
-
Only one Pen class and it cannot be inherited. When create specify colour and width
-
Use SystemColors, SystemPens and SystemBrushes class to ensure UI has same look and feel as rest of system, e.g. Highlight text colour available from System.Colors.HighlightText
Rendering
- Always Dispose of graphics objects when finished - use a lot of resources, failure to do so -> application degredation
SolidBrush myBrush = new SolidBrush(Color.MintCream);
Graphics g = this.CreateGraphics();
Rectangle myRectangle = new Rectangle(0, 0, 30, 20);
g.FillElipse(myBrush, myRectangle);
g.Dispose();
myBrush.Dispose();
-
To render text
- Create Font and Brush objects
- Obtain reference to Graphics object
- Call Graphics.DrawString specifying string, font, brush and location
- Dispose of objects
-
Complex Shapes
- Obtain reference to Graphics object
- Create instance of GraphicsPath class
- Add figures to GraphicsPath
- Call Graphics.DrawPath to draw path outline or Graphics.FillPath to fill shape
- Dispaose of Graphics object
-
The GraphicsPath object describes any complex closed shape or set of shapes
GraphicsPath myPath = new GraphicsPath(new Point[] {new Point(1,1), new Point(32,54), new Point(33,5)}, new byte[] {(byte)PathPointType.Start, (byte)PathPointType.Line, (byte)PathPoint.Bezier});
-
Add figures to path using methods such as
- GraphicsPath.AddClosedCurve
- GraphicsPath.AddElipse
- GraphicsPath.AddPie
- GraphicsPath.AddString
- ...
-
Can create figures by adding lines, arcs, curves, etc.
-
Begin by calling GraphicsPath.StartFigure
-
Add lines
- AddArc
- AddCurve
- AddLine
-
Optionally call GraphicsPath.CloseFigure (if not called figure is closed at run time by drawing line from first point to last)
-
Lesson 2 - Authoring Controls
Inheritance
-
All controls inherit (directly or indirectly) from Control class
- Provides low level logic for UI and common control methods
- No code to render control
- No code to provide unique functionality
-
Inherit from existing control = easiest way create new control
- Inherits functionality and appearance
- Can inherit from most Windows Forms controls (other than those marked sealed)
- Use if retaining look and functionality of existing control but with some custom additions
- Use if wish to retain functionality of existing control but a new look
- Do not use if need radically different functionality
-
Inherit from UserControl
-
Single control may not provide all functionality required, e.g. want control bound to data source that shows first name, last name and phone number in separate TextBox
-
Rather than implement logic in form can group multiple Windows Form controls into single unit (User Control)
-
Sometimes called Composite Control
-
UserControl provides base functionality
- UserControl designer allows other Windows Form controls to be added and custom functionality implemented
- Limited customisation of UI - just configuring ands positioning constituent controls
-
Use when need to combine functionality of multiple existing controls and additional logic as single unit
-
-
Inherit from Control
- Create custom control if rich UI or functionality unachievable via other inheritance mencahisms
- Must program logic specific to control and code required to render visual representation
-
Additional members
- Add to control in same way as add to class (at any access level)
- Form hosting control has access to public members
- Public properties are automatically displayed in Properties window (unless Browsable attribute is false)
[System.ComponentModel.Browsable(false)]
public int StockNumber
{
}
Create Inherited Control
- Can either add new functionality or override members exposed by base class
public class NumberBox : System.Windows.Forms.TextBox
{
protected override void OnKeyPress(KeyPressEventArgs e)
{
if(char.IsNumber(e.KeyChar) == false)
e.Handled = true;
}
}
- Override OnPaint method to modify control appearance, e.g to create button rendered as string of text
public class Wowbutton : System.Windows.Forms.Button
{
protected override void OnPaint(PaintEventArgs pe)
{
System.Drawing.Drawing2D.GraphicsPath myPath = new System.Drawing.Drawing2D.GraphicsPath();
myPath.AddString("Wow!", Font.Family, (int)Font.Style, 72, new PointF(0,0), StringFormat.GenericDefault);
Region myRegion = new Region(myPath);
this.Region = myRegion;
}
}
- Note some controls, e.g. TextBox, drawn by form they exist on and not by the control itself, thus Paint() never called.
Creating UserControl
-
Create class derived from UserControl
-
Add Windows Form controls via UserControl designer
-
Expose any appropriate properties from constituent controls
-
Write custom functionality for user control
-
Constituent controls are treated as private
-
If want to allow other developers to change properties of constituent controls must selectively expose them through properties of the user control
public color ButtonColor
{
get
{
return Button1.BackColor;
}
set
{
Button1.BackColor = value;
}
}
- Can expose entire constituent control by changing Modifiers property to desired access level. Property only available in Properties window - does not exist at runtime.
Creating Custom Control
- Most customisable and configurable, but most time consuming to develop
- Must write all code to render visual representation - most time-intensive part of control development
- Default handler for Paint event is OnPaint() method
- Coordinates measured relative to upper left corner of control
protected override void OnPaint(PaintEventArgs e)
{
Brush aBrush = new SolidBrush(Color.Red);
Rectangle clientRectangle = new Rectangle(new Point(0,0), this.Size);
e.Graphics.FillEllipse(aBrush, clientRectangle);
}
- When control resized ClipRectangle resized but control not necessarily redrawn. Can force redraw by using Control.SetStyle to set ResizeRedraw flag to true
- Can manually cause redraw by calling Refesh method
Lesson 3 - Using Controls
Adding to Toolbox
- Right click toolbox, choose Customize
- Choose .NET Framework Components Tab and click Browse
- Select DLL or EXE containing control
- Control is added to toolbox
Providing Toolbox Bitmap
- VisualStudio provide default icon bitmap for custom controls in toolbox
- Specify custom bitmap viaToolboxBitmapAttribute class
- Specify 16 by 16 pixel bitmap to use or
[ToolboxBitmap(@"C:\Pasta.bmp")
public class PastaMaker : Control
{
}
Specify a type - custom control will have same Toolbox bitmap as that of specified type
[ToolboxBitmpa(typeof(Button))]
public class myButton : Button
{
}
Debugging Control
- Controls are not stand-alone projects - must be hosted within Windows Form project while debugging
- If control part of executable project - add new Windows Form to project to hold control
- If control part of non-executable project (e.g. class library) - add additional project to solution to test control
Licensing
-
.NET provides built-in license management
-
Default licensing model requires any control to be licensed to have LicenseProviderAttribute applied to it
- Specifies LicenseProvider to use for validating license
- LicenseProvider = abstract class providing standard interface for validation
- In control constructor call LicenseManager.Validate to return reference to valid license - if control not licensed it will fail to load
-
LicenseManager.Validate validates license by calling LicenseProvider.GetLicense - retrieves license and checks validity by calling LicenseProvider.IsKeyvalid
-
Validation scheme depends on LicenseProvider implementation
- .NET Framework includes implementation of LicesneProvider called LicFileLicenseProvider
- GetLicense method of LicFileLicenseProvider searches for text file named
<FullName>.LIC
(where FullName = fully qualified control name) - Can override to provide own validation logic
- Implement dispose for every licensed control
[LicenseProvider(typeof(LicFileLicenseProvider))]
public class Widget : System.Windows.Forms.Control
{
private License myLicense;
public Widget()
{
myLicense = LicenseManager.Validate(typeof(Widget), this);
}
protected override void Dispose(bool Disposing)
{
if(myLicense != null)
{
myLicense.Dispose();
myLicense = null;
}
}
}
Hosting in IE
-
Every Windows Forms control can be hosted within IE
-
To host in IE, control must be installed in
- Global Assembly cache
- same virtual directory as HTML page it is declared in
-
Add to HTML page via
<OBJECT>
tag which specifies controls classid -
Classid = 2 parts
- Path to file containing control
- Fully qualified name of control
<OBJECT id="myControl" classid="http:ControlLibrary.dll#ControlLibrary1.myControl" VIEWASTEXT>
</OBJECT>