WinForms.Net
Scrappy notes because I'm primarily a web developer
- Managed code access to later Windows apis (file dialogs etc):
- Krypton controls- free winforms controls
- Icons:
- FamFamFam icons - nice, free (used in the FF WebDeveloper toolbar, flags used in Wikipedia)
- PInvoke - free with attribution
- My SortableBindingList<T> (for Windows.Forms)
Events
Note the order: Enter - GotFocus - TextChanged - Leave - Validating - Validated - LostFocus
TextBox can be ReadOnly (gets focus), !Enabled (no focus).
Validation levels:
- TextBox properties- simple properties like MaxLength, CharacterCasing, AllowTabs
- Key Events- KeyPress (and for ctl/alt KeyDown/Up) in combo with KeyChar:
if (Char.IsDigit(e.KeyChar) == true) e.Handled=false
form1.KeyPreview= true for form-level keys - Validating Event- CausesValidation of next control must be true (false for help providers)
- Form level- on a button_Click
- MessageBox.Show or myErrorProvider.SetError(textBox1, "message") (use string.Empty to clear the errorProvider)
Exception Handling
You should at least log all unhandled exceptions, and preferably show a MessageBox saying sorry before Application.Exit().
In Asp.Net it's simple to trap unhandled exceptions in Application_Error in global.asax or in CustomErrors in web.config.
In Winforms and console apps, you can use the AppDomain.UnhandledException event:
AppDomain.CurrentDomain.UnhandledException +=
(o, eventArgs) =>
{
Console.WriteLine("Application errrored!");
Log.LogException(eventArgs.ExceptionObject as Exception);
Environment.Exit(0);
};
In Winforms you also have a Application.ThreadException (check the UnhandledExceptionMode as by default you'll swallow the exception and continue). It only traps exceptions on the main UI thread, so you probably need the AppDomain event too.
You can use Application.Exit() or Environment.Exit(0) to close (and stop the default .Net unhandled exception dialog).
GDI+
Is also used with the PrintPage event of the PrintDocument component
Graphics object is available from a form1.CreateGraphics factory method (anything Control-derived), from PaintEventArgs(or PrintPageEventArgs).Graphics, or the static Graphics.FromImage(img)
Draw* for lines (with a Pen), Fill* for filled shapes (with a Brush).
Pen myPen = new Pen(myBrush)
Coordinates: Point, Size, Rectangle (a Size with a Point as origin).
g.DrawString(str, new Font("Arial", 36, FontStyle.Regular), myPen, 0, 0);
Dispose all Pens/Brushes/Graphics objects (enclose them in using)
GraphicsPaths- is for complex shapes- use AddString etc, StartFigure/CloseFigure
- Resize- In form_Resize, call form1.Invalidate()
- Resize- form.ResizeRedraw= true
Making thumbnail (best quality)
private static void ResizeImage(string origPath, string smallPath)
{
using (var orig = Image.FromFile(origPath))
{
const int newWidth = 150;
const int newHeight = 200;
var small = new Bitmap(newWidth, newHeight);
using (var g = Graphics.FromImage(small))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.FillRectangle(Brushes.White, 0, 0, newWidth, newHeight);
g.DrawImage(orig, 0, 0, newWidth, newHeight);
}
small.Save(smallPath, ImageFormat.Jpeg);
}
}
Compress a jpeg
private static void CompressImage(string origPath, string compressedPath)
{
using (var orig = Image.FromFile(origPath))
{
//look up jpeg mime type
ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders().First(
e => e.FormatID == ImageFormat.Jpeg.Guid);
var encoderPars = new EncoderParameters();
//quality must be a LONG from 1 to 100 - not an INT
encoderPars.Param[0] =
new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 60L);
orig.Save(compressedPath, codec, encoderPars);
}
}
Overlaying images
private static void OverlayImage(string origPath, string overlayPath, string newPath)
{
using (Image background = Image.FromFile(origPath))
{
using (var g = Graphics.FromImage(background))
{
using (var overlay = new Bitmap(overlayPath))
{
g.DrawImage(overlay, new Point(780, 0));
}
}
background.Save(newPath, ImageFormat.Jpeg);
}
}
DPI problems: if the image isn't default 96dpi, bitmap.SetResolution(overlay.HorizontalResolution, overlay.VerticalResolution);
Authoring Controls
- Inherit from existing control.
You can override OnPaint (not for TextBox)- assign Region for round buttons etc.
VS: add reference + Project-Add Inherited Control - Composite (user) controls inherit from UserControl. Use tb.Modifiers = public
VS: Project -Add User Control - Custom controls inherit from Control
Override OnPaint (PaintEventArgs.Graphics
In #ctor,SetStyle(ControlStyles.ResizeRedraw, true)
so resizing redraws (or manually Refresh())
or in _SizeChanged callInvalidate()
VS: New Project =Windows Control Library
Add toolbox icons- [ToolboxBitmap(typeof(Button))]. Or pass the path, or simply put add YourClassName.bmp as an embedded resource (bmp filename== classname) and it does it magically.
You can also use LicenseManager.Validate.
Help
- Form and controls have a HelpRequested event (when F1 pressed)
- You can show "?" button in the titlebar ("What's This"): form1.HelpButton= true AND false for Maximise/Minimise.
In 2.0, forms have a HelpButtonClicked event for this. - There's a static Help class (with ShowHelp(control, "file://c:\myhelp.chm") and ShowHelpIndex()) and a HelpProvider.
- A HelpProvider has a HelpNamespace (file or URL). You can just SetHelpString(ctl,str) for tooltip-like ("pop-up") help with no namespace (the What's This titlebar button calls this) or use SetHelpKeyword (ie "topic"= topic.htm).
- There's also an AccessibleName, for accessibility tools (and Tooltips!)
Settings
WinForms 2.0 designer - properties - Application Settings/ PropertyBindings creates the Settings class and binds for you. (msdn)
For C# (not VB) you need:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Properties.Settings.Default.Save();
}
Tooltips on TreeView treenodes
Option 1: treeview1.ShowNodeToolTips = true and set the treeNode's ToolTipText
Option 2: Add a Windows.Forms.ToolTip, and handle the treeview's MouseMove event. You can use the treeNode's ToolTipText directly, or cast the Tag. You need to remember the last node or tooltip to avoid unpleasant repeat flashing as the cursor moves.
private string lastNode; //remember tooltips to avoid flashing
private void DatabaseScripterTreeView_MouseMove(object sender, MouseEventArgs e)
{
// Get the node at the current mouse pointer location.
TreeNode theNode = GetNodeAt(e.X, e.Y);
// if mouse not over a node, or it has no tag, no tooltip
if (theNode == null || string.IsNullOrEmpty(theNode.ToolTipText))
{
toolTip1.Active = false;
lastNode = null;
return;
}
//check still on same node - otherwise fires repeatedly
if (theNode.ToolTipText == lastNode) return;
lastNode = theNode.ToolTipText;
//show the tooltip
var tag = theNode.ToolTipText;
if (!toolTip1.Active) toolTip1.Active = true;
toolTip1.SetToolTip(this, tag);
}
CrossThread Pattern
For the dreaded cross-thread exception. You can either define a delegate or use an Action/ Func (or new MethodInvoker- anonymous delegates don't work).
void Model_Changed(object sender, EventArgs e)
{
//model changed by background worker or UI
UpdateUI();
}
/// <summary>
/// Updates the UI in a threadsafe way
/// </summary>
private void UpdateUI()
{
if (InvokeRequired)
{
//call myself on the windows thread
Invoke(new Action(UpdateUI));
}
else
{
txtName.Text = "Hello";
}
}