You are on page 1of 564

Windows Forms Programming in C# By Chris Sells Publisher: Addison Wesley Pub Date: August 27, 200 !

SB": 0# 2$#$$%20#& Pages: 7 % Slots: $'0 Table of Contents

Co(yright )i*rosoft '"+T De,elo(-ent Series .igures Tables .ore/ord Prefa*e Who Should 0ead This Boo12 Con,entions Conta*t A*1no/ledg-ents Cha(ter $' 3ello, Windo/s .or-s Win.or-s fro- S*rat*h Windo/s .or-s in 4isual Studio '"+T Arranging Controls Controls A((li*ation Settings 0esour*es Dialogs Dra/ing and Printing Data Binding )ultithreaded 5ser !nterfa*es De(loy-ent )o,ing fro- ).C Where Are We2 Cha(ter 2' .or-s Sho/ing .or-s .or- 6ifeti-e .or- Si7e and 6o*ation .or- Adorn-ents .or- Trans(aren*y .or- )enus Child Controls 6ayout

)ulti(le Do*u-ent !nterfa*e 4isual !nheritan*e Where Are We2 Cha(ter ' Dialogs Standard Dialogs Styles Data +8*hange Data 4alidation !-(le-enting 3el( Where Are We2 Cha(ter 9' Dra/ing Basi*s Dra/ing to the S*reen Colors Brushes Pens Sha(es Paths !-ages Where Are We2 Cha(ter :' Dra/ing Te8t .onts Strings Where Are We2 Cha(ter %' Ad,an*ed Dra/ing Page 5nits Transfor-s 0egions ;(ti-i7ed Dra/ing Where Are We2 Cha(ter 7' Printing Print Do*u-ents Print Controllers Basi* Print +,ents )argins Page Settings Printer Settings Where Are We2 Cha(ter &' Controls Standard Controls Custo- Controls 5ser Controls

Drag and Dro( Where Are We2 Cha(ter <' Design#Ti-e !ntegration Co-(onents Design#Ti-e !ntegration Basi*s +8tender Pro(erty Pro,iders Ty(e Con,erters 5! Ty(e +ditors Custo- Designers Where Are We2 Cha(ter $0' 0esour*es 0esour*e Basi*s 0esour*e 6o*ali7ation Where Are We2 Cha(ter $$' A((li*ations and Settings A((li*ations +n,iron-ent Settings Where Are We2 Cha(ter $2' Data Sets and Designer Su((ort Data Sets Designer Su((ort Ty(ed Data Sets Where Are We2 Cha(ter $ ' Data Binding and Data =rids Data Binding Data =rids Custo- Data Sour*es Where Are We2 Cha(ter $9' )ultithreaded 5ser !nterfa*es 6ong#0unning ;(erations Asyn*hronous Web Ser,i*es Where Are We2 Cha(ter $:' Web De(loy-ent 3osting Controls in !nternet +8(lorer Code A**ess Se*urity "o#Tou*h De(loy-ent Partially Trusted Asse-bly Considerations !n*reasing Per-issions Authenti*ode

Where Are We2 A((endi8 A' )o,ing fro- ).C A .e/ Words About ).C ).C 4ersus Win.or-s =enghis A((endi8 B' Delegates and +,ents Delegates +,ents 3a((iness in the 5ni,erse A((endi8 C' Seriali7ation Basi*s Strea-s .or-atters !Seriali7able Data 4ersioning A((endi8 D' Standard Win.or-s Co-(onents and Controls Co-(onents and Controls Defined Standard Co-(onents Standard Controls Bibliogra(hy To(

Foreword
Windows application development has changed substantially since Windows 1.0 was introduced in 1983. Both the way Windows programmers write software and the architecture of the software they write have changed dramatically. The most recent step in this continuous evolution involves the icrosoft .!"T #ramewor$. This new platform influences both the developer%s tools and their very definition of &application.& The .!"T #ramewor$' its compliant languages' and the tools that support them' let developers trade a bit of performance and some control for improvements in developer productivity' code safety' and overall robustness of the completed application. #or many developers' this tradeoff is e(tremely e(citing' as they%ll be able to get their wor$ done faster without compromising )uality. *n fact' some developers find that the )uality of their code increases substantially when using languages li$e +, or -B.!"T because the languages themselves are inherently an improvement over older offerings. *n the wa$e of the release of these new managed toolsets' a common misconception in the software development community is that applications written with the .!"T #ramewor$ are designed only to be written to be &Web apps&.applications which really live on a central Web server' but show their user interface through a Web browser li$e *nternet "(plorer. Web/based applications are very appealing for some solutions. Because some Web browser is probably installed on every machine in the world' no distribution of the Web application is necessary. 0s long as users $now how to reach the application%s server and interact with it' they can successfully use it. 1pdates to the application are done on the server where the application is actually running and don%t need to involve updating every client which has a local copy of the software. !o local copy e(ists2 But not all applications written with the .!"T #ramewor$ must be Web applications. The .!"T #ramewor$ provides a set of classes 3$nown collectively as &Windows #orms&4 that are designed to implement &smart client& applications. 5mart clients are applications that provide a local user interface directly using the Windows user interface features' rather than using 6T 7 as a presentation layer. *n fact' it%s very common for developers to use Windows #orms to write stand/alone client applications which provide very rich user interfaces' wor$ offline' and still let developers reap the benefits of the .!"T #ramewor$. 5mart client applications have several benefits over Web/oriented applications. Being able to get something done while offline is a very important feature' and will remain so until high/bandwidth connections are available everywhere people want to get wor$ done' from airplanes to living rooms. Because their code e(ecutes locally' no round/trip must go over the networ$ to the server before the smart client application can respond to the user. That missing round/trip renders smart client applications impervious to networ$ latency as well. Because of its intimate relationship with the machine where it is running' a smart client application is generally able to provide a much richer user e(perience. +ustom drawing'

interesting font settings' and convenient controls are some of the visual features which help set apart the smart client application from a Web/based application. 5mart client applications also' by their nature' are able to use all of the resources available on the client computer. 7ocal dis$ and memory storage is easy to access. 8roblems which can benefit from those strengths are great candidates for solutions involving a smart client. 9pportunities for those solutions have always been around: #or nearly a decade' #+ made it possible for +;; programmers to write client/side applications with very rich user interfaces. #+%s goal was to provide an ob<ect/oriented wrapper that added value to +;; programmers. 5ometimes' that value was only to ma$e particular 08*s more convenient from +;;' but the bul$ of #+ was aimed at offering a framewor$ that made rich client applications easy to write by integrating features most commonly found in such applications. Windows #orms developers en<oy a more complete set of lightweight wrappers for the system 08*s than #+ developers did. The e(tensive coverage of the .!"T #ramewor$ +lass 7ibrary is born out of necessity as' unli$e #+ developers' managed programmers have a few challenges in ma$ing direct calls to the raw system 08*s. *n this boo$' +hris 5ells discusses how the Windows #orms classes and their supporting infrastructure can be used to write robust and rich smart client applications. *f you%re an e(perienced Windows programmer who has previously used #+ or directly utili=ed the Win3> 08* to write such applications' you will find +hris%s direct delivery very appropriate for transferring your $nowledge to the managed classes. *f you%re a developer with less Windows development e(perience' you%ll find the treatment of core concepts in application 1* programming indispensable. 0 new language' and a new framewor$ for writing smart client applications' offers a new set of compromises to software engineers. What is software engineering besides choosing a solution which brings an acceptable set of compromises to its users? The more tools a developer has and the better he or she is at applying them appropriately' the more problems that developer will be able to solve. 3Best of all' a new set of tools for client applications will give +hris new focus' and he%ll )uit barraging me with complaints about #+.4 @ead on to add Windows #orms to your toolbo(. i$e Blas=c=a$ 8riary #+ Aeveloper icrosoft +orporation mi$eblasBmsn.com

Preface
0s a fairly public figure in the Windows developer community' * often get as$ed if * thin$ that .!"T is going to &ta$e off.& * always answer the same way: *t%s not a matter of &if'& it%s a matter of &when.&

icrosoft%s .!"T #ramewor$ has so many benefits that even as a gri==led old +;;CWin3> guy' * wasn%t able to resist the siren song of a managed development environment. *t%s ironic that the temporary dip in the economy has caused fol$s to avoid anything new <ust when .!"T has come along to deliver significant reductions in time to mar$et and cost while simultaneously increasing code )uality. The organi=ations that have already adopted .!"T $now that it%s going to have a long and happy life' especially as it gets pushed further and further into icrosoft%s own plans for the future of the Windows platform' both on the server and on the client. The primary server/side technology in .!"T is 058.!"T' which provides the infrastructure needed to build Web sites and Web services. 058.!"T gives developers the reach to deploy Web sites to anyone by aiming at the baseline of features offered by the middle/ generation Web browsers. To provide the highest level of functionality possible' 058.!"T does most of the wor$ on the server side' leaving the client/side 6T 7 as a thin wrapper to trigger server/side re)uests for new pages of data. The server side handles almost everything' from data manipulation to user preferences to the rendering of simple things li$e menus and toolbars. This model provides the greatest availability across operating systems and browsers. *f' on the other hand' your targeted customers are Windows users' an 6T 7/based e(perience limits your users to a lowest/common/denominator approach that is unnecessary. *n fact' in an attempt to provide a richer client/side e(perience' many organi=ations that $now they%re targeting Windows users re)uire specific versions of icrosoft%s *nternet "(plorer 3*"4 Web browser. 0s soon as that $ind of targeting happens' *" becomes less of a browser and more of an 6T 7/based application runtime. #or that purpose' the 6T 7 ob<ect model is fairly primitive' often re)uiring that you do a lot of wor$ to do things that are usually simple 3li$e $eeping trac$ of a user%s session state4. *f you%re targeting Windows users' the .!"T #ramewor$ gives you a much richer set of ob<ects for building interactive user interfaces. This brings me to the sub<ect of this boo$: Windows #orms 3Win#orms4. Win#orms is the face of .!"T on the client' providing a forms/based development environment meant to embody the best of the 1* ob<ect models that have come before it. *n addition' it has one feature that no Windows/based development framewor$ has provided to date: the deployment features of 6T 7/based Web applications. The ability to combine the richness of Windows applications with the deployment of Web applications signals a completely new world for Windows developers' one that ma$es me more than happy to give up the mess of unmanaged code.

Who Should Read This Book?


When writing this boo$' * had two target audiences in mind. * wanted to provide real/world Win#orms coverage for both the programmer who has already programmed in .!"T and for the programmer who hasn%t. Toward that end' * briefly introduce core .!"T topics as they come up. 6owever' the .!"T #ramewor$ itself is a large area that this boo$ doesn%t pretend to cover completely. *nstead' when * thin$ more information would be useful' *

reference another wor$ that provides the full details. *n particular' * find that *%ve referenced Essential .NET' by Aon Bo(' with +hris 5ells' a great deal' ma$ing it a good companion to this boo$. *n this same category' * also recommend Pragmatic ADO.NET' by 5hawn Wildermuth' Advanced .NET Remoting' by *ngo @ammer' .NET Web Services' by Deith Ballinger' and Applied Microsoft .NET Frame or! Programming' by Eeffrey @ichter. 3#or more details on these boo$s' see the Bibliography.4 Two core .!"T topics are of special importance to Win#orms programmers' and * cover them in more detail in 0ppendi( B: Aelegates and "vents and in 0ppendi( +: 5eriali=ation Basics. The coverage of delegates and events is particularly important if you%re new to .!"T' although * don%t recommend diving into that topic until you%ve got a Win#orms/ specific frame of reference 3which is provided about one/third of the way through +hapter 1: 6ello' Windows #orms4. 9ne other note: any years ago' * wrote my first five/day training course. The topic was Windows 9F and included a few hours of coverage on the new controls: what they loo$ed li$e' what their properties' methods' and events were' and how to program against them. Those hours seemed li$e days both for me and for the students. The details of a particular control are interesting only when you%re putting that control to use' and when that time comes' the control/specific documentation and *ntelli5ense do a marvelous <ob of giving you the information you need. Toward that end' this boo$ covers none of the standard controls completely. *nstead' as each control is interesting in the conte(t of the current topic.such as the AataGrid control in +hapter 13: Aata Binding and Aata Grids.that control is covered appropriately. 0lso' +hapter 8: +ontrols and +hapter 9: Aesign/Time *ntegration introduce the broad range of categories of controls that Win#orms provides' including the category of nonvisual controls called components in .!"T. #inally' to give you a visual to go with all the controls and components and to introduce you to each one%s ma<or functionality' 0ppendi( A: 5tandard Win#orms +omponents and +ontrols provides a list of the standard controls and components. * wouldn%t thin$ of wasting your time by attempting to be more thorough than the reference documentation that comes with the .!"T #ramewor$ 5AD and -isual 5tudio .!"T. *nstead' this boo$ focuses on the real/world scenarios that aren%t covered in detail elsewhere.

Conventions
*f you have decided to ta$e the plunge with this boo$' *%d li$e to than$ you for your faith and e(press my hope that * live up to it. To aid you in reading the te(t' * want to let you in on some conventions * use in my writing. #irst and foremost' the wonderful thing about Win#orms is how visual it is' and that%s why * use a lot of figures to illustrate its features. 5ome of those pictures really need to be in color to ma$e the point' so be sure to chec$ the color pages at the center of this boo$ for those color plates. 0s useful as figures are' * thin$ primarily in code. +ode is shown in monospace type:

System.Console.WriteLine("Hello, WinForms.");

+onsole application activation is also shown in monospace type:


C:\> csc.exe hello.cs

When a part of a code snippet or a command line activation is of particular interest' * mar$ it in bold and often provide a comment:
// Notice the use of the .NET System namespace System.Console.WriteLine("Hello, WinForms.");

When * want to direct your attention to a piece of code even more fully' * replace superfluous code with ellipses:
class MyForm : System.Win o!s.Forms.Form "
... // fields

#ri$ate $oi MyForm%Loa (o&'ect sen er, System.Com#onentMo el.($ent)r*s e) " Messa*e+ox.Sho!("Hello ,rom MyForm"); -

#urthermore' to ma$e the printed code more readable' * often drop namespaces and protection $eywords when they don%t provide additional information:
// Shortened System.Windows.Forms.Form !ase class

class MyForm : Form " ... .. ,iel s


// "emo#ed pri#ate specifier and System.Component$odel namespace

$oi MyForm%Loa (o&'ect sen er, ($ent)r*s e) " Messa*e+ox.Sho!("Hello ,rom MyForm"); -

+onversely' when showing .!"T attributes' * use their full name:


%Seriali&a!le'ttri!ute(

class MyC/stom0y#e "...-

5ome languages' such as +,' let you drop the &0ttribute& suffi( for convenience' but that ma$es it hard to pin down the details of the attribute class in the online documentation. 0lso' * sometimes omit error chec$ing from the printed code for clarity' but * try to leave it in the sample code that comes with this boo$. *n the prose itself' * often put a word or phrase in italics to indicate a new term that *%m about to define. 0s an e(ample of this $ind of term and its definition' "egemon# is a preponderant influence or authority' as well as a potent business practice.

#inally' * often mention $eyboard shortcuts because * find them convenient. The ones * mention are the default -isual 5tudio Aeveloper $ey bindings. *f you%re not using those $ey bindings' you%ll need to map the $eyboard shortcuts to your own settings.

Contact
The up/to/date information for this boo$' including the source code and the errata' are maintained at http:CCwww.sellsbrothers.comCwritingCwfboo$. This site also provides a way for you to send feedbac$ to me about the boo$' both complimentary and less so.

Acknowledgments
0lthough this boo$ is already dedicated to my family' *%d also li$e to ac$nowledge them here. * wor$ from my home' but in completing the boo$ * often had to spend a great deal of e(tra time at the end to get the thing out the door. y wife' elissa' was enormously understanding when * had a deadline and gave me the space * needed to meet it. 0lso' * tend to leave my office door open because * li$e my family' and often my boys' Eohn and Tom' will come in to tal$ to me about their day. "ven though they%re only nine and seven' respectively' they%re uncharacteristically understanding when it comes to letting me focus on my wor$ for &<ust another five minutes& 3although woe is me if * overpromise and underdeliver to those two' *%ll tell you4. *n the family category' *%d also li$e to than$ Beth and Eoel 6owie' my sister and brother/in/law' for giving me even more space and ta$ing their nephews when the boys were tired of hearing me say &<ust another five minutes.& 9f course' * need to than$ my parents' who made me a voracious reader and passed along the writing s$ills * never even $new they had %til very recently. 0lthough my family gave me the space to write this boo$' it would not be what it is without the efforts of some other very helpful fol$s. ichael Weinhardt was my co/author for the two/part MSDN Maga$ine series &Building Windows #orms +ontrols and +omponents with @ich Aesign/Time #eatures'& which was the predecessor to +hapter 9: Aesign/Time *ntegration. 6e also contributed some of the best figures in this boo$' including the resource resolution figures in +hapter 10: @esources. 5imilarly' 5hawn &The 0A9 Guy& Wildermuth helped me a great deal not only with the two database chapters but also with +hapter 8: +ontrols and 0ppendi( A: 5tandard Win#orms +omponents and +ontrols. The boo$ would not have been the same without you two' i$e and 5hawn. *n addition' i$e Woodring gave me great feedbac$ on the threading portions of this boo$. Deith Brown has always been the wind beneath my security wings' giving me tons of guidance on how .!"T security really wor$s 3and why4. #rit= 9nion gave me great feedbac$ when this boo$ started as a five/day training course' as did *an Griffiths. 0nd' luc$y for me' * caught 0llan +ooper <ust as he was getting interested in programming again. 6e read each chapter threatening to stop when he got bored. 6e gave me fabulous feedbac$ that really left a mar$ 3particularly on the resources chapter4.

*%d also li$e to than$ a few guys that didn%t $now they were helping me with my boo$' including 7<ubomir 5pasovs$i for &6elp 0uthoring in .!"T& on dotnet<un$ies.com' Bob 8owell for the Araw@ound@ect method on dotnet>HI.com' and Eoseph !ewcomer for his multi/instance article on flounder.com. *n this same category' *%d li$e to than$ Eeff 8rosise for inspiring this boo$ with the single Win#orms chapter in his boo$ Programming Microsoft .NET. That one chapter drove me to write this boo$ because * couldn%t stand the idea of Win#orms summari=ed in a single chapter. *%d also li$e to than$ Aon Bo( for letting me tag along on his boo$ Essential .NET. * didn%t write a word of it' but my close involvement with it taught me tons about how .!"T really wor$s. That education ma$es its way into this boo$. This boo$%s reviewers deserve special than$s: Bill Woodruff' Brian Graf' +hristophe !asarre' +ristof #al$' Aoug @eilly' #umia$i Joshimatsu' Eohan "ricsson' ar$ @ideout' artin 6eller' 8ierre !allet' @ichard Blewett' 5cott Aensmore' 5erge 5himanovs$y' 5uresh Eambu' Tim Tabor' and Keeshan 0m<ad. The comments of all my reviewers had a huge impact on the boo$' and * can%t tell you how important you all were to me. *%d especially li$e to single out Bill' +hristophe' ar$' and artin as being e(tra thorough' and +hristophe 3again4 for being there after *%d applied reviewer comments to ma$e sure everything still made sense. * can%t tell you how much embarrassment you guys saved me. Than$s2 9f course' * have to than$ the guys at icrosoft who helped invent this technology and then were there to help the community 3and me specifically4 in understanding it: ar$ Boulter' +hris 0nderson' and Eamie +ool. *%d li$e to than$ MSDN Maga$ine' MSDN Online' and Windo s Developer maga=ine for allowing me to reuse material from articles that they originally published 3as listed in the Bibliography4. *%d also li$e to than$ my readers' whose feedbac$ on those initial pieces helped shape the final version of this content' as well as inspiring me to dig even deeper than * had initially. 7ast but not least' *%d li$e to than$ the fine fol$s at 0ddison/Wesley. *n increasingly tight times' they still manage to provide me an environment where * can write what * thin$ best. 5pecial than$s go to Betsy 6ardinger: copy editor' frustrated/fiction/author' $indred spirit' and hyphen mentor. *n addition to turning my prose into "nglish' she also managed to catch technical inconsistencies that hardcore developers missed. Than$s' Betsy2 These fol$s' along with a bunch *%m sure *%m missing' have helped shape everything good that comes through in this boo$. The errors that remain are mine. +hris 5ells 0pril >003 www.sellsbrothers.com

Chapter 1. Hello, Windows Forms


0s easy to use as Windows #orms 3Win#orms4 is' the sheer amount of functionality that it provides can ma$e it intimidating. especially when combined with the huge number of features that -isual 5tudio .!"T 3-5.!"T4 provides solely for the purpose of building Win#orms code. 5o this chapter ta$es a )uic$ loo$ at most of what Win#orms provides' including forms' controls' application settings' resources' dialogs' drawing' printing' data binding' threading' and even deployment over the Web. We also loo$ at how the -5.!"T environment facilitates Win#orms development. The remaining chapters will stuff you full' providing the sumptuous details of these topics' but in this chapter you%ll get your first taste.

WinForms from Scratch


0 typical Windows #orms application has at least one form. Without the form it%s <ust an &application'& which is pretty boring. 0 form is simply a window' the unit of the icrosoft user interface we%ve seen since Windows 1.0. 9ne form in a Win#orms application is typically the main form' which means that it is either the parent or the owner of all other forms that may be shown during the lifetime of the application. *t%s where the main menu is shown' along with the toolbar' status bar' and so on. When the main form goes away' the application e(its.
L1M L1M

The distinction between a form%s &parent& and &owner& is covered in detail in +hapter >: #orms.

The main form of an application can be a simple message bo(' a dialog bo(' a 5ingle Aocument *nterface 35A*4 window' a ultiple Aocument *nterface 3 A*4 window' or something more complicated' such as the forms you%re used to seeing in applications li$e -isual 5tudio .!"T. These latter forms may include multiple child windows' tool windows' and floating toolbars. *f your application is enormously simple' you can implement it using the staple of any windowing system' the lowly message bo%:
class MyFirst)## " static $oi Main() "
System.Windows.Forms.$essage)o*.Show+ ,ello- Windows Forms ./

*f you%re new to +,' ain is the entry point for any +, application. The ain function must be a member of a class' and hence the need for y#irst0pp. 6owever' the .!"T runtime doesn%t create an instance of the y#irst0pp class when our code is loaded and e(ecuted' so our ain function must be mar$ed static. *n this way' you mar$ a method as available without creating an instance of the type.

The single line of real code in our first Win#orms application calls the static 5how method of the 5ystem.Windows.#orms. essageBo( class' which is really a long/winded way of saying we%re calling a method on the essageBo( class contained within the 5ystem.Windows.#orms namespace. Namespaces are used e(tensively in the .!"T #ramewor$ +lass 7ibraries 3#+74 to separate types' such as classes' structures' enumerations' and so on' into logical groupings. This separation is necessary when you%ve got thousands of icrosoft employees wor$ing on the #+7 as well as hundreds of third parties e(tending it and millions of programmers trying to learn it. Without namespaces' you would need all $inds of wac$y conventions to $eep things uni)uely named 3as demonstrated by the e(isting Win3> 08*4. 6owever' as necessary as namespaces are' they%re a little too much typing for me' so * recommend the +, &sing statement' as shown here:
using System/ using System.Windows.Forms/

class MyFirst)## " static $oi Main() " -

$essage)o*.Show+ ,ello- Windows Forms ./

When the compiler sees that the essageBo( class is being used' it first loo$s in the global namespace' which is where all types end up that aren%t contained by a namespace 3for e(ample' the y#irst0pp class is in the global namespace4. *f the compiler can%t find the type in the global namespace' it loo$s at all the namespaces currently being used.in this case' 5ystem and 5ystem.Windows.#orms. *f the compiler finds a type name being used that e(ists in two or more namespaces' it produces an error and we%re forced to go bac$ to the long notation. But in practice this is rare enough to ma$e the short form the form of choice when you%re typing code by hand. 6owever' even though the essageBo( class is enormously handy for showing your users simple string information or as$ing them yesCno )uestions' it%s hard to build a real application with essageBo(. #or most things' you%ll need an instance of the #orm class 3or a #orm/derived class4:
class MyFirst)## " static $oi Main() " Form ,orm 1 ne! Form(); -

form.Show+./ // Not what you want to do

0lthough this code will show the form' you%ll have to be )uic$ to see it because 5how shows the form modelessly. *f you%re not steeped in user interface lore' a modeless form is one that displays but allows other activities 3called modes4 to ta$e place. 5o' immediately after 5how puts our new form on the screen' it returns control to the ain function' which promptly returns' e(iting the process and ta$ing our nascent form with it. To show a form

modall#'that is' to not return control to the ain function until the form has closed.the documentation suggests using the 5howAialog function:
class MyFirst)## " static $oi Main() " Form ,orm 1 ne! Form(); -

form.Show0ialog+./ // Still not what you want to do

This code would show a blan$ form and wait for the user to close it before returning control to the ain function' but it%s not the code you will generally be writing. *nstead' to ma$e it accessible in other parts of your application' you%ll be designating one form as the main form. To do this' pass the main form as an argument to the @un method of the 0pplication ob<ect' which also resides in the 5ystem.Windows.#orms namespace:
class MyFirst)## " static $oi Main() " Form ,orm 1 ne! Form(); -

'pplication."un+form./ // This is what you want to do

The 0pplication class%s static @un method will show the main form' and when it%s closed' @un will return' letting our ain function e(it and closing the process. To see this in action' you can compile your first Win#orms application using the following command line:
L>M L>M

To get a command prompt with the proper 80T6 environment variable set to access the .!"T command line tools' clic$ on 5tartN8rogramsN icrosoft -isual 5tudio .!"TN-isual 5tudio .!"T ToolsN-isual 5tudio .!"T +ommand 8rompt. *f you don%t have -5.!"T installed' you can set

up the 80T6 using the corvars.bat batch file in your #ramewor$5ADOBin directory.

C:\> csc.exe .t:!inexe .r:System.Win o!s.Forms. ll MyFirst)##.cs

The csc.e(e command invo$es the compiler on our source file' as$ing it to produce a Windows application via the Ct flag 3where the &t& stands for &target&4' pulling in the 5ystem.Windows.#orms.dll library using the Cr flag 3where the &r& stands for &reference&4. The <ob of the compiler is to pull together the various source code files into a .!"T assembly. 0n assembl# is a collection of .!"T types' code' or resources 3or all three4. 0n assembly can be either an application' in which case it has an .e(e e(tension' or a library' in which case it has a .dll e(tension. The only real difference between the types of assemblies is whether the assembly has an entry point that can be called by Windows when the assembly is launched 3.e(e files do' and .dll files do not4. !ow that that the compiler has produced y#irst0pp.e(e' you can e(ecute it and see an application so boring' it%s not even worth a screen shot. When you close the form' y#irst0pp.e(e will e(it' ending your first Win#orms e(perience.

To spice things up a bit' we can set a property on our new form before showing it:
class MyFirst)## " static $oi Main() " Form ,orm 1 ne! Form(); )##lication.2/n(,orm);

form.Te*t 1 ,ello- WinForms2 /

7i$e most ob<ects in the #+7' #orm ob<ects have several properties to access' methods to call' and events to handle. *n this case' we%ve set the Te(t property' which' for a #orm' sets the caption. We could do the same thing to set other properties on the form' showing it when we were finished' but that%s not the way we generally do things in Win#orms. *nstead' each custom form is a class that derives from #orm and initiali=es its own properties:
class $yFirstForm 3 Form 4 pu!lic $yFirstForm+. 4 this.Te*t 1 ,ello- WinForms2 / 5 5

class MyFirst)## " static $oi Main() "


Form form 1 new $yFirstForm+./

)##lication.2/n(,orm); -

!otice that the y#irst#orm class derives from #orm and then initiali=es its own properties in the constructor. This gives us a simpler usage model' as shown in the new ain function' which creates an instance of the y#irst#orm class. Jou also gain the potential for reuse should y#irst#orm be needed in other parts of your application. 5till' our form is pretty boring. *t doesn%t even include a way to interact with it e(cept for the system/provided adornments. We can add some interactivity by adding a button:
class MyFirstForm : Form " #/&lic MyFirstForm() " this.0ext 1 "Hello, WinForms3";
)utton !utton 1 new )utton+./ !utton.Te*t 1 Clic6 $e2 / this.Controls.'dd+!utton./

0dding a button to the form is a matter of creating a new Button ob<ect' setting the properties that we li$e' and adding the Button ob<ect to the list of controls that the form manages. This code will produce a button on the form that does that nifty 3/A depress thing that buttons do when you press them' but nothing else interesting will happen. That%s because we%re still not handling the button%s clic$ event' where an event is a way for a

control to notify its container that something has happened. #or e(ample' the following code handles the button%s +lic$ event:
class MyFirstForm : Form " #/&lic MyFirstForm() " this.0ext 1 "Hello, WinForms3"; +/tton &/tton 1 ne! +/tton(); &/tton.0ext 1 "Clic4 Me3"; this.Controls.) (&/tton);

!utton.Clic6 71 new E#ent,andler+!utton8Clic6./

#oid !utton8Clic6+o!9ect sender- E#ent'rgs e. 4 $essage)o*.Show+ That:s a strong- confident clic6 you:#e got... ./ 5

6andling the button%s +lic$ event involves two things. The first is creating a handler function with the appropriate signatureP we%ve named this function buttonQ+lic$. The signature of the vast ma<ority of .!"T events is a function that returns nothing and ta$es two parameters: an ob<ect that represents the sender of the event 3our button' in this case4' and an instance of a 5ystem."vent0rgs ob<ect 3or an ob<ect that derives from the "vent0rgs class4. The second thing that%s needed to subscribe to an event in +, is shown by the use of the &;R& operator in the y#irst#orm constructor. This notation means that we%d li$e to add a function to the list of all the other functions that care about a particular event on a particular ob<ect' and that re)uires an instance of an "vent6andler delegate ob<ect. 0 delegate is a class that translates invocations on an event into calls on the functions that have subscribed to the event. #or this particular event' we have the following logical delegate and event definitions elsewhere in the .!"T #+7:
names#ace System " #/&lic ele*ate $oi ($entHan ler(o&'ect sen er, ($ent)r*s e);

names#ace System.Win o!s.Forms " #/&lic class +/tton " #/&lic e$ent ($entHan ler Clic4; -

!otice that the +lic$ event on the Button class is a reference to an "vent6andler delegate' and so to add our own method to the list of subscribers' we need to also create an instance of the delegate. 9f course' it can )uic$ly become tedious to figure out the delegate signatures of all the events you%re interested in or to add controls to a form via code by hand.
L3M L3M

#or a more detailed' although less reverent' loo$ at delegates and events' read 0ppendi( B: Aelegates and "vents. #or a much more detailed and

reverent loo$' refer to

Essential .NET 30ddison/Wesley' >0034' by Aon Bo(' with +hris 5ells.

7uc$ily' it%s also unnecessary because of the Win#orms Wi=ard and the Win#orms Aesigner provided by -isual 5tudio .!"T.

Windows Forms in Visual Studio . !T


ost Win#orms pro<ects start in the !ew 8ro<ect dialog bo(' available via #ile N !ew N 8ro<ect 3+trl;5hift;!4 and shown in #igure 1.1. Figure 1.1. WinForms Projects

To build an application' you%ll want the Windows 0pplication pro<ect template. To build a library of custom controls or forms for reuse' you%ll want the Windows +ontrol 7ibrary pro<ect template. When you run the Windows 0pplication Wi=ard' choosing whatever you li$e for the pro<ect name and location' you%ll get a blan$ form in the Aesigner' as shown in #igure 1.>. Figure 1.2. The WinForms Designer

Before we start the drag/and/drop e(travagan=a that the Aesigner enables' let%s ta$e a loo$ at a slightly abbreviated version of the code generated by the Win#orms application Wi=ard 3available by right/clic$ing on the design surface and choosing -iew +ode or by pressing #I4:
/sin* System; /sin* System.Win o!s.Forms; names#ace MySecon )## " #/&lic class Form5 : System.Win o!s.Forms.Form " #/&lic Form5() "
;nitiali&eComponent+./

#region Windows Form 0esigner generated code /// <summary= /// "e>uired method for 0esigner support ? do not modify /// the contents of this method with the code editor. /// </summary= pri#ate #oid ;nitiali&eComponent+. 4 this.Si&e 1 new System.0rawing.Si&e+@AA-@AA./ this.Te*t 1 FormB / 5 #endregion

static $oi Main() " )##lication.2/n(ne! Form5()); -

ost of this code should be familiar' including the &sing statements at the top' the form class that derives from the #orm base class' the static ain function inside a class providing the entry point to the application' and the call to 0pplication.@un' passing an instance of the main form class. The only thing that%s different from what we did ourselves is the call to *nitiali=e+omponent in the form%s constructor to set the form%s properties instead of doing it in the constructor itself. This is done so that the Win#orms Aesigner' which we can use to design our form visually' has a place to put the code to initiali=e the form and the form%s control. #or e(ample' dragging a button from the Toolbo( onto the form%s design surface will change the *nitiali=e+omponent implementation to loo$ li$e this:
#ri$ate $oi
this.!uttonB 1 new System.Windows.Forms.)utton+./ this.SuspendCayout+./ // // !uttonB // this.!uttonB.Cocation 1 new System.0rawing.Point+DE- FG./ this.!uttonB.Name 1 !uttonB / this.!uttonB.Ta!;nde* 1 A/ this.!uttonB.Te*t 1 !uttonB / // // FormB // this.'utoScale)aseSi&e 1 new System.0rawing.Si&e+H- B@./ this.ClientSi&e 1 new System.0rawing.Si&e+GDG- GEE./ this.Controls.'dd"ange+ new System.Windows.Forms.Control%( 4 this.!uttonB5./ this.Name 1 FormB /

6nitiali7eCom#onent() "

this.0ext 1 "Form5";
this."esumeCayout+false./

!otice again that this code is very similar to what we built ourselves' but this time created for us by the Aesigner. 1nfortunately' for this process to wor$ reliably' the Aesigner must have complete control over the *nitiali=e+omponent method. *n fact' notice that the Wi=ard/generated *nitiali=e+omponent code is wrapped in a region' which will hide the code by default' and is mar$ed with a telling comment:
... 2e8/ire metho ,or 9esi*ner s/##ort : o not mo i,y ... the contents o, this metho !ith the co e e itor.

*t may loo$ li$e your favorite programming language' but *nitiali=e+omponent is actually the seriali=ed form of the ob<ect model that the Aesigner is using to manage the design surface. 0lthough you can ma$e minor changes to this code' such as changing the Te(t property on the new button' ma<or changes are li$ely to be ignored.or worse' thrown away. #eel free to e(periment with <ust how far you can go by modifying this seriali=ation format by hand' but don%t be surprised when your wor$ is lost. * recommend putting custom form initiali=ation into the form%s constructor' after the call to *nitiali=e+omponent' giving you confidence that your code will be safe from the Aesigner.

9f course' we put up with the transgression of the Aesigner because of the benefits it provides. #or e(ample' instead of writing lines of code to set properties on the form or the controls contained therein' all you have to do is to right/clic$ on the ob<ect of interest and choose 8roperties 3or press #H4 to bring up the 8roperty Browser for the selected ob<ect' as shown in #igure 1.3. Figure 1. . The Propert! "rowser

0ny properties with nondefault values' as indicated by values in boldface in the browser' will be written into the *nitiali=e+omponent method for you. 5imilarly' to choose an event to handle for the form or the form%s controls' you can press the "vents lighting bolt at the top of the 8roperty Browser window to open the list shown in #igure 1.H. Figure 1.#. $ist of %&ents

Jou have a few ways to handle an event from the 8roperty Browser window. 9ne way is to find the event you%d li$e to handle on the ob<ect selected 3say' +lic$4 and type the name of the function you%d li$e to call when this event is fired 3say' buttonQ+lic$4. Then press "nter' and -5.!"T will ta$e you to the body of an event handler with that name and the correct signature' all ready for you to implement:
#ri$ate $oi &/tton%Clic4(o&'ect sen er, System.($ent)r*s e) "

0fter you%ve added a handler to a form' that handler will show up in a drop/down list for other events having the same signature. This techni)ue is handy if you%d li$e the same event for multiple ob<ects to be handled by the same method' such as multiple buttons with the same handler. Jou can use the sender argument to determine which ob<ect fired the event:
#ri$ate $oi &/tton%Clic4(o&'ect sen er, System.($ent)r*s e) " +/tton &/tton 1 sen er as +/tton; Messa*e+ox.Sho!(&/tton.0ext ; " !as clic4e "); -

*f' as is often the case' you%d li$e each event that you handle for each ob<ect to be uni)ue or you <ust don%t care what the name of the handler is' you can simply double/clic$ on the name of the event in the 8roperty BrowserP an event handler name will be generated for you' based on the name of the control and the name of the event. #or e(ample' if you double/clic$ed on the 7oad event for the #orm1 form' the event handler name would be #orm1Q7oad. #urthermore' if you%re handling the defa&lt event of an ob<ect' you can handle it simply by double/clic$ing on the ob<ect itself. This will generate an event handler name <ust as if you%d double/clic$ed on that event name in the 8roperty Browser event list. The default event of an ob<ect is meant to be intuitively the most handled event for a particular type. #or e(ample' *%m sure you won%t be surprised to learn that the default event for a button is +lic$ and that the default event for a #orm is 7oad. 1nfortunately' neither the Aesigner nor the 8roperty Browser gives any indication what the default event will be for a particular type' but e(perimentation should reveal few surprises.

Arranging Controls
The beauty of the Aesigner is that it lets you lay out your controls lovingly within your form' ma$ing sure everything lines up nicely' as shown in #igure 1.F. Figure 1.'. (icel! $aid)*ut Form at +deal ,i-e

But then someone resi=es it' as shown in #igure 1.S. Figure 1... (icel! $aid)*ut Form /esi-ed

The user isn%t resi=ing the form to get more gray space but to ma$e the controls bigger so that they will hold more data. #or that to happen' the controls need to resi=e to ta$e up the newly available space. Jou can do this manually by handling the form%s @esi=e event and writing the code. 9r you can do it with anchoring. Anc"oring is one of the ways that Win#orms provides for automatic layout control of your forms and the controls contained therein. By default' all controls are anchored to the upper/ left' so that as the form is resi=ed and moved' all controls are $ept at their position relative to the upper/left corner of the form. 6owever' in this case' we%d clearly li$e to have the te(t bo( controls widen or narrow as the form is resi=ed. We implement this by setting each te(t bo(%s 0nchor property. *n the 8roperty Browser for the control' we choose the 0nchor property' which displays an editor li$e the one in #igure 1.I. Figure 1.0. ,etting the 1nchor Propert!

To change the te(t bo(es so that they anchor to the right edge as well as the top and left edges is a matter of clic$ing on the anchor rectangle on the right and changing the 0nchor property to Top' 7eft' @ight. This will cause the te(t bo(es to resi=e as the form resi=es' as shown in #igure 1.8. Figure 1.2. 1nchoring Te3t "o3es Top, $eft, /ight and "uttons "ottom, /ight

The default anchoring is top/left' but those edges need not be a part of the anchoring settings at all. #or e(ample' notice that #igure 1.8 anchors the 9D and +ancel buttons to the bottom/right' as is customary with Windows dialogs. *f instead of building a dialog/style form' you%d li$e to build a window/style form' anchoring is not your best bet. #or e(ample' suppose you%re building an "(plorer/style application' with a menu bar and toolbar on the top' a status bar on the bottom' and a tree view and a list view ta$ing up the rest of the space as determined by a splitter between them. *n that $ind of application' anchoring won%t do. *nstead' you%ll want doc$ing. Doc!ing allows you to &stic$& any control on the edge of its container' the way a status bar is stuc$ to the bottom of a form. By default' most controls have the Aoc$ property set to !one 3the default for the 5tatusBar control is Bottom4. Jou can change the Aoc$ property in the 8roperty Browser by pic$ing a single edge to doc$ to or to ta$e up whatever space is left' as shown in #igure 1.9.

Figure 1.4. ,etting the Doc5 Propert!

0s an e(ample' the form in #igure 1.10 shows the Aoc$ properties for a status bar' a tree view' and a list view' the latter two being split with a splitter control. Jou can arrange all this without writing a line of code. Figure 1.16. Doc5ing and ,plitting

0nchoring' doc$ing' and splitting are not the only ways to arrange controls on a form. Win#orms also lets you group controls and handle custom layout for special situations. *n addition' Win#orms supports arranging windows within a parent' which we call A*. These techni)ues are all covered in detail in +hapter >: #orms.

Controls
9ften' after arranging a set of controls <ust right' you need that group of controls elsewhere. *n that case' you can copy and paste the controls between forms' ma$ing sure that all the settings are maintained' or you can encapsulate the controls into a &ser control for a more robust form of reuse. 1ser controls are containers for other controls and are best created in a Windows +ontrol 7ibrary pro<ect. To add a Windows +ontrol 7ibrary pro<ect to an e(isting solution' you use the 0dd !ew 8ro<ect item from the menu you get when you right/clic$ on your Win#orms application%s solution in 5olution "(plorer. Jou%ll also want to ma$e sure that you%re creating the new pro<ect in the same location as your e(isting solution' because -5.!"T >00> defaults to placing new pro<ects one folder too far up the hierarchy in most cases. #igure 1.11 shows how to add a new pro<ect called y#irst+ontrol7ibrary to an e(isting solution called y5econd0pp.

Figure 1.11. 1dding a (ew Project to an %3isting ,olution

0fter you%ve created a control library pro<ect' you%ll be presented with a user control design surface very li$e that of a form. The only real difference is that there%s no border or caption' which will be provided by the form host of your new control. The code generated by the Wi=ard loo$s very much li$e the code generated for a new form e(cept that the base class is 1ser+ontrol instead of #orm:
/sin* System; /sin* System.Win o!s.Forms; names#ace MyFirstControlLi&rary " #/&lic class IserControlB 3 System.Windows.Forms.IserControl " #/&lic <serControl5() " 6nitiali7eCom#onent(); #ri$ate $oi 6nitiali7eCom#onent() "

*n the Aesigner' you can drop and arrange any controls on the user control that you li$e' setting their properties and handling events <ust as on a form. #igure 1.1> shows a sample user control as seen in the Aesigner. Figure 1.12. 1 7ser Control ,hown in the Designer

When you%re happy with your control' you can add it to the Toolbo( by dragging the user control%s source code file' in this case 1ser+ontrol1.cs' from the 5olution "(plorer to the Toolbo(. When it%s there' you can drag and drop it onto the forms of your choice' setting properties and handling events via the 8roperty Browser <ust as with any of the built/in controls. #igure 1.13 shows the user control from #igure 1.1> hosted on a form. Figure 1.1 . Hosting a 7ser Control

1ser controls aren%t the only $ind of custom controls. *f you%re interested in drawing the contents of your controls yourself' scrolling your controls' or getting more details about user controls' you%ll want to read +hapter 8: +ontrols.

A""lication Settings
0s some applications get more sophisticated' users e(pect more from all their applications. #or e(ample' some applications let users set fonts and colors and all $inds of cra=y things 3guaranteeing that you%ll never be able to successfully test every combination4. *n addition to the other fancy user interface 31*4 features that it provides' Win#orms supports the idea

of reading a control%s properties from an application%s configuration file' also called a .config file. .!"T allows every application to have a .config file associated with it. #or e(ample' when .!"T loads y5econd0pp.e(e' it will loo$ for a corresponding y5econd0pp.e(e.config file in the same folder as the application. 0 .config file can have standard settings for things such as security' remoting' loading' and versioning. *n addition to standard .!"T settings' a configuration file can have custom settings strictly for the application%s use. #or e(ample' the following is a .config file containing settings specifying a custom $ey/value pair:
=con,i*/ration> =a##Settin*s>
<add 6ey1 $ainFormJpacity #alue1 .H /=

=.a##Settin*s> =.con,i*/ration>

!otice that the .config file is in T 7 format. This means that savvy users can open the .config file in !otepad to change the setting. #or this setting to be used to set the main form%s opacity property' the setting needs to be read:
/sin* System.Con,i*/ration; #/&lic class MainForm : System.Win o!s.Forms.Form " #/&lic MainForm() " 6nitiali7eCom#onent(); .. ),ter 6nitiali7eCom#onent call
'ppSettings"eader appSettings 1 new 'ppSettings"eader+./ o!9ect o 1 appSettings.KetLalue+ $ainFormJpacity - typeof+dou!le../ this.Jpacity 1 +dou!le.o/

...

*nstead of opening the .config file directly' this code uses the 0pp5ettings@eader function from the 5ystem.+onfiguration class. This class provides access to the $ey/value pairs in the Uapp5ettingsV section of the .config file associated with the application. We use the reader to get the value of the ain#orm9pacity $ey and use that to set the property on the form. *f this is the $ind of thing you%d li$e to provide in your application%s forms for a wide variety of properties' it will be a chore to manually pull each value from the 0pp5ettings@eader. #or this reason' each control on each form' as well as the form itself' has a set of dynamic properties available in the 8roperty Browser. The d#namic properties are those automatically read from the application%s .config file. #or e(ample' to tell the Aesigner to generate code to pull in the opacity setting for the main form' you clic$ the 0dvanced property%s &W& button under the Aynamic 8roperties for the form' bringing up the list of potential properties to be made dynamic' as shown in #igure 1.1H.

Figure 1.1#. D!namic Properties for 8ainForm

0ny property chec$ed in this dialog will be read from the .config file. !otice the $ey mapping provided by the dialog after we%ve chosen to ma$e 9pacity dynamic. This mapping is the $ey that will be used in the .config file. 0fter you%ve chosen a dynamic property' three things happen. #irst' a file called app.config is added to your pro<ect. This file will be automatically copied into the output directory of your pro<ect as it is built and will be renamed to match the name of your application' saving you from having to manually $eep .config files up/to/date in @elease and Aebug directories. The second thing that happens is that the contents of app.config will be populated with whatever value is set in the 8roperty Browser for that property. #or e(ample' the following app.config file will be generated for ain#orm.9pacity 3assuming a default opacity value of 14:
=>xml $ersion1"5.?" enco in*1"Win o!s:5@A@">> =con,i*/ration> =a##Settin*s> =3:: <ser a##lication an con,i*/re #ro#erty settin*s *o here.::> =3:: (xam#le: =a 4ey1"settin*Bame" $al/e1"settin*Cal/e".> ::>
<add 6ey1 $ainForm.Jpacity #alue1 B /=

=.a##Settin*s> =.con,i*/ration>

The final thing that happens for each dynamic property is that the *nitiali=e+omponent function is augmented with code to pull in the properties at run time' saving you the need to write that code yourself:
#/&lic MainForm() " 6nitiali7eCom#onent(); -

#ri$ate $oi ...

6nitiali7eCom#onent() "

System.Configuration.'ppSettings"eader configuration'ppSettings 1 new System.Configuration.'ppSettings"eader+./ this.Jpacity 1 ++System.0ou!le.+configuration'ppSettings.KetLalue+ $ainForm.Jpacity - typeof+System.0ou!le..../

...

0s useful as the .config file is' it%s not for everything. *n fact' its usefulness is limited to read/only machinewide application settings because the 0pp5ettings@eader has no matching 0pp5ettingsWriter. *nstead' machinewide or per/user application settings that can be changed' such as the position of the main form between sessions' should be $ept either in a file in an operating systemXprovided special folder or' even better' in a .!"T/ specific place called isolated storage. Both of these are covered in detail' along with a discussion of application lifetime and environment' in +hapter 11: 0pplications and 5ettings.

Resources
The .config file' dynamic properties' files in special folders' and isolated storage all provide data used to control an application%s loo$ and feel' as well as its behavior' while remaining separate from the code itself. 9ne other ma<or place for this $ind of data for applications and controls is resources. 0 reso&rce is a named piece of data bound into the "T" or A77 at build time. #or e(ample' you could set the bac$ground image of a form in your application by loading a bitmap from a file:
#/&lic MainForm() " 6nitiali7eCom#onent();
this.)ac6ground;mage 1 new )itmap+M C3NW;N0JWSNWe!NWallpaperN'&ul.9pg ./

The problem with this code' of course' is that not all installations of Windows will have 0=ul.<pg' and even those that have it may not have it in the same place. "ven if you shipped this picture with your application' a space/conscious user might decide to remove it' causing your application to fault. The only safe way to ma$e sure that the picture' or any file' stays with code is to embed it as a resource. @esources can be conveniently embedded in two ways. 9ne way is to right/clic$ on your pro<ect in 5olution "(plorer' choose 0dd "(isting *tem' and open the file you%d li$e to embed as a resource. The file will be copied into your pro<ect%s directory but is not yet embedded. To embed the file' right/clic$ on it and choose 8roperties' changing Build 0ction from +ontent 3the default4 to "mbedded @esource. To load the resource' many of the .!"T classes' such as Bitmap' provide constructors that ta$e resource identifiers:
#/&lic MainForm() " 6nitiali7eCom#onent();

this.)ac6ground;mage 1 new )itmap+this.KetType+.- '&ul.9pg ./

When embedded as a resource' the name of the resource will be composed of the pro<ect%s default namespace and the name of the file where the resource came from.in this e(ample' y5econd0pp.0=ul.<pg. When the picture is loaded at run time' the first argument is a type that shares the same namespace as that of the embedded resource' and the second argument is the rest of the name.
LHM LHM

Jou can get the default namespace of a +, pro<ect by right/clic$ing on a pro<ect in the 5olution "(plorer and choosing 8roperties N +ommon

8roperties N General N Aefault !amespace.

7uc$ily' if the resource/naming scheme is less than intuitive for you or you%d really li$e to see what the bac$ground image is going to loo$ li$e in the Aesigner' you can set the value of many properties in your forms by using the 8roperty Browser directly' s$ipping the need to write the resource/loading code at all. #or e(ample' to set the bac$ground image for a form' you merely press the &W& button in the 8roperty Browser ne(t to the Bac$ground*mage property and choose the file from the file systemP the data will be read directly into a bundle of resources maintained for that form. This action causes the image to be shown in the Aesigner and the code to be generated that loads the resource at run time:
names#ace MySecon )## " #/&lic class MainForm : System.Win o!s.Forms.Form " #/&lic MainForm() " 6nitiali7eCom#onent(); #ri$ate $oi 6nitiali7eCom#onent() "
System."esources."esource$anager resources 1 new System."esources."esource$anager+typeof+$ainForm../ ... this.)ac6ground;mage 1 +)itmap.resources.KetJ!9ect+ Othis.)ac6ground;mage ./ ... ...

*n this case' instead of using the Bitmap constructor directly' the generated code is using the @esource anager class. This class will load the bundle of resources specific to this form' whether those resources happen to be in the e(ecuting application or in a library assembly with a set of locali=ed resources specific to the current user%s culture settings. *n this way' you can locali=e your forms without changing the code or even recompiling. #or more details about resources' including locali=ation and internationali=ation concerns' see +hapter 10: @esources.

#ialogs

Jou%ve already seen how to create and show forms' but there is a special usage of forms that show as dialogs. 0lthough it%s not always the case' dialogs are typically modal and e(ist to ta$e information from a user before a tas$ can be completed.in other words' a dialog is a form that has a &dialog& with the user. #or e(ample' the 9ptions dialog in #igure 1.1F was created by right/clic$ing on a pro<ect in 5olutions "(plorer and choosing 0dd Windows #orm. *mplementing the form was a matter of e(posing the favorite color setting as a property' dropping the controls onto the form%s design surface' and setting the +ontrolBo( property to false so that it loo$s li$e a dialog. Figure 1.1'. 1 Dialog "o3

Jou can use this form as a modal dialog by calling the 5howAialog method:
$oi $ie!D#tionsMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " MyD#tions9ialo* l* 1 ne! MyD#tions9ialo*(); l*.Fa$oriteColor 1 this.color; i,( dlg.Show0ialog+. 11 0ialog"esult.JP ) " this.color 1 l*.Fa$oriteColor; -

!otice that an instance of the custom class' y9ptionsAialog' is created' but before it%s shown' the initial values are passed in via a property. When the modal 5howAialog method returns' it provides a member of the Aialog@esult enumeration' either 9D or +ancel in this case. 0lthough it%s possible to implement the 9D and +ancel buttons% +lic$ events inside the y9ptionsAialog class' there%s a much easier way to ma$e 9D and +ancel act as they should: Jou set each button%s Aialog@esult property appropriately' and set the y9ptionsAialog form properties 0cceptButton and +ancelButton to refer to the appropriate buttons. *n addition to closing the dialog and returning the result to the caller of 5howAialog' setting these properties enables the "nter and "5+ $eys and highlights the 9D button as the default button on the form. Jou may still feel the need to handle the 9D clic$ event to validate the data typed into the dialog. 0lthough you can do that' Win#orms provides built/in support for validation. By using an "rror8rovider component' along with the -alidating event' you can validate the contents of each control when the user moves focus from that control. #or e(ample' if we

want the user to specify a color with some green in it' we can drop an "rror8rovider component onto the y9ptionsAialog form and handle the -alidating event for the +hange button whenever it loses focus:
$oi chan*eColor+/tton%Cali atin*(o&'ect sen er, Cancel($ent)r*s e) " &yte *reenness 1 chan*eColor+/tton.+ac4Color.E; strin* err 1 / i,( *reenness = Color.Li*htEreen.E ) " err 1 "6Fm sorry, !e !ere *oin* ,or lea,y, lea,y...";
e.Cancel 1 true/

errorPro#iderB.SetError+changeColor)utton- err./

*n the -alidating handler' notice that we set the +ancel"vent0rgs +ancel property to true. This cancels the loss of focus from the control that caused the validating event and stops the dialog from closing. 0lso notice the call to "rror8rovider.5et"rror. When this string is empty' the error provider%s error indicator for that control is hidden. When this string contains something' the error provider shows an icon to the right of the control and provides a tooltip with the error string' as shown in #igure 1.1S. Figure 1.1.. %rrorPro&ider Pro&iding an %rror

The -alidating event handler is called whenever focus is moved from a control whose +auses-alidation property is set to true 3the default4 to another control whose +auses-alidation property is set to true. To let the user cancel your dialog without entering valid data' ma$e sure to set the +ancel button%s +auses-alidation property to false' or else you%ll have pretty frustrated users. "rror8rovider and the -alidating event provide most of what%s needed for basic validation' but more complicated validation scenarios re)uire some custom coding. 5imilarly' because not all dialogs are modal' you%ll need other ways of communicating user settings between your dialog and the rest of your application. #or a discussion of these issues' as well as a list of the standard dialogs and how to use them' you%ll want to read +hapter 3: Aialogs.

#rawing and $rinting

0s nifty as all the built/in controls are and as nicely as you can arrange them on forms using the Aesigner' user controls' and dialogs' sometimes you need to ta$e things into your own hands and render the state of your form or control yourself. #or e(ample' if you need to compose a fancy 0bout bo(' as shown in #igure 1.1I' you%ll need to handle the form%s 8aint event and do the drawing yourself. Figure 1.10. Custom Drawing

The following is the 8aint event/handling code to fill the inside of the 0bout bo(:
/sin* System.9ra!in*; ... $oi )&o/t9ialo*%Gaint(o&'ect sen er, Gaint($ent)r*s e) "
Kraphics g 1 e.Kraphics/

*.Smoothin*Mo e 1 Smoothin*Mo e.)nti)lias; 2ectan*le rect 1 this.Client2ectan*le; int cx 1 rect.Wi th; int cy 1 rect.Hei*ht; ,loat scale 1 (,loat)cy.(,loat)cx; /sin*( LinearEra ient+r/sh &r/sh 1 ne! LinearEra ient+r/sh( this.Client2ectan*le, Color.(m#ty, Color.(m#ty, HA) ) " Color+len &len 1 ne! Color+len (); &len .Colors 1 ne! ColorIJ " Color.2e , Color.Ereen, Color.+l/e -; &len .Gositions 1 ne! ,loatIJ " ?, .A,, 5 -; &r/sh.6nter#olationColors 1 &len ; /sin*( Gen #en 1 ne! Gen(&r/sh) ) " ,or( int x 1 ?; x = cx; x ;1 K ) "
g.0rawCine+peng.0rawCine+peng.0rawCine+peng.0rawCine+penA- * Q scale- c* ? *- A./ A- +c* ? *. Q scale- c* ? *- c* Q scale./ c* ? *- A Q scale- c*- +c* ? *. Q scale./ c* ? *- c* Q scale- c*- * Q scale./

Strin*Format ,ormat 1 ne! Strin*Format(); ,ormat.)li*nment 1 Strin*)li*nment.Center; ,ormat.Line)li*nment 1 Strin*)li*nment.Center; strin* s 1 ")inFt *ra#hics cool>";
g.0rawString+s- this.Font- !rush- rect- format./

!otice the use of the Graphics ob<ect from the 8aint"vent0rgs passed to the event handler. This provides an abstraction around the specific device we%re drawing on. *f we%d li$e to print instead' it%s a matter of getting at another Graphics ob<ect that models the printer. We can do that using the 8rintAocument component and handling the events that it fires when the user re)uests a document to be printed. #or e(ample' we can drag the 8rintAocument component from the Toolbo( onto our 0boutAialog form and use it to implement a 8rint button:
$oi #rint+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " Grint9ialo* l* 1 ne! Grint9ialo*(); l*.9oc/ment 1 #rint9oc/ment5; i,( l*.Sho!9ialo*() 11 9ialo*2es/lt.DL ) " #rint9oc/ment5.Grint(); -

!otice that before we as$ the 8rintAocument component to print' we use the standard 8rintAialog component to as$ the user which printer to use. *f the user presses the 9D button' we as$ the document to print. 9f course' it can%t print on its own. *nstead' it will fire the 8rint8age event' as$ing us to draw each page:
/sin* System.9ra!in*.Grintin*;
... #oid print0ocumentB8PrintPage+o!9ect sender- PrintPageE#ent'rgs e. 4 Kraphics g 1 e.Kraphics/ ... 5

*f you%d li$e to print more than one page' set the 6as ore8ages property of the 8rint8age"vent0rgs class until all pages have been printed. *f you%d li$e to be notified at the beginning and end of each print re)uest as a whole' you%ll want to handle the Begin8rint and "nd8rint events. *f you%d li$e to change settings' such as margins' paper si=e' landscape versus portrait mode' and so on' you%ll want to handle the Yuery8age5ettings event. 0fter you have the 8rintAocument events handled' Win#orms ma$es adding print preview as easy as using the 8rint8review dialog:
$oi #rintGre$ie!+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " #rintGre$ie!9ialo*5.9oc/ment 1 #rint9oc/ment5; #rintGre$ie!9ialo*5.Sho!9ialo*(); -

#or more details of the printing and the drawing primitives used to render state onto a Graphics ob<ect' you%ll want to read +hapter H: Arawing Basics and +hapter I: 8rinting.

#ata Binding
Aatabase/centric applications are fully supported in Win#orms. To get started' you can use 5erver "(plorer to add connections to whatever databases you%d li$e. #or e(ample' #igure 1.18 shows 5erver "(plorer and the tables in a database maintained on a popular Windows developer resource site. Figure 1.12. 1 Data9ase Connection in ,er&er %3plorer

Aragging a table from 5erver "(plorer onto a Aesigner surface creates two components: a connection to connect to the database' and an adapter to shuttle data bac$ and forth across the connection. @ight/clic$ing on the adapter in the Aesigner and choosing Generate Aataset allows you to create a new data set' a Aata5et/derived class specially generated to hold data for the table you originally dragged from 5erver "(plorer. The default General Aataset options will also create an instance of the new data set for you to associate with controls. 0ssociating a source of data' such as a data set' with one or more controls is $nown as data binding. Binding a control to a data source provides for bidirectional communication between the control and the data source so that when the data is modified in one place' it%s propagated to the other. 5everal data bound controls are provided with Win#orms' including 7istBo( and +omboBo(' but of all of them' the AataGrid control is the most fle(ible. #igure 1.19 shows a form with a data grid bound to the data set that%s already been created. Figure 1.14. 1 Data:rid "ound to a Data ,et

When there%s a data set on the form' it%s easy to bind the data grid to it. Jou set the data grid%s Aata5ource property in the 8roperty Browser and fill the data set when the form is loaded. To do this' you use the data adapter:
$oi 9o!nloa sForm%Loa (o&'ect sen er, ($ent)r*s e) "
s>l0ata'dapterB.Fill+downloads0ataSetB./

This is only a scratch on the surface of what can be done with data binding in general and the data grid specifically. #or more information' read +hapter 1>: Aata 5ets and Aesigner 5upport' and +hapter 13: Aata Binding and Aata Grids.

%ultithreaded &ser 'nterfaces


Because the Aesigner provides so much functionality via drag and drop and the 8roperty Browser' it won%t be long before you get to the meat of your programming chores. 0nd when that happens' you%re bound to run into a tas$ that ta$es long enough to annoy your users if you ma$e them wait while it completes.for e(ample' printing or calculating the last digit of pi. *t%s especially annoying if your application free=es while an operation ta$es place on the 1* thread' showing a blan$ s)uare where the main form used to be and leaving your users time to consider your competitors. To build applications that remain responsive in the face of long/running operations' you need threads. 0nd even though Win#orms doesn%t provide threading support' .!"T provides many threading options that Win#orms integrates well with' once you%re familiar with how to do it appropriately. To e(plore your threading options' read +hapter 1H: ultithreaded 1ser *nterfaces.

#e"lo(ment

When you%ve got your application <ust how you li$e it' all arranged and responsive and fancy' you%ll want to share it. Jou have several options. Jou can create an archive of your files and send them as an e/mail to your friends and family' from which they can e(tract the files into the folder of their choice and run your application. 9r' if you li$e' you can use the -5.!"T 5etup 8ro<ect template to create a pro<ect that produces a icrosoft 5etup *nformation 3 5*4 file containing your application%s files. @ecipients can use this 5* file to install the application into the folder of their choice. 9f course' the problem with both of these techni)ues is that as soon as you share your application' that%s when you find the crushing bug that' when the moon is full and the sun is in the house of 9rion' causes bad' bad things to happen. When problems come up' you need to remember who received your application so that you can let them $now to install the new version before the e(isting version formats +: or resets your boss%s inesweeper high scores. 9f course' all of this e(plains why your *T department mandates that all internal applications be Web applications. The Web application deployment model is so simple' there is no deployment. *nstead' whenever users surf to the Web application in the morning' they get the version that the *T department uploaded to the server the night before. That deployment model has never been available out of the bo( for Windows applications. 1ntil now. 0t this point' you should stop reading and try the following: 1. 1sing the Windows 0pplication pro<ect template' create a pro<ect called Aeployment#un. >. Arag and drop some controls from the Toolbo(' and compile your application. 3. *n the shell e(plorer' navigate to your Aeployment#unObin folder and right/clic$ on the Aebug folder' choosing 8roperties. H. +hoose Web 5haring and turn it on' using Aeployment#un as the name of the share. F. 1sing 5tart N @un' enter the following 1@7: http:CClocalhostCAeployment#unCAeployment#un.e(e Jou%ve <ust used the no(to&c" deplo#ment feature of .!"T to deploy your Win#orms application li$e a Web application' e(cept that it%s a real Windows application complete with full user control over the frame' the toolbar' the menu bar' the status bar' shortcut $eys' and so on. 0ny libraries that are re)uired to ma$e your application run' such as custom or third/party controls' will be downloaded from the same virtual directory that the application came from. 0nd' <ust li$e a Web application' by default your Win#orms application is running in a security sandbo(. *n the case of no/touch deployment applications' the sandbo( is provided by .!"T )ode Access Sec&rit#' which dictates that the permissions of your code are limited according to where the code came from' such as across the intranet. This is in contrast to classic Windows security' where code is awarded permissions based on who launched the application' an approach that doesn%t wor$ very well when everyone seems to run as 0dministrator.

#or the details of deploying Win#orms applications and controls over the Web.including hosting Win#orms controls on a Web page' application deployment' versioning' caching' and most importantly' security.turn to +hapter 1F: Web Aeployment.

%oving from %FC


Because +, is a member of the + family of languages' you may be a former +;; programmer' and even a former icrosoft #oundation +lasses 3 #+4 programmer. #+ was a wonderful framewor$ for building document/centric applications. "ven today' #+ provides some features that Win#orms still doesn%t have' such as command handling and document management. 9f course' Win#orms also has plenty of features that #+ never had' including anchoring and Web deployment. *f you%d li$e an overview of the differences between Win#orms and #+ and a discussion of how to deal with the lac$ of some features' you should read 0ppendi( 0: oving from #+.

Where Are We?


Win#orms provides a great deal of functionality' as this chapter has shown. !ot only does it give you the basics needed to build applications' forms' controls' resources' and dialogs' but it also provides advanced features such as anchoring' doc$ing' user controls' print preview' data binding' and Web deployment' along with wi=ards to get you started and a #orm Aesigner to let you visually develop your loo$ and feel. 0nd' where Win#orms stops' the rest of the .!"T #ramewor$ steps in to provide drawing' ob<ect seriali=ation' threading' security' and tons of other bits of functionality in thousands of classes and components. 9ne boo$ can%t cover all of those' but *%ll show you what you need to $now to write real Win#orms applications and controls in the rest of this one.

Chapter 2. Forms
*n a technology named &Windows #orms'& you can e(pect the form to play a critical role. This chapter e(plores the basics' including the display of forms' the lifetime of a form' form si=e and location' non/client form adornments' menus' and child controls' as well as advanced topics such as form transparency' nonrectangular forms' control layout' A* forms' and visual inheritance. 0nd if that%s not enough' +hapter 3 is all about using forms as dialogs. 5ome of the material in this chapter.notably child control topics such as anchoring and doc$ing.applies e)ually well to user controls as it does to forms. 0lthough some material is common to both topics' topics that are more commonly associated with forms are covered in this chapter' and topics more commonly associated with controls are covered in +hapter 8: +ontrols.

Showing Forms
0ny form.that is' any class that derives from the #orm base class.can be shown in one of two ways. 6ere' it is shown modelessly:
$oi &/tton5%Clic4(o&'ect sen er, System.($ent)r*s e) " )notherForm ,orm 1 ne! )notherForm();
form.Show+./ // Show form modelessly

6ere' a form is shown modally:


$oi &/tton5%Clic4(o&'ect sen er, System.($ent)r*s e) " )notherForm ,orm 1 ne! )notherForm();
form.Show0ialog+./ // show form modally

#orm.5how shows the new form modelessly and returns immediately without creating any relationship between the currently active form and the new form. This means that the e(isting form can be closed' leaving the new form behind. #orm.5howAialog' on the other hand' shows the form modally and does not return control until the created form has been closed' either by using the e(plicit +lose method or by setting the Aialog@esult property 3more on this in +hapter 3: Aialogs4.
L1M L1M

6owever' if the main form is closed' 0pplication.@un will close all other forms and return.

*wner and *wned Forms


0s the 5howAialog method shows the new form' it uses the currently active form as the new form%s logical owner. 0n o ner is a window that contributes to the behavior of the
L>M

o ned form. #or e(ample' if an owner has a modal child' then activating the owner' such as by using the 0lt;Tab tas$/switching $eystro$e' activates the owned form. *n the modeless case' when the owner form is minimi=ed or restored' so is the owned form. 0lso' an owned form is always shown on top of an owner form' even if the owner is currently active' as if the user has clic$ed on the owner' as shown in #igure >.1.
L>M

The form shown using 5howAialog will have an 9wner property set to null if no owner is provided e(plicitly. 6owever' the user interaction

behavior for modal forms is the same whether or not the 9wner property is set.

Figure 2.1. *wner)*wned /elationship

When a form is activated modelessly via the 5how method' by default the new form does not have an owner. 5etting the owner of a modeless form is a matter of setting the new form%s 9wner property:
$oi &/tton5%Clic4(o&'ect sen er, System.($ent)r*s e) " )notherForm ,orm 1 ne! )notherForm();
form.Jwner 1 this/ // Esta!lish owner/owned relationship

,orm.Sho!(); -

*n the modal case' in spite of the implicit owner/owned relationship that Win#orms creates' the modal form will have a null 9wner property unless the 9wner property is set e(plicitly. Jou can do this by setting the 9wner property <ust before the call to 5howAialog or by passing the owner form as an argument to the 5howAialog override that ta$es an *Win3>Window parameter:
L3M L3M

*Win3>Window is an interface e(posed by 1* ob<ects in Win#orms that e(pose a Win3> 6W!A via the *Win3>Window.6andle property.

$oi &/tton5%Clic4(o&'ect sen er, System.($ent)r*s e) " )notherForm ,orm 1 ne! )notherForm();
form.Show0ialog+this./ // Passing the owner as an argument

0n owner form can enumerate the list of forms it owns using the 9wned#orms collection:
$oi &/tton5%Clic4(o&'ect sen er, System.($ent)r*s e) " )notherForm ,orm 1 ne! )notherForm(); ,orm.D!ner 1 this;

,orm.Sho!();
foreach+ Form ownedForm in this.JwnedForms . 4

Messa*e+ox.Sho!(o!ne Form.0ext);
5

Jou may have noticed that in addition to an optional owner' a form can have an optional parent' as e(posed via the 8arent property. 0s it turns out' normal forms have a 8arent property that is always null. The one e(ception to this rule is A* child forms' which * discuss later. 1nli$e the owner/owned relationship' the parent/child relationship dictates clipping.that is' a child%s edge is clipped to the edge of the parent' as shown in #igure >.>. Figure 2.2. 1 Child $ist"o3 Control Clipped to the Client 1rea of +ts Parent Form

The parent/child relationship is reserved for parent forms 3or parent container controls4 and child controls 3with the e(ception of A*' which is discussed later4.

Form )ifetime
0lthough the user can%t see a form until either 5how or 5howAialog is called' a form e(ists as soon as the ob<ect is created. 0 new form ob<ect wa$es up in the ob<ect%s constr&ctor' which the runtime calls when an ob<ect is first created. *t%s during the constructor that *nitiali=e+omponent is called and therefore when all the child controls are created and initiali=ed. *t%s a bad idea to put custom code into the *nitiali=e+omponent function because the Aesigner is li$ely to throw it away. 6owever' if you%d li$e to add other controls or change anything set by the *nitiali=e+omponent method' you can do that in the constructor. *f the initial form implementation was generated by one of the -5.!"T wi=ards' you%ll even have a helpful comment indicating where the Aesigner thin$s that you should add your initiali=ation code:
#/&lic Form5() "

.. 2e8/ire ,or Win o!s Form 9esi*ner s/##ort 6nitiali7eCom#onent();


// TJ0J3 'dd any constructor code after ;nitiali&eComponent call

.. ) in* a control +/tton another+/tton 1 ne! +/tton(); this.Controls.) (another+/tton); .. Chan*in* a #ro#erty this.0ext 1 "Somethin* Bot Lno!n )t 9esi*n 0ime";

When #orm.5how or #orm.5howAialog is called' that%s the form%s cue to show itself as well as all its child controls. Jou can be notified that this has happened when the code handles the 7oad event:
$oi 6nitiali7eCom#onent() " ...
this.Coad 71 new System.E#ent,andler+this.FormB8Coad./

... #oid FormB8Coad+o!9ect sender- System.E#ent'rgs e. 4

Messa*e+ox.Sho!("Welcome to Form53"); -

The 7oad event is useful for doing any final initiali=ation right before a form is shown. 0lso' the 7oad event is a good place to change the -isible property and the 5how*nTas$bar property if you%d li$e the form to start as hidden:
LHM LHM

5tarting a form as hidden is useful for forms that need to be running but that shouldn%t show themselves right away. 0n e(ample is a form with a

notify icon in the tas$bar.

$oi

Form5%Loa (o&'ect sen er, ($ent)r*s e) "

// 0on:t show this form this.Lisi!le 1 false/ this.Show;nTas6!ar 1 false/

When a form is shown' it will become the active form. *t%s the active form that receives $eyboard input. 0n inactive form is made active when users clic$ on it or otherwise indicate to Windows that they would li$e it to be active' such as by using 0lt;Tab to switch to it. Jou can ma$e an inactive form active programmatically by using the #orm.0ctivate method. When a form is made active' including when the form is first loaded' it receives the 0ctivated event:
LFM LFM

9lder implementations of Win3> allowed an application to set itself active on top of the currently active window' something that could be pretty odern implementations of Win3> allow an application to set a window as active only if another window in that application is

annoying.

currently active. 5ome of these implementations flash a bac$ground application%s button on the shell%s tas$bar to indicate that the application would li$e your attention.

$oi 6nitiali7eCom#onent() " ...


this.'cti#ated 71 new System.E#ent,andler+this.FormB8'cti#ated./

... #oid FormB8'cti#ated+o!9ect sender- System.E#ent'rgs e. 4

this.*ame.2es/me();
5

*f an application has a form that is the currently active window as far as the operating system is concerned' you can discover that using the #orm.0ctive#orm static method. *f #orm.0ctive#orm is null' it means that none of your application%s forms is currently active. To trac$ when a form deactivates' handle the Aeactivate event:
$oi 6nitiali7eCom#onent() " ... ...

this.0eacti#ate 71 new System.E#ent,andler+this.FormB80eacti#ate./

#oid FormB80eacti#ate+o!9ect sender- System.E#ent'rgs e. 4 5

this.*ame.Ga/se();

*f' in addition to controlling whether or not a form is active' you%d li$e to control its visibility' either you can use the 6ide and 5how methods' which set the -isible property' or you can set the -isible property directly:
$oi this.,ide+./ // Set Lisi!le property indirectly this.Lisi!le 1 false/ // Set Lisi!le property directly

hi e+/tton%Clic4(o&'ect sen er, System.($ent)r*s e) "

0s you might e(pect' there is an event that you can handle as your form flic$ers in and out of visual reality. *t%s called -isible+hanged. The 0ctivated' Aeactivate' and -isible+hanged events are all handy for restarting and pausing activities that re)uire user interaction or attention' such as in a game. To stop an activity altogether' you%ll want to handle the +losing or the +losed event. The +losing event can be canceled if users change their minds:
$oi Form5%Closin*(o&'ect sen er, Cancel($ent)r*s e) " 9ialo*2es/lt res 1 Messa*e+ox.Sho!( ")&ort yo/r *ame>", "Eame 6n Gro*ress", Messa*e+ox+/ttons.MesBo); e.Cancel 1 (res 11 9ialo*2es/lt.Bo); $oi Form5%Close (o&'ect sen er, ($ent)r*s e) " Messa*e+ox.Sho!("Mo/r *ame !as a&orte "); -

!otice that during the +losing event the handler can set the +ancel"vent0rgs.+ancel property to true' canceling the closing of the form. This is also the best place to seriali=e a

form%s visible properties' such as si=e and location' before Windows closes the form. 9n the other hand' the +losed event is merely a notification that the form has already gone away.

Form Si*e and )ocation


Auring its lifetime' the form is li$ely to ta$e up space at some location. The initial location for the form is governed by the 5tart8osition property' which can have one of several values from the #orm5tart8osition enumeration:
en/m FormStartGosition " CenterGarent, CenterScreen, Man/al, Win o!s9e,a/lt+o/n s, Win o!s9e,a/ltLocation, .. -

e,a/lt

These values have the following behavior:

Windows0efaultCocation .

The form%s starting position will be determined by Windows' which will pic$ a location staggered from the upper/left corner of the screen toward the lower right in an attempt to ma$e sure that new windows neither cover each other nor fall off the screen. The form will be shown with whatever the 5i=e property was set to in the Aesigner. Windows0efault)ounds. Windows will be as$ed to determine a default si=e as well as location. CenterScreen. The form will be centered on the des!top' that area not ta$en up by the shell tas$bar and the li$e. CenterParent . The form will be centered over the owner 3or the currently active form' if there%s no owner4 when 5howAialog is used. *f 5how is used' the behavior is that of WindowsAefault7ocation. $anual . 0llows you to set the initial location and the si=e of the form without any Windows intervention.

The si=e and location of the form are e(posed via the 5i=e and 7ocation properties' of type 5i=e and 8oint' respectively 3both from the 5ystem.Arawing namespace4. 0s a shortcut' the properties of the si=e of a form are e(posed directly via the 6eight and Width form properties' and those of the location are e(posed via the 7eft' @ight' Top' and Bottom properties. #igure >.3 shows the basic si=e and location properties on a form. Figure 2. . The Des5top$ocation, $ocation, Client,i-e, and ,i-e Properties

When the upper/left corner of a form changes' that%s a move' which can be handled in the ove or 7ocation+hanged event handler. When the width or height of a form changes' that%s a resi$e' which can be handled in the @esi=e or 5i=e+hanged event handler. 5ometimes one gesture of the mouse can cause all move and si=e events to happen. #or e(ample' resi=ing a form by dragging the top/left corner would change the location and the si=e of the form.
LSM LSM

Why are there two events for move and two more for resi=e? The T((+hanged events are so named to be consistent with data binding. The ove and @esi=e events are more familiar to -isual Basic programmers and are $ept for their benefit. Both events are functionally e)uivalent.

The location of the form is in absolute screen coordinates. *f you%re interested in the location of the form relative to the des$top.so that' for e(ample' your form%s caption never appears underneath the shell%s tas$bar.then even if it%s on the top edge' as shown in #igure >.3' you can use the Aes$top7ocation property. 6ere%s an e(ample:
$oi FormN%Loa (o&'ect sen er, ($ent)r*s e) " .. Co/l en /# /n er the shellFs tas4&ar this.Location 1 ne! Goint(5, 5);
// Will always !e in the des6top this.0es6topCocation 1 new Point+B- B./

.. ) sim#ler ,orm o, the #rece in* line this.Set9es4to#Location(5, 5); -

7ocations are e(pressed via the 8oint structure from the 5ystem.Arawing namespace' the interesting parts of which are shown here:
str/ct Goint " .. Fiel s #/&lic static rea only Goint (m#ty; .. Constr/ctors #/&lic Goint(int x, int y);

.. Gro#erties #/&lic &ool 6s(m#ty " *et; #/&lic int O " *et; set; #/&lic int M " *et; set; .. Metho s #/&lic static Goint Ceilin*(GointF $al/e); #/&lic $oi D,,set(int x, int y); #/&lic static Goint 2o/n (GointF $al/e); #/&lic $irt/al strin* 0oStrin*(); #/&lic static Goint 0r/ncate(GointF $al/e);

The 8oint# structure is very similar to the 8oint structure' but 8oint# is used in drawing applications when more precise floating point measurements are re)uired. 5ometimes you%ll need to convert from a 8oint to a 8oint# ob<ect to be able to call some methods or set some properties. Jou can do so without any e(tra effort:
.. Can con$ert irectly ,rom Goint to GointF Goint #t5 1 ne! Goint(5?, @?);
PointF ptG 1 ptB/ // Rields PointF+BA.Af- GA.Af.

6owever' because floating point numbers contain e(tra precision that will be lost in the conversion' you%ll need to be e(plicit about how to convert from a 8oint# to a 8oint ob<ect using the static Truncate' @ound' and +eiling methods of the 8oint class:
.. Bee to &e ex#licit !hen con$ertin* ,rom a GointF to a Goint GointF #t5 1 ne! GointF(5.@,, 5.P,);
Point ptG 1 Point.Truncate+ptB./ // Rields Point+B- B./ Point pt@ 1 Point."ound+ptB./ // Rields Point+B- G./ Point ptS 1 Point.Ceiling+ptB./ // Rields Point+G- G./

The si=e of a window is reflected in the 5i=e property' also from 5ystem.Arawing 35i=e also has a 5i=e# counterpart and provides the same capabilities for conversion4:
str/ct Si7e " .. Fiel s #/&lic static rea only Si7e (m#ty; .. Constr/ctors #/&lic Si7e(int !i th, int hei*ht); .. Gro#erties #/&lic int Hei*ht " *et; set; #/&lic &ool 6s(m#ty " *et; #/&lic int Wi th " *et; set; .. Metho s #/&lic static Si7e Ceilin*(Si7eF $al/e); #/&lic $irt/al &ool (8/als(o&'ect o&'); #/&lic static Si7e 2o/n (Si7eF $al/e); #/&lic $irt/al strin* 0oStrin*(); #/&lic static Si7e 0r/ncate(Si7eF $al/e); -

0lthough the 5i=e property represents the si=e of the entire window' a form isn%t responsible for rendering all of its contents. The form can have edges' a caption' and scrollbars' all of which are drawn by Windows. The part that the form is responsible for is the +lient5i=e' as shown in #igure >.3. *t%s useful to save the +lient5i=e property between application sessions because it%s independent of the current adornment settings the user has established. 5imilarly' resi=ing the form to ma$e sure there%s enough space to render your form%s state is often related to the client area of the form and not to the si=e of the form as a whole:
$oi this.ClientSi&e 1 new Si&e+BAA- BAA./ // Calls SetClientSi&eCore this.SetClientSi&eCore+BAA- BAA./

Form@%Loa (o&'ect sen er, ($ent)r*s e) "

0 @ectangle combines a 8oint and a 5i=e and also has a @ectangle# counterpart. 5tructure @ectangleThe Bounds property gives a rectangle of the form relative to the screen' whereas the Aes$topBounds property is a rectangle relative to the des$top for top/level windows 3and not for child windows4. The +lient@ectangle property is a rectangle relative to the form itself' describing the client area of the form. 9f the three' +lient@ectangle tends to be the most used' if for no other reason than to describe which area to use when drawing:
$oi Form5%Gaint(o&'ect sen er, Gaint($ent)r*s e) " Era#hics * 1 e.Era#hics;
g.FillEllipse+)rushes.Rellow- this.Client"ectangle./ g.0rawEllipse+Pens.0ar6)lue- this.Client"ectangle./

0lso' it%s sometimes necessary to convert a point that%s relative to the screen to one that%s relative to the client or vice versa. #or e(ample' the 6elp@e)uest event' generated when the user clic$s on the 6elp button and then clic$s on a control' is sent to the handler in screen coordinates. 6owever' to determine which control was clic$ed on re)uires the mouse position in client coordinates. Jou can convert between the two coordinate systems by using 8ointTo5creen and 8ointTo+lient:
$oi Form5%Hel#2e8/este (o&'ect sen er, Hel#($ent)r*s e) "
// Con#ert screen coordinates to client coordinates Point pt 1 this.PointToClient+e.$ousePos./

.. Loo4 ,or control /ser clic4e on ,oreach( Control control in this.Controls ) " i,( control.+o/n s.Contains(#t) ) " Control controlBee in*Hel# 1 control; ... &rea4; -

To translate an entire rectangle between screen and client coordinates' you can also use @ectangleTo5creen and @ectangleTo+lient.

/estricting Form ,i-e


9ften our careful control layouts or rendering re)uirements dictate a certain minimum amount of space. 7ess often' our forms can%t be made to ta$e advantage of more than a certain amount of space 3although anchoring and doc$ing' described later' should help with that4. "ither way' it%s possible to set a form%s minimum or ma(imum si=e via the inimum5i=e and a(imum5i=e properties' respectively. The following e(ample sets a fi(ed height of >00' a minimum width of 300' and a ma(imum width so large as to be unlimited:
$oi Form@%Loa (o&'ect sen er, ($ent)r*s e) " .. min !i th is N??, min hei*ht is @??
this.$inimumSi&e 1 new Si&e+@AA- GAA./

.. max !i th is /nlimite , max hei*ht is @??


this.$a*imumSi&e 1 new Si&e+int.$a*Lalue- GAA./

!otice that the code uses the ma(imum value of an integer to specify that there is no effective ma(imum width on the form. Jou may be tempted to use =ero for this value instead' but if either the Width or the 6eight property of the 5i=e used to set the minimum or ma(imum is non=ero' then both values are used. This would set the ma(imum si=e of your form to =ero instead of &no ma(imum.& 9ne other setting that governs a form%s si=e and location is Window5tate' which can be one of the values from the #ormWindow5tate enumeration:
en/m FormWin o!State " Maximi7e , Minimi7e , Bormal, .. Form.Win o!State -

e,a/lt $al/e

By default' the Window5tate is set to !ormal' which means that it%s not ma(imi=ed to ta$e up the entire des$top' nor is it minimi=ed so that none of the form shows at all and only a button is shown in the tas$bar. Jour program can get or set this property at will to manage the state of your form. 6owever' if you%re saving the si=e and location of your form between application sessions' you may decide to reset the Window5tate to !ormal so that the si=e being saved represents the si=e in the normal state and not the minimi=ed or ma(imi=ed si=e:
$oi Form@%Closin*(o&'ect sen er, Cancel($ent)r*s e) " .. Ca#t/re the #ro#erties &e,ore the ,orm is *one
FormWindowState state 1 this.WindowState/ this.WindowState 1 FormWindowState.Normal/ Point location 1 this.Cocation/ Si&e si&e 1 this.ClientSi&e/

.. ... sa$e state, location an si7e #ro#erties &et!een sessions ... .. ... restore #ro#erties in Loa e$ent ... -

#or a description of how and where to $eep application settings between sessions' read +hapter 11: 0pplications and 5ettings.

;)*rder
0nother location property that you may let your users influence or $eep between sessions is the Top7evel property. 5o far *%ve discussed location in terms of ( and y. 6owever' as the user switches between windows' Windows also <uggles the $(order' which dictates which windows are drawn on top of one another. #urthermore' =/order is split into two tiers. !ormal windows are drawn lowest =/order to highest' front to bac$. 0bove all the normal windows are the topmost windows' which are also drawn relative to each other' lowest =/order to highest' but no matter the =/order' are always drawn on top of any normal window. #or an e(ample of a topmost window' pressing +trl;5hift;"5+ under many versions of Windows will bring up Tas$ anager. By default' it%s a topmost window and always draws on top of normal windows' whether or not it is the active window. Jou can change this behavior 3* always do4 by unchec$ing the 9ptions N 0lways 9n Top setting. *f Tas$ anager were implemented using Win#orms' it would implement this feature by toggling the Top ost property on its main form.

Form Adornments
*n addition to si=e and location' forms have a number of properties that manage various other aspects of their appearance as well as corresponding behavior. The following settings govern the non(client adornments of a form: those parts of a form outside the client area that are drawn by Windows.

sets whether the form has a border' whether it can be resi=ed' and whether it has a normal/si=e or small caption. Good forms and dialogs leave the default value of 5i=able. 0nnoying dialogs change this property to one of the nonsi=able options. Generally' programmers choose nonsi=able options because of fear of control/layout issues' but Win#orms handles that nicely' as * discuss later in this chapter.
Form)orderStyle

*n addition' there are two tool window styles.one fi(ed and one si=able.for use in building floating toolbar/style windows.

is a Boolean determining whether or not the icon on the upper left of the form as well as the close button on the upper right are shown. *f this property is set to false' neither left/clic$ing on the upper/left corner of the form nor right/clic$ing on the caption will show the 5ystem menu. 5imilarly' when +ontrolBo( is false' the a(imi=eBo( and inimi=eBo( properties will be ignored' and those buttons will not be shown. This property defaults to true but is often set to false for modal dialogs.
Control)o*

The $a*imi&e)o* and $inimi&e)o* properties determine whether the ma(imi=e and minimi=e buttons are shown on the form%s caption. These properties default to true but are often set to false for modal dialogs. The ,elp)utton property shows the )uestion mar$ button ne(t to the close button in the upper right' but only if +ontrolBo( is set to true and a(imi=eBo( and inimi=eBo( are both set to false. This property defaults to false but is often set to true for modal dialogs. When the user clic$s on the help button and then somewhere else on the form' the 6elp@e)uested event is fired for the form to provide the user with help. Whether the 6elpButton property is true or false' the 6elp@e)uested event is always fired when the user presses #1. The ;con property determines the image used as the icon for the form. The Si&eKripStyle property allows values from the 5i=eGrip5tyle enumeration: 0uto' 6ide' or 5how. 0 si$e grip is the adornment on the lower/right corner of a window that indicates that it can be resi=ed. The default is 0uto and indicates the si=e grip in the lower/right corner &if needed'& depending on the form%s #ormBorder5tyle property. The 0uto setting <udges the si=e grip needed if the form is si=able and is shown modally. 0lso' if the form has a 5tatusBar control' the form%s 5i=eGrip5tyle is ignored in favor of the 5i=ingGrip Boolean property on the status bar control itself. Show;nTas6!ar is a Boolean governing whether the form%s Te(t property should appear in a button on the shell tas$bar. This property defaults to true but is often set to false for modal forms.

0lthough most of the properties are independent of each other' not all of these combinations will wor$ together. #or e(ample' when the #ormBorder5tyle is set to either of the tool window settings' no ma(imi=e or minimi=e bo( is shown' regardless of the value of the a(imi=eBo( and inimi=eBo( properties. "(perimentation will reveal what wor$s and what doesn%t.

Form Trans"arenc(
*n addition to the properties that specify how the non/client area of a form are rendered by Windows' the #orm class provides a set of properties that allow you to change the appearance of the form as a whole' including ma$ing it partially transparent or removing pieces of the form altogether. The property that governs transparency of the entire form is called 9pacity and defaults to 1.0' or 100Z opa)ue. 0 value between 0.0 and 1.0 denotes a degree of opacity using the alpha/blending support in more modern versions of Windows' where any number less than 1.0 results in a form that is partially transparent. 9pacity is mostly a parlor tric$' but it%s $ind of fun for ma$ing top/level windows less annoying than they would normally be' as shown here:
LIM LIM

0lpha/blending is the blending of partially transparent elements together based on an alpha value denoting their level of transparency.

$oi

6nitiali7eCom#onent() "

... this.Jpacity 1 A.H/

this.0ext 1 "D#acity 1 ?.A"; this.0o#Most 1 tr/e; ...

$oi D#acityForm%)cti$ate (o&'ect sen er, ($ent)r*s e) " timer5.(na&le 1 tr/e; $oi $oi D#acityForm%9eacti$ate(o&'ect sen er, ($ent)r*s e) " timer5.(na&le 1 ,alse;
this.Jpacity 1 A.H/

timer5%0ic4(o&'ect sen er, ($ent)r*s e) "

if+ this.Jpacity < B.A . this.Jpacity 71 A.B/

this.0ext 1 "D#acity 1 " ; this.D#acity.0oStrin*();

this.0ext 1 "D#acity 1 " ; this.D#acity.0oStrin*(); -

This e(ample shows code from a top/level form whose 9pacity property starts at F0Z. When the form is activated' it starts a timer that increases the 9pacity by 10Z on each tic$' giving a nice &fade in& effect' as shown in #igure >.H. When the form is deactivated' it is set to F0Z opa)ue again' ma$ing it available for viewing and clic$ing but hopefully not obscuring too much. Figure 2.#. *pacit!

(onrectangular Forms

9pacity affects the transparency of the entire form. *t%s also possible to change the shape of the form by ma$ing parts of the form completely transparent. 9ne way to do this is with the TransparencyDey property' which designates a color to use in mar$ing transparent pi(els. When a pi(el on the form is supposed to be drawn with the transparent $ey color' that pi(el will instead be removed from the form' in two senses: The pi(el will not be drawn' and clic$ing on that spot on the form will actually result in a clic$ on what%s showing through from underneath. #or e(ample' setting the TransparencyDey property to the same as the Bac$+olor property of the form will cause a form to lose its bac$ground 3as well as anything else drawn with that color4' as shown in #igure >.F. Figure 2.'. Form ,hown in Front of (otepad with Transparenc!<e! ,et to "ac5Color

The novelty of the form shown in #igure >.F seems limited until you combine it with #ormBorder5tyle.!one' which removes the non/client area altogether' as shown in #igure >.S. Figure 2... Transparenc!<e! Com9ined with Form"order,t!le.(one

The combination of a transparent color to erase the form%s bac$ground and the removal of the form border yields a nonrectangular window' which is all the rage with the $ids these days. The transparency $ey color is used to create a region that describes the visible area of the form to Windows. 0s easy as setting TransparencyDey is' you need to be careful with it. #or e(ample' you need to choose a color that you $now won%t appear in the parts of your form that you need to show' or else they%ll be made transparent' too. 0lso' when using the TransparencyDey' you must calculate the region each time the form is drawn. 0nd most importantly' TransparencyDey re)uires certain capabilities of the video driver that are often missing' causing it to fail completely. 5o instead of using TransparencyDey' you may want to set the form%s @egion property directly. This approach is slightly less convenient but much more robust. @egions are covered in detail in +hapter S: 0dvanced Arawing' but here%s an e(ample of using an ellipse as the form%s region:
/sin* System.9ra!in*.9ra!in*@9; $oi Set(lli#se2e*ion() " .. )ss/me: this.Form+or erStyle 1 Form+or erStyle.Bone 2ectan*le rect 1 this.Client2ectan*le;
using+ KraphicsPath path 1 new KraphicsPath+. . 4 path.'ddEllipse+rect./ this."egion 1 new "egion+path./

#oid TransparentForm8Coad+o!9ect sender- E#ent'rgs e. 4 SetEllipse"egion+./ 5 #oid TransparentForm8Si&eChanged+o!9ect sender- E#ent'rgs e. 4 SetEllipse"egion+./ 5

!otice that our code sets the region both when the form is loaded and whenever the form is resi=ed. 6owever' as careful as we are to handle resi=ing' with the caption and the edges on the form missing' there%s no way for the user to actually move or resi=e the form. When that%s the case' you%re on the hoo$ to implement moving and resi=ing yourself. 6ere%s an e(ample of using the mouse events to move the form around when the user clic$s in the client area of the form:
Goint o!nGoint 1 Goint.(m#ty;

#oid TransparentForm8$ouse0own+o!9ect sender- $ouseE#ent'rgs e. 4

i,( e.+/tton 31 Mo/se+/ttons.Le,t ) ret/rn; o!nGoint 1 ne! Goint(e.O, e.M);

#oid TransparentForm8$ouse$o#e+o!9ect sender- $ouseE#ent'rgs e. 4

i,( o!nGoint 11 Goint.(m#ty ) ret/rn; Goint location 1 ne! Goint( this.Le,t ; e.O : o!nGoint.O, this.0o# ; e.M : this.Location 1 location;

o!nGoint.M);

#oid TransparentForm8$ouseIp+o!9ect sender- $ouseE#ent'rgs e. 4

i,( e.+/tton 31 Mo/se+/ttons.Le,t ) ret/rn; o!nGoint 1 Goint.(m#ty;


5

When the user clic$s on the client area of the form' the ouseAown event is fired' and we handle this event by caching the point on the screen where the user clic$ed. When the user moves the mouse' the ouse ove event is fired' and we use that to move the form based on the difference between the current mouse location and the point where the user first clic$ed. #inally' when the user releases the mouse button' the ouse1p event fires' which we use to stop the move. Jou%d need something similar to implement resi=ing. The details of mouse events' as well as $eyboard events' are covered in +hapter 8: +ontrols.

Form %enus
0s interesting as forms themselves are.with their lifetime' adornments' transparency settings' and input options.they%re all the more interesting with controls on them. But before we get into managing a form%s controls in general' we need to ta$e a )uic$ loo$ at menus' which have special support in Win#orms. !ot only does -5.!"T have a special enu Aesigner 3as shown in #igure >.I4' but also' unli$e every other control' a #orm can show only one main menu at a time' as stored in the #orm. enu property 3which is of type ain enu4. Figure 2.0. The =,.(%T 8enu Designer

0lthough you are limited to a single main menu showing on a form at a time' you can switch menus at run time to your heart%s content by setting the #orm. enu property:
$oi $oi $oi this.$enu 1 this.main$enuG/ this.$enu 1 this.main$enuB/

sho!Men/5+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

sho!Men/@+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " sho!BoMen/+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

this.$enu 1 null/

*n fact' if you drag multiple ain enu components from the Toolbo( onto the form' you can select each one and edit it separately' but you%ll need to set the form%s enu property to pic$ which one to start with. !o matter which menu you choose to designate as the form%s main menu 3or even if you choose none at all4' menus themselves are only containers for items modeled via the enu*tem class. *n fact' menus and menu items are both containers. The ain enu class has a enu*tems collection that contains =ero or more enu*tem ob<ects. This ma$es up the list of items across the top of the menu' such as #ile' "dit' and 6elp. "ach of these enu*tem ob<ects in turn has its own enu*tems collection' which contains the ne(t level of items.for e(ample' #ile N 5ave' #ile N @ecent #iles' #ile N "(it. 0nything below that level shows up as a cascading menu of items' such as #ile N @ecent #iles N foo.t(t. The enu Aesigner ta$es the menu structure you lay out and generates the code to populate the menu items in *nitiali=e+omponent. #or e(ample' using the Aesigner to create the #ile N "(it and 6elp N 0bout menu items as shown in #igure >.I results in code that loo$s li$e this 3and really ma$es you appreciate the Aesigner4:
#ri$ate $oi
this.main$enuB 1 new System.Windows.Forms.$ain$enu+./ this.file$enu;tem 1 new System.Windows.Forms.$enu;tem+./ this.fileE*it$enu;tem 1 new System.Windows.Forms.$enu;tem+./ this.help$enu;tem 1 new System.Windows.Forms.$enu;tem+./ this.help'!out$enu;tem 1 new System.Windows.Forms.$enu;tem+./

6nitiali7eCom#onent() "

... .. mainMen/5
this.main$enuB.$enu;tems.'dd"ange+

new System.Windows.Forms.$enu;tem%( 4 this.file$enu;temthis.help$enu;tem5./

.. ,ileMen/6tem this.,ileMen/6tem.6n ex 1 ?;
this.file$enu;tem.$enu;tems.'dd"ange+ new System.Windows.Forms.$enu;tem%( 4 this.fileE*it$enu;tem5./ this.file$enu;tem.Te*t 1 TFile /

.. ,ile(xitMen/6tem this.,ile(xitMen/6tem.6n ex 1 ?;
this.fileE*it$enu;tem.Te*t 1 TE*it /

.. hel#Men/6tem this.hel#Men/6tem.6n ex 1 5;

this.help$enu;tem.$enu;tems.'dd"ange+ new System.Windows.Forms.$enu;tem%( 4 this.help'!out$enu;tem5./ this.help$enu;tem.Te*t 1 T,elp /

.. hel#)&o/tMen/6tem this.hel#)&o/tMen/6tem.6n ex 1 ?;
this.help'!out$enu;tem.Te*t 1 T'!out... /

.. Form5 ...
this.$enu 1 this.main$enuB/

... -

The

enu*tem class has the following design/time properties and events:

class Men/6tem : Men/, 6Com#onent, 69is#osa&le " .. Gro#erties #/&lic &ool Chec4e " *et; set; #/&lic &ool 9e,a/lt6tem " *et; set; #/&lic &ool (na&le " *et; set; #/&lic &ool M iList " *et; set; #/&lic int Mer*eDr er " *et; set; #/&lic Men/Mer*e Mer*e0y#e " *et; set; #/&lic &ool D!ner9ra! " *et; set; #/&lic &ool 2a ioChec4 " *et; set; #/&lic Shortc/t Shortc/t " *et; set; #/&lic &ool Sho!Shortc/t " *et; set; #/&lic strin* 0ext " *et; set; #/&lic &ool Cisi&le " *et; set; .. ($ents #/&lic e$ent #/&lic e$ent #/&lic e$ent #/&lic e$ent #/&lic e$ent ($entHan ler Clic4; 9ra!6tem($entHan ler 9ra!6tem; Meas/re6tem($entHan ler Meas/re6tem; ($entHan ler Go#/#; ($entHan ler Select;

The ma<or things you%ll want to focus on are the +hec$ed and @adio+hec$ properties 3which mar$ an item as chosen4' the "nabled and -isible properties 3which determine whether the item can be chosen or whether it will be shown4' the 5hortcut property 3which allows you to assign a $eyboard shortcut to a menu item' such as +trl;5 for 5ave4' and the Te(t property' which is what%s shown to the user. 0 Te(t property that includes an &[& 3ampersand4 will underline the ne(t characterP for e(ample' &[5ave& will show as &5ave&' thereby providing a visual cue for $eyboard menu navigation via the 0lt $ey. Typing &/& 3hyphen4 as the Te(t in the enu Aesigner will create a separator between groups of related menu items. The menu merging and di7ist properties are A*/related' and * discuss them later in this chapter. 9f course' the +lic$ event handler is the big celebrity in the menu item list of events because it gets fired when the user clic$s on a menu item:
$oi ,ile(xitMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " this.Close(); $oi hel#)&o/tMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " Messa*e+ox.Sho!(")inFt men/s cool>", ")&o/t..."); -

Conte3t 8enus
Eust as a form can show at most one main menu' a form 3or a control4 can show at most one conte(t menu' as managed via the +onte(t enu property. 1nli$e the ain enu property' which is of type ain enu' the +onte(t enu property is of type +onte(t enu' and you%ll need to ma$e sure to drag the correct class from the Toolbo(. 0 +onte(t enu is also a collection of menu items' but unli$e ain enu ob<ects' +onte(t enu ob<ects have no concept of items &across the top.& +onte(t menus are always vertical at every level' and this is reflected in the +onte(t enu Aesigner' as shown in #igure >.8. Figure 2.2. Conte3t 8enu Designer

*n the case of conte(t menus' the Aesigner always shows &+onte(t enu& at the top level' and items can only fall under that. 6owever' everything else is the same: "ach +onte(t enu ob<ect has a enu*tems collection filled with enu*tem ob<ects' all with the same properties and events.

The one remaining difference between ain enu ob<ects and +onte(t enu ob<ects is that controls also have a +onte(t enu property' whereas only a form has a ain enu property. 0lthough many controls have their own conte(t menus.for e(ample' a Te(tBo( has things such as +opy and 8aste in its conte(t menu.you can replace a control%s built/in conte(t menu by setting the +onte(t enu property of the control. 0s a rule' most of the operations available from any control%s conte(t menu are also available as methods on the control. This means that you can replace the conte(t menu on a control but still provide the operations that the control%s menu would provide' implementing those options by sending the command to the control itself:
$oi co#yMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " text+ox5.Co#y(); -

Child Controls
1nli$e main and conte(t menus' controls dragged from the Toolbo( onto the surface provided by the Aesigner are added to the form%s +ontrols collection. Jou can use that collection yourself to add controls dynamically at run time 3as * showed earlier in this chapter4' or you can add arrays of controls the way the Aesigner/generated *nitiali=e+omponent does:
L8M L8M

Technically spea$ing' both

ain enu and +onte(t enu are

components' as are all the rest of the items that you can drag and drop from

the Toolbo( that show up on the tray along the bottom of the design surface. #or a list of the standard Win#orms components' see 0ppendi( A: 5tandard Win#orms +omponents and +ontrols.

$oi

6nitiali7eCom#onent() " control creation an initiali7ation...

this.SuspendCayout+./

.. ...chil

this.Controls.'dd"ange+ new System.Windows.Forms.Control%( 4 this.status)arBthis.te*t)o*B5./ this."esumeCayout+false./

!otice the calls to 5uspend7ayout and @esume7ayout. 5uspend7ayout is an optimi=ation to $eep the form from updating itself visually until @esume7ayout is called. By brac$eting several tas$s.the child control creation and initiali=ation as well as the addition of the controls to the control collection.in 5uspend7ayout and @esume7ayout' we don%t have to worry about the form trying to draw itself until everything is set up. This is something that you can do if you need to ma$e a nontrivial set of changes to the form%s properties or controls yourself.

Control ;)*rder

*n addition to 0dd and 0dd@ange' you can remove controls from the form%s collection using @emove and @emove@ange 3another good place to use 5uspend7ayout and @esume7ayout4. When the controls have been in the collection long enough to be drawn' the order in the collection establishes the =/order among the controls on the form. #igure >.9 shows an e(ample of a form with three buttons that have been added to the controls collection in =/order order:
this.Controls.) 2an*e( ne! System.Win o!s.Forms.ControlIJ " this.7Dr er?+/tton, this.7Dr er5+/tton, this.7Dr er@+/tton-);

Figure 2.4. ;)*rder and Ta9 *rder

*f you need to change the =/order at design time' you can right/clic$ on a control and choose Bring To #ront' which brings the control to =/order =ero' or 5end To Bac$' which sets the =/order to the last item in the collection. 5imilarly' you can move a control at run time using the +ontrol.BringTo#ront and +ontrol.5endToBac$ methods.

Control Ta9 *rder


Airecting your attention again to #igure >.9' notice the little numbers in the upper/left corner. Those numbers indicate the tab order of the controls on the form and are put there by the -iew N Tab 9rder menu item in -5.!"T. Tab order is the order in which the controls will receive focus as the user presses the Tab $ey. Tab order is e(posed on each control by the Tab*nde( property. 0lso notice that tab order has no relation to =/orderP our button with a =/order of 0 has a tab order of >.

Themed Controls

odern versions of Windows 3Windows T8 and later4 support controls that are drawn with a theme. 0 t"eme can be whatever the Windows shell team adds in the future' but one of the main aspects of a theme is that a user can ad<ust the way the basic controls are drawn. #or e(ample' when buttons aren%t themed' they loo$ li$e those in #igure >.10. Figure 2.16. 7nthemed "uttons in Windows >P

6owever' when the Windows T8 theme is applied in the Aisplay control panel' buttons 3and other standard controls4 ta$e on a futuristic loo$' as shown in #igure >.11. Figure 2.11. Themed "uttons in Windows >P

By default' a Win#orms application always gets the Windows standard controls without themed rendering. 1nder version 1.0' enabling themed rendering re)uired a .manifest file. "nabling themed controls under Win#orms v1.1 no longer re)uires a .manifest file. *nstead' there%s a method on the 5ystem.Windows.#orms.0pplication class that you call before showing any 1*:
L9M L9M

Jou can read about adding theme support to Win#orms 1.0 applications in &1sing Windows T8 Themes with Windows #orms'&

http:CCwww.gotdotnet.comCteamCwindowsformsCThemes.asp(

static $oi -

'pplication.Ena!leLisualStyles+./

Main() "

)##lication.2/n(ne! Form5());

The "nable-isual5tyles method eliminates the need to add a .manifest file to your application' but that%s not the whole <obP you%ll also need to set the #lat5tyle on each control on your form from the default value of #lat5tyle.5tandard to #lat5tyle.5ystem. #igure >.1> shows the difference when the application is running under Windows T8. Figure 2.12. WinForms Flat,t!les

0s a shortcut to setting the #lat5tyle property on the subset of controls that support it 3buttons' group bo(es' and labels4' you can consider a hun$ of code such as the following:
L10M

L10M

To be more thorough about getting controls and contained controls on a form' see the Get0ll+ontrols method in +hapter 3: Aialogs.

#/&lic My0heme Form() " .. 2e8/ire ,or Win o!s Form 9esi*ner s/##ort 6nitiali7eCom#onent();
// Set the FlatStyle for all controls SetFlatStyleSystem+this./

#oid SetFlatStyleSystem+Control parent. 4 foreach+ Control control in parent.Controls . 4 // Jnly these controls ha#e a FlatStyle property )utton)ase !utton 1 control as )utton)ase/ Kroup)o* group 1 control as Kroup)o*/ Ca!el la!el 1 control as Ca!el/ if+ !utton 21 null . !utton.FlatStyle 1 FlatStyle.System/ else if+ group 21 null . group.FlatStyle 1 FlatStyle.System/ else if+ la!el 21 null . la!el.FlatStyle 1 FlatStyle.System/ // Set contained controls FlatStyle- too SetFlatStyleSystem+control./ 5 5

9nly the standard Windows controls support a themed appearance by default. *f you build your own custom controls that draw themselves' they%ll have to handle their own themed rendering 3how to build and draw custom controls is discussed in +hapter 8: +ontrols4.

Hosting C*8 Controls


0s wonderful and varied as Win#orms controls are' especially considering the burgeoning third/party Win#orms control mar$et' +omponent 9b<ect odel 3+9 4 controls have been around a lot longer' and you may still need to use some of them in your Win#orms applications. Win#orms provides built/in support for hosting +9 controls' and -5.!"T ma$es it easy to ta$e advantage of that support.
L11M L11M

+9

controls are also $nown as 97" controls and 0ctiveT controls.

The first step is to get your +9 control of choice to show up on the Toolbo( so that you can drag it onto your forms. To get a +9 control to show up in the Toolbo(' right/clic$ on the Toolbo( and choose +ustomi=e Toolbo(' which will bring up the +ustomi=e Toolbo( dialog' as shown in #igure >.13. Figure 2.1 . Customi-e Tool9o3 Dialog

0ll the items under the +9 +omponents tab are +9 controls registered on your machine. +hec$ing any of them and pressing 9D will add the control to the Toolbo(' as shown in #igure >.1H. Figure 2.1#. C*8 Component 1dded to the Tool9o3

0fter a +9 control has been added to the Toolbo(' you can drop an instance onto a form' set the properties' and handle the events. 0ny +9 control added to your Win#orms pro<ect will cause a pair of interop assemblies to be generated by -5.!"T and added to the pro<ect. *t%s this code that you%re referencing and that forwards your calls to the underlying +9 control.
L1>M L13M L1>M

The a(imp.e(e command line tool generates +9 #or more information about +9 interop' see

control interop assemblies in the same way that -5.!"T does.

L13M

Essential .NET* +ol&me ,- T"e )ommon .ang&age R&ntime

30ddison/Wesley' >0034' by Aon Bo(' with +hris 5ells.

0lso' +9 controls need +9 initiali=ed in a 1*/friendly manner' so ma$e sure that the 5T0Thread0ttribute adorns your ain method:
%ST'Thread'ttri!ute(

static $oi Main() " )##lication.2/n(ne! Form5()); -

When you create a new Windows #orms application in -5.!"T' this attribute is applied by default' so to hurt yourself you must actively remove it.

)a(out

0fter all this tal$ of collecting and ordering controls' you may be wondering how to arrange them' especially in the face of the needs of different users with respect to system font si=e and the si=e of the data being entered.

Form 1uto),caling
#or e(ample' if you lay out a form with system font si=e set to !ormal 39S dots per inch' or dpi4 in the Aisplay control panel' what happens when your users are using 7arge 31>0 dpi4 or one of the custom settings? Jou%d certainly prefer that a form such as that in #igure >.1F show correctly at all font si=es. Figure 2.1'. 1 ,ample Form at (ormal ,i-e Fonts

*f you%re the curious type' you might even attempt to simulate this form for 7arge fonts by changing the form%s font si=e from 8.> points 3the default4 to 10 points' preserving appro(imately the same proportion as between 9S dpi and 1>0 dpi. 1nfortunately' this wouldn%t leave you feeling confident because this change yields a form that loo$s li$e the one in #igure >.1S. Figure 2.1.. +ncreasing the Form?s Font ,i-e at (ormal ,i-e Fonts

!otice that increasing the font si=e increases the height of the Te(tBo( control' but not the si=e of the form overall' to maintain the same proportional si=ing and spacing. 6owever' if you perform the actual test by changing from !ormal to 7arge fonts in the 0dvanced settings of the Aisplay control panel 3which will li$ely re)uire a restart of Windows4' you may be pleased to notice that showing your form at this new font si=e loo$s li$e #igure >.1I without the need to recompile your application. Figure 2.10. The ,ample Form at $arge ,i-e Fonts

The secret to ma$ing this wor$ is a form property called 0uto5cale. When a form is first loaded' if 0uto5cale is set to true 3the default4' it uses another property called 0uto5caleBase5i=e. This property is actually set by the Aesigner and specifies the average width and height of characters in the form%s font. The default font.8.>F/point 5 5ans 5erif under Windows T8 !ormal fonts.has an average width and height of F(13. This information will be encoded into the *nitiali=e+omponent function:
this.)/toScale+aseSi7e 1 ne! Si7e(A, 5N);

1nder 7arge fonts' the default font will be I.8/point 5 5ans 5erif' but the average width and height of the font has now increased to S(1F 3that%s why they call it &7arge& fonts4. 0t load time' the form calls #orm.Get0uto5cale5i=e and notices the difference between the scale it was designed with and the current scale' and the form ad<usts its height and width and those of its controls along with the positions of the controls. This $eeps the &feel& of the form roughly the same' no matter what the system font settings are. *n our sample' the form%s client area width increased from >9S to 3I8 3\>IZ4 as the width of the font went from F to S 3\>0Z4. 5imilarly' the height increased from FH to SS 3\>>Z4 as the height of the font went from 13 to 1S 3\>3Z4. @ounding errors ma$e the scaling imperfect' and it seems that Win#orms uses a little fudge factor to ma$e sure that things are big enough. But in general' the auto/scaling should yield forms that loo$ pretty good given the amount of wor$ you had to do to achieve the effect 3\0Z4.

1nchoring
5caling for system font settings is not all the wor$ that needs to be done to ma$e your form ad<ust itself to your users% whims. #or e(ample' to enter a long string into a te(t bo(' users may attempt to widen the form' as shown in #igure >.18. Figure 2.12. 1ll Controls 1nchored Top, $eft

1nfortunately' the user isn%t li$ely to be happy with this. The form gets bigger' but the contained controls do not. *deally' we%d li$e the te(t bo( to e(pand as the form e(pands' something that can be achieved manually:

int $oi $oi -

elta 1 ?; Form5%Loa (o&'ect sen er, ($ent)r*s e) "

delta 1 Client"ectangle.Width ? te*t)o*B.Width/

te*t)o*B.Width 1 Client"ectangle.Width ? delta/

Form5%Si7eChan*e (o&'ect sen er, ($ent)r*s e) "

Auring the form%s 7oad event' this code captures the delta between the width of the te(t bo( and the width of the client rectangle so that when the form%s si=e is changed' we can reset the width of the te(t bo( to maintain the difference in width as a constant. Deeping this difference constant means $eeping the distance between the right edge of the te(t bo( a fi(ed number of pi(els from the right edge of the form. Deeping an edge of a control a constant distance away from its container edge is called anc"oring. By default' all controls are anchored to the top and left edges of their containers. We%re accustomed to Windows moving our child controls to $eep this anchoring intact as the container%s left or top edge changes. 6owever' Windows does only so much. *t doesn%t resi=e our controls to anchor them to other edges. #ortunately' Win#orms does' without re)uiring you to write the manual anchoring code <ust shown. Jou can change the edges that a control is anchored to by changing the 0nchor property to any bitwise combination of the values in the 0nchor5tyles enumeration:
en/m )nchorStyles " Bone, Le,t, .. e,a/lt 0o#, .. e,a/lt +ottom, 2i*ht, -

Getting our te(t bo( to resi=e as the form is resi=ed is a matter of changing the 0nchor property to include the right edge as well as the left and the top edges. 1sing the 8roperty Browser' you even get a fancy drop/down editor' as shown in #igure >.19. Figure 2.14. ,etting the 1nchor Propert! in the Propert! "rowser

"ven though Windows provides built/in support for anchoring to the top and left edges' anchoring does not have to include the left or top edges at all. #or e(ample' it%s common to

anchor a modal dialog%s 9D and +ancel buttons to only the bottom and right edges so that these buttons stay at the bottom/right corner as the dialog is resi=ed' but aren%t resi=ed themselves. @esi=ing of a control happens if you have two opposing edges selected. *f you don%t have either of the opposing edges selected' neither left nor right' then the control will not be resi=ed in that dimension but will maintain the same proportion of space between the opposing edges. The middle s)uare in #igures >.>0 and >.>1 shows this behavior as well as several other anchoring combinations. Figure 2.26. 1nchoring ,ettings 9efore Widening

Figure 2.21. 1nchoring ,ettings after Widening

Doc5ing
0s powerful as anchoring is' it doesn%t do everything. #or e(ample' if you wanted to build a te(t editor' you%d probably li$e to have a menu' a toolbar' and a status bar along with a te(t bo( that ta$es up the rest of the client area not occupied by the menu' the toolbar' and the status bar. 0nchoring would be tric$y in this case' because some controls need more or less space depending on the run/time environment they find themselves in 3recall what happened to the te(t bo( when we increased the font si=e earlier4. Because anchoring depends on $eeping a control a fi(ed number of pi(els away from a form%s edge' we%d have to do some programming at run/time to figure out how high the status bar was' for e(ample' and then set that as the distance to anchor the te(t bo( away from the edge. *nstead' it would be much easier to tell the form that the te(t bo( should simply ta$e whatever space remains in the client area. #or that' we have doc$ing. Doc!ing is a way to specify a specific edge that we%d li$e to have a control &stic$& itself to. #or e(ample' #igure >.>> shows a form with three controls' all doc$ed. The menu is doc$ed to the top edge' the status bar is doc$ed to the bottom edge' and the te(t bo( is doc$ed to fill the rest. Figure 2.22. 1 Doc5ing %3ample

Jou implement the doc$ing behavior by setting each control%s Aoc$ property to one of the values in the Aoc$5tyle enumeration 3e(posed nicely in the 8roperty Browser' as shown in #igure >.>34:
en/m 9oc4Style " Bone, .. e,a/lt Le,t, 0o#, 2i*ht, +ottom, Fill, -

Figure 2.2 . ,etting the Doc5 Propert! in the Propert! "rowser

Doc5ing and ;)*rder


0s the form resi=es' the doc$ing settings $eep the controls along their designated edges 3or the rest of the space' as determined by the #ill Aoc$5tyle4. *t%s even possible to have multiple controls doc$ed to the same edge' as shown in #igure >.>H. Figure 2.2#. Two ,tatus "ars Doc5ed to the "ottom %dge

0lthough * don%t recommend doc$ing two status bars to the same edge' it%s certainly possible. Aoc$ing is done in reverse =/order priority. *n other words' for statusBar1 to be closest to the bottom edge' it must be further down in the =/order than statusBar>. The following 0dd@ange call gives statusBar1 edge priority over statusBar>:
this.Controls.) 2an*e( ne! System.Win o!s.Forms.ControlIJ "
this.status)arG- // &?order A- lowest !ottom edge priority this.te*t)o*B- // &?order B this.status)arB5./ // &?order G- highest !ottom edge priority

Given the drag/and/drop Aesigner model' which inserts each new control with a =/order of 0' it ma$es sense that doc$ing priority is the reverse of =/order. 6owever' as you add new controls on the form and need to ad<ust the =/order' you may find a conflict between controls along a certain edge and those set to fill. *n that case' the fill control needs to have the lowest edge priority on the form or else it will doc$ all the way to an edge set to be used by another control. #igure >.>F shows an e(ample. Figure 2.2'. Te3t"o3 Whose Doc5,t!le.Fill Has Higher Doc5ing Priorit! than ,tatus"ar

!otice that the te(t bo( has a scroll bar' but the bottom part of it is cut off by the status bar along the bottom edge. This indicates that the status bar has a lower doc$ing priority than the te(t bo(. 6owever' doc$ing priority isn%t set directly in the Aesigner. *nstead' you set the =/order. *n our e(ample' right/clic$ing on the te(t bo( in the Aesigner and choosing Bring To #ront will push the te(t bo( to the top of the =/order but to the bottom of the doc$ing priority' letting the status bar own the bottom edge and removing it from the client area that the te(t bo( is allowed to fill. 0s a rule' whenever you see a visual anomaly li$e

this on your form' you can generally resolve the problem by bringing to the front the control set to Aoc$5tyle.#ill.

,plitting
9ften' when doc$ing is used' you%d li$e the user to have the ability to resi=e some of the controls independently of the si=e of the form itself. #or e(ample' Windows "(plorer splits the space between the toolbar and the status bar' with a tree view on the left and a list view on the right. To resi=e these controls' "(plorer provides a splitter' which is a bar separating two controls. The user can drag the bar to change the proportion of the space shared between the controls. #igure >.>S shows a simple e(ample of a 5plitter control between a Tree-iew control doc$ing to the left edge and a 7ist-iew control doc$ed to fill. Figure 2.2.. 1n %3ample of ,plitting @with Pointer +ndicating a Potential DragA

5plitter controls are available in the Toolbo(. The splitter manages the si=e of the preceding control in the =/order 3or the empty space' if there is no preceding control4. #or e(ample' the relative =/orders of the tree view' splitter' and list view shown in #igure >.>S are set this way:
this.Controls.) 2an*e( ne! System.Win o!s.Forms.ControlIJ "
this.listLiewB- // &?order A- si&e set directly !y splitter this.splitterB- // &?order B- splitter this.treeLiewB- // &?order G- si&e set indirectly !y splitter

this.stat/s+ar5-);

Jou can split controls vertically by setting the Aoc$ property to Aoc$5tyle.7eft 3the default4 or split them hori=ontally by setting the Aoc$ property to Aoc$5tyle.Top. 0n e(ample of hori=ontal splitting is shown in #igure >.>I' where the group bo( has a =/order of =ero' followed by a splitter with the Aoc$ property set to Aoc$5tyle.Top. Figure 2.20. Hori-ontal ,plitting

:rouping
To achieve advanced layout effects' it%s often necessary to brea$ the problem into groups. #or e(ample' imagine a form showing a list of people on the left and a list of details about the current selection on the right' as shown in #igure >.>8. Figure 2.22. :rouping, Doc5ing, and 1nchoring

Jou can%t tell by loo$ing at this single picture' but as the group bo(es in #igure >.>8 change si=e' the controls inside the group bo(es also change si=e. This happens because of two attributes of group bo(es. The first is that group bo(es are container controls' meaning that they act as a parent for child controls' <ust as a form does. The list bo( on the left is a child of the group bo( on the left and not directly a child of the form. 5imilarly' the label and te(t bo( controls on the right are children of the group bo( on the right. The second important attribute of container controls is that they share the same layout characteristics of forms' in that child controls can be anchored or doc$ed. 0s a result' the anchoring and doc$ing settings of a control are relative' not to the edges of the form' but rather to the edges of the container. #or e(ample' the list bo( in #igure >.>8 is actually set to Aoc$5tyle.#ill to ta$e up the entire client area of the group bo(. 5imilarly' the anchoring properties of the te(t bo(es on the right are anchored top' left' and rightP therefore' as the group bo( changes width or changes position relative to the parent form' the te(t bo(es act as you would e(pect relative to the group bo(.

The GroupBo( control is one of three container controls Win#orms providesP the other two are the 8anel control and Tab+ontrol. The 8anel control is <ust li$e a group bo( e(cept that there is no label and no frame. 0 panel is handy if you%d li$e something that loo$s and acts li$e a s&bform' or a form within a form. Tab+ontrol is a container of one or more Tab8age controls' each of which is a container control with a tab at the top' as shown in #igure >.>9. Figure 2.24. 1 Ta9Control with Two Ta9Page Controls

Custom $a!out
The combination of anchoring' doc$ing' splitting' and grouping solves a large ma<ority of common layout problems. 6owever' it doesn%t solve them all. #or e(ample' these techni)ues don%t let you automatically spread controls proportionally across a client area in a table or gridli$e manner' as shown in #igure >.30. Figure 2. 6. Custom $a!out %3ample

The following 7ayout event handler arranges the nine button controls of #igure >.30 proportionally as the form is resi=ed. 3This is also a very good use of the 5uspend7ayout and @esume7ayout methods to suspend layout' and of the 5et+lient5i=e+ore method to set the client area based on an even multiple of the width and the height.4
$oi Eri Form%Layo/t(o&'ect sen er, Layo/t($ent)r*s e) "

.. S/s#en layo/t /ntil !eFre ,inishe this.S/s#en Layo/t();

mo$in* thin*s

.. )rran*e the &/ttons in a *ri on the ,orm +/ttonIJ &/ttons 1 ne! +/ttonIJ " &/tton5, ..., -; int cx 1 Client2ectan*le.Wi th.N; int cy 1 Client2ectan*le.Hei*ht.N; ,or( int ro! 1 ?; ro! 31 N; ;;ro! ) " ,or( int col 1 ?; col 31 N; ;;col ) " +/tton &/tton 1 &/ttonsIcol Q N ; ro!J; &/tton.Set+o/n s(cx Q ro!, cy Q col, cx, cy); .. Set ,orm client si7e to &e m/lti#le o, !i th.hei*ht SetClientSi7eCore(cx Q N, cy Q N); .. 2es/me the layo/t this.2es/meLayo/t(); -

0lthough you can use the 7ayout event to handle all the layout needs of a form' it%s much easier to use anchoring' doc$ing' and grouping wherever possible and fall bac$ on the 7ayout event only to handle special cases.

%ulti"le #ocument 'nterface


Eust as controls can be grouped into containers' windows themselves can be grouped into containers in a style of application called ultiple Aocument *nterface 3 A*4. A* was invented as a way to contain a set of related windows in a single frame' as shown in #igure >.31. Figure 2. 1. ,ample 8D+ Form

0n A* form has two pieces: a parent and a child. Jou designate the parent form by setting the *s di+ontainer property to true' and you designate the child form by setting the di8arent property before showing the form:
$oi 6nitiali7eCom#onent() " ...
this.;s$diContainer 1 true/

... $oi cm FileBe!Chil %Clic4(o&'ect sen er, ($ent)r*s e) " Form chil 1 ne! Chil Form();
child.$diParent 1 this/

chil .Sho!(); -

Eust as the parent has a property indicating that it%s an A* parent' the child has a property' *s di+hild' that tells it that it%s being shown as an A* child. 0nd <ust as a form is a collection of controls' an A* parent form has a collection of A* children called di+hildren. When a child is activated' either by direct user input or by the 0ctivate method' the A* parent receives the di+hild0ctivate event. To see or change which of the A* children is currently active' each A* parent form provides an 0ctive di+hild property. 0n A* parent is e(pected to have two sets of special menu items: one to arrange the children inside the parent frame' and a second one to list the active children and select among them. #igure >.3> shows a typical menu of this sort. Figure 2. 2. 1n 8D+ Child 8anagement 8enu

*n #igure >.3>' the top/level Window menu has four items for arranging the child forms inside the A* parent. By setting the Window menu%s di7ist property to true' you display a separator and all the Te(t properties of all the A* child forms at the end of the menu' allowing the user to pic$ one to activate. *f the Window menu were empty.for e(ample' if you wanted to list the child windows in a submenu.no separator would be added. To implement the items that arrange the children inside the parent' the #orm class provides the 7ayout di method' which ta$es one of the four di7ayout enumeration values:
$oi $oi $oi $oi !in o!0ileChil renHori7Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "
this.Cayout$di+$diCayout.Tile,ori&ontal./

!in o!)rran*e6consMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "

this.Cayout$di+$diCayout.'rrange;cons./

!in o!Casca eMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "

this.Cayout$di+$diCayout.Cascade./

!in o!0ileChil renCertMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "

this.Cayout$di+$diCayout.TileLertical./

To implement the Windows cascading menu to show A* child forms and have the selected child brought to the front' you set the Windows menu item di7ist property to true.

8enu 8erging
Win#orms ma$es A* so easy that it almost ta$es all the satisfaction out of things until we get to menu merging' which gives us something to sin$ our teeth into. The basic idea of men& merging is that there is only one main menu shown in a form' even if that form

contains A* children. *nstead of letting each A* child have its own menu' you merge the menu items of the child with the menu items of the parent' simplifying things for the user. Why don%t you simply put everything into the parent%s main menu to start with? The reason is that lots of menu items' such as #ile N +lose' don%t ma$e sense without a child' so showing them isn%t helpful. 5imilarly' the set of operations can well change between A* children' so the merged menu should consist only of the items from the parent that always ma$e sense' such as #ile N "(it' and the items from the currently active child. #or e(ample' #igure >.33 shows an A* parent #ile menu when there are no children' and #igure >.3H shows the same #ile menu when there is a child. Figure 2. . 8D+ Parent File 8enu with (o 8D+ Children

Figure 2. #. 8D+ Parent File 8enu with an 8D+ Child

*n the Aesigner' both the parent and the child forms have a main menu' as shown in #igure >.3F. Figure 2. '. The Parent and Child 8enus in the Designer

!otice that the child%s menu items don%t contain all the items shown in #igure >.3H when the child is active at run time. *nstead' the child has only the new items that are to be added to the parent menu. #or the merging to wor$' we need to set two properties on the menu items to be merged.at the top level' such as #ile' as well as at the lower levels' such as #ile N !ew. erging is governed on a per/item basis via ergeType and erge9rder. The property is one of the following enu erge enumeration values:
en/m Men/Mer*e " Mer*e6tems, ) , 2emo$e, 2e#lace, -

ergeType

erge9rder dictates how things are ordered at the top level and in the submenus. The top/ level menu is arranged by merge order right to left' lowest to highest. *n our e(ample' notice that the parent #ile and Window menus are separated by the child "dit menu. To do this' you set the merge order for #ile' "dit' and Window to 0' 1' and >' respectively. But don%t be confused by the numbers * used. * could have easily used 0' 100' and H0H to achieve the same menu item placement. The actual numbers don%t matter.only the order. Because the parent as well as the child menus have a #ile menu at merge order 0' we have several options. 9ne is to set the merge types to 0dd' giving us two #ile menus when a child is created. That%s useful when the menus have different names' as with our "dit and Window menus' but it doesn%t wor$ when they have the same name. 0nother option is to set the merge type to @emove on the parent menu' removing the parent%s #ile menu when the child menu items are merged. @emove is handy when a menu item isn%t useful after two menus are merged' but not in this case because the child needs a #ile menu.

0 third option is to use @eplace and let the child%s #ile menu completely replace that of the parent' but this removes all the menu items owned by the parent #ile menu that are child/ independent. *n our e(ample' the option we want is erge*tems' which merges second/level items using the same algorithm used to merge items at the top level.that is' as specified by the erge9rder and ergeType properties. Tables >.1 and >.> show the menu merge settings used by the e(ample to create a merged top/level menu and a merged #ile menu. *f you find that you can%t get the items you want in the merged menu or if you have dynamic menu items' you can access the parent%s menu using the parent%s public erged enu property. *n this way' you can either modify Win#orms% automatic action' or you can replace it with something of your own ma$ing. 0nd as if that weren%t enough' because you can still put controls on an A* parent form' you can actually mi( doc$ing with A* to achieve layouts such as the one shown in #igure >.3S. Figure 2. .. 8i3ing Doc5ing and 8D+

!otice that the A* children automatically arrange themselves in the client area not occupied with doc$ed controls' ma$ing these $inds of layouts possible.

Visual 'nheritance
0fter all the settings and behavior and layout details you%ve learned to pac$ into forms in this chapter' you may decide that you%d li$e to $eep some of your hard wor$ in a #orm/ derived base class for easy reuse by further derivations' and you can certainly do that. *f

you follow the convention that forms initiali=e their own properties and the properties of their child controls in a function called *nitiali=e+omponent' the Aesigner provides direct support for your vis&al in"eritance: the reuse of a form base class via inheritance. Ta9le 2.1. Parent 8enu 8erge ,ettings
Parent $enu;tem $ergeType $ergeJrder

#ile #ile N !ew +hild #ile N "(it Window

erge*tems 0dd 0dd 0dd

0 0 > >

Ta9le 2.2. Child 8enu 8erge ,ettings


Child $enu;tem $ergeType $ergeJrder

#ile #ile N 5ave #ile N +lose "dit

erge*tems 0dd 0dd 0dd

0 1 1 1

The goal of visual inheritance is to allow a base class to capture common 1* elements' which are then shared and augmented by deriving classes. #or e(ample' imagine a Base#orm class that derives from #orm and provides a menu bar' a status bar' and an icon' as shown hosted in the Aesigner in #igure >.3I. Figure 2. 0. "ase Class 7sed in =isual +nheritance

Base#orm can now serve as a base class for all forms that contain at least this functionality' such as the "ditor#orm shown in #igure >.38.
L1HM L1HM

a$e sure that your pro<ect is compiled before you use the Aesigner on inherited forms.

Figure 2. 2. %ditorForm Deri&ed from "aseForm

The "ditor#orm class was created by deriving from Base#orm' overriding the Te(t property' adding the Te(tBo( control to the form' and overriding the Te(t property of the status bar from the base class. * could have done this wor$ by hand' but instead * created the "ditor#orm. * right/clic$ed on the pro<ect in 5olution "(plorer and chose 0dd N 0dd *nherited #orm. Then * set the form%s name and chose Base#orm from the list of forms in the pro<ect displayed in the *nheritance 8ic$er dialog' as shown in #igure >.39. Figure 2. 4. The +nheritance Pic5er Dialog

The initial "ditor#orm loo$ed <ust li$e Base#orm e(cept for the little arrow over the status bar 3as shown in the lower/right corner of #igure >.384. This arrow indicates a control inherited from the base. 0fter inheriting the new form class from the e(isting form class' * used the Toolbo( to add the new controls and used the 8roperty Browser to change the form%s Te(t property. 6owever' to change the Te(t property of the status bar' * first had to change the access modifier. By default the Aesigner adds all fields as private' which means that they%re accessible only from that class' the Base#orm class in our e(ample. *f you want to use the Aesigner to set a property on one of the controls in the base class from the deriving class'

by default you can%t until you change the access modifier in the field declaration in the base class:
pri#ate

Stat/s+ar stat/s+ar;

Jou change the private $eyword to protected to allow access by deriving classes:
protected

Stat/s+ar stat/s+ar;
L1FM

*f you%re really into +ooperes)ue visual design' you can change this $eyword by selecting the status bar on Base#orm using the Aesigner and changing the odifiers field.
L1FM

0lan +ooper invented the drag/and/drop visual design mechanism for -isual Basic.

The purpose of this e(ercise in reuse is that when a new feature is needed across the set of forms that derive from Base#orm or when a bug is found' you can ma$e the changes to the base form' and deriving forms automatically benefit. #or e(ample' Base#orm could add an "dit menu' and that would propagate to the "ditor#orm on the ne(t compile. 0s nifty as visual inheritance is' it%s not without its limitations. #or e(ample' although you can completely replace the main menu of a base form in a deriving form' you can%t add or subtract menu items or replace menu item event handlers from the base. 6owever' as a template mechanism to avoid duplicating controls and code' visual inheritance is definitely worth your consideration.

Where Are We?


We%ve e(plored how to show forms' control their lifetime' si=e' and location' dictate their non/client adornments' manage main and conte(t menus and child controls' ma$e whole forms partially transparent and parts of forms completely transparent' arrange controls using anchoring' doc$ing' splitting' and grouping' arrange A* children in A* parent forms' and pac$age forms for reuse via visual inheritance. Jou might thin$ that you $now all there is to $now about forms. 6owever' you would be mista$en. +hapter 3 is all about using forms as dialogs.

Chapter . Dialogs
0 dialog is defined by its usage. *f a form is the application%s main window' it%s a window' not a dialog. 6owever' if a form pops up in response to a user re)uest for service' such as a re)uest to open a file' and stops all other user interactions with the application' it%s a dialog 3a modal dialog' specifically4.

6owever' things get a little mur$y when we consider modeless dialogs. They don%t stop the user from interacting with the rest of the application' but they do provide a means of interaction outside the main window. What ma$es things a bit mur$ier is the Win#orms terminology. 5tandard dialogs are e(posed by the T((Aialog family of components' such as the #ile9penAialog. ost of these components support only modal activation using 5howAialog' but a couple of them support modeless activation using 5how. *n contrast' custom dialogs are classes that derive from the #orm base class and can be shown modally or modelessly based on whether they%re activated using 5howAialog or 5how. !o matter how a dialog is defined' this chapter covers things you%d normally thin$ of as dialog/related issues' including standard dialogs' custom forms to be used as dialogs' modal and modeless activation and lifetime' transferring data in and out' validating user/ entered data' and providing help. To aid you in ma$ing the transition to the unification of dialog/li$e functionality with forms' * don%t use the term &dialog& e(cept when referring to the standard dialog components.

Standard #ialogs
Win#orms ships with several standard dialogs 3sometimes $nown as &common dialogs&4 provided as components from the 5ystem.Windows. #orms namespace. 0 component is li$e a control in that you can drag it from the Toolbo( onto a design surface and set its properties using the 8roperty Browser. 6owever' unli$e a control' a component doesn%t render in a region. *nstead' it shows up on the tray along the bottom of the design surface so that you can choose it' and it isn%t shown at run time at all. #or the details of components' read +hapter 9: Aesign/Time *ntegration. Because all the standard dialogs are components' they can be created in two ways: manually or by using the Aesigner. #or e(ample' creating and showing an instance of the +olorAialog component manually loo$s li$e this:
$oi color9ialo*+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

Color0ialog dlg 1 new Color0ialog+./

l*.Color 1 Color.2e ; 9ialo*2es/lt res 1 l*.Sho!9ialo*(); i,( res 11 9ialo*2es/lt.DL ) " Messa*e+ox.Sho!("Mo/ #ic4e " ; l*.Color.0oStrin*()); -

6owever' if you drag a +olorAialog component from the Toolbo(' you can show it without e(plicitly writing the creation code' because the Aesigner will generate it for you in the *nitiali=e+omponent function:
$oi 6nitiali7eCom#onent() " ... ... color9ialo*+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

this.color0ialogB 1 new Color0ialog+./

$oi

color0ialogB.Color 1 Color."ed/ 0ialog"esult res 1 color0ialogB.Show0ialog+./

i,( res 11 9ialo*2es/lt.DL ) " Messa*e+ox.Sho!("Mo/ #ic4e " ; -

color0ialogB.Color .0oStrin*());

* tend to prefer the latter approach because * li$e to set properties visually' but either one wor$s <ust fine. The following standard dialogs come with Win#orms:

allows the user to pic$ a color e(posed by the +olor property of type 5ystem.Arawing.+olor. Folder)rowser0ialog allows the user to pic$ a folder e(posed by the 5elected8ath property of type string. This component is available only in .!"T 1.1 and later. Font0ialog allows the user to choose a font and its properties' such as bold' italics' and so on. The user/configured font ob<ect of type 5ystem.Arawing.#ont is available from the #ont property of the component. JpenFile0ialog and Sa#eFile0ialog allow the user to pic$ a file to open or save' as appropriate for the dialog. The chosen file name is available from the #ile!ame property of type string. PageSetup0ialog- Print0ialog- and PrintPre#iew0ialog are related to printing' which is discussed in +hapter I: 8rinting.
Color0ialog

0ll but one of the standard dialogs' including the #olderBrowserAialog that .!"T 1.0 forgot' are wrappers around e(isting common dialogs in Windows. Because these dialogs don%t support modeless operation' neither do the Win#orms components. 6owever' the 8rint8reviewAialog component' which provides a new dialog <ust for Win#orms and is not available from Windows' supports both modal and modeless operation using 5howAialog and 5how' respectively.

St(les

+hapter >: #orms introduces the important dialog/related properties: +ontrolBo(' #ormBorder5tyle' 6elpButton' a(imi=eBo(' inimi=eBo(' and 5how*nTas$bar. By default' a new form shows the control bo(' is si=able' doesn%t show the help button' can be minimi=ed and ma(imi=ed' and is shown in the shell%s tas$bar. #or a main window' these are fine settings and will yield a form that loo$s li$e the one in #igure 3.1. Figure .1. T!pical 8ain Window Form ,ettings

0 typical modal form' on the other hand' is more li$ely to hide the minimi=e and ma(imi=e bo(es' to show the help button' and to choose not to show up in the tas$bar 3the parent will already be there4' as shown in #igure 3.>. Figure .2. T!pical 8odal Form ,ettings

Jou may find it interesting that even though the 5i=eGrip5tyle hasn%t changed from its default of 0uto between the main window and the modal form e(amples' by default the si=e grip is shown only when the form is shown modally. 5imilarly' although the +ontrolBo( property remains true when the border style is changed to #i(edAialog' the

control bo( will not be shown' as you can see in #igure 3.3. +learly' Win#orms has its own ideas about what to show along the edge of your form' sometimes disregarding your preferences. Figure . . T!pical 8odal Form ,ettings

Typical modeless form settings are <ust li$e the si=able modal form settings 3shown in #igure 3.>4 e(cept that calling 5how instead of 5howAialog causes the si=e grip to go away. These e(amples should serve most of your needs' although it%s certainly possible to vary form properties to get a few more variations. #or e(ample' you can use the border styles #i(edToolWindow and 5i=ableToolWindow to show the caption in miniature 3handy for floating toolbar or toolbo( windows4.

,etting 8odal =ersus 8odeless "eha&ior D!namicall!


*f you%d li$e your form to change its settings based on whether or not it%s being shown modally' you can chec$ a form%s odal property:
$oi Loan)##lication9ialo*%Loa (o&'ect sen er, ($ent)r*s e) " .. Sho! as a ,ixe :si7e mo al ,orm this.Form+or erStyle 1 Form+or erStyle.Fixe 9ialo*; .. Sho! as a si7a&le mo eless ,orm this.Form+or erStyle 1 Form+or erStyle.Si7a&le;

if+ this.$odal . 4

5 else 4

Aepending on whether the form was shown using 5howAialog or 5how' the odal property will be true or false' respectively. 6owever' because the way a form is shown isn%t $nown until after it%s been created' the odal property isn%t accurate in the constructor and will always be false at that time. Jou will be able to get the form%s actual odal property value only in the 7oad event or later.

#ata !+change
!o matter what $ind of form you%ve got' after you%ve created it' you need to get data into it and out of it. 0lthough it is possible for a form to update an application%s data directly when the user presses 9D or 0pply' this is generally considered bad practice for anything e(cept the main form of your application. The problem is that changes in one part of the application might adversely affect your code. #or this reason' forms should be as stand/ alone as possible. This means that forms will have a set of properties that they manage' letting the client of the form populate the initial values of the properties and pulling out the final values as appropriate' <ust as you saw earlier in the typical usage of +olorAialog. Because most properties managed by a form are actually properties on the controls that ma$e up the form' you may be tempted to ma$e the control fields in your form public' letting the client of the form do this:
Loan)##lication9ialo* l* 1 ne! Loan)##lication9ialo*(); DL Q. -

dlg.applicantNameTe*t)o*.Te*t 1 Uoe )orrower / // 0JN:T2

9ialo*2es/lt res 1 l*.Sho!9ialo*(); i,( res 11 9ialo*2es/lt.DL ) " .Q /ser #resse

The problem with this approach is the same problem you%ll encounter when ma$ing any field public: *f 7oan0pplicationAialog wants to change the way the applicant%s name is displayed' such as from a Te(tBo( control to a 7abel control' all users of the 7oan0pplicationAialog class must now be updated. To avoid this problem' the general practice is to e(pose public custom form properties that get and set the form%s child control properties:
L1M L1M

Because the dialog%s constructor calls *nitiali=e+omponent' which creates the dialog%s child controls' the client of the dialog is free to get and set

properties as soon as the dialog ob<ect is created.

#/&lic strin* )##licantBame "


get 4 5

ret/rn a##licantBame0ext+ox.0ext;

set 4

a##licantBame0ext+ox.0ext 1 $al/e;

The client uses properties in the same way a field is used. 6owever' unli$e a field' getting or setting a property e(ecutes code that you%re free to change without re)uiring a code change in the form client. #urthermore' properties result in a simpler usage model for the form client' because they no longer need to concern themselves with the implementation details best left to the form:
Loan)##lication9ialo* 9ialo*2es/lt res 1 l* 1 ne! Loan)##lication9ialo*();

dlg.'pplicantName 1 Uoe )orrower /

l*.Sho!9ialo*();

Handling *< and Cancel


Before data can be retrieved from the property of a modal form' 5howAialog must first return' and this means that the form must be closed. 9ne way to do that is by calling the #orm.+lose function inside each button%s +lic$ event handler:
$oi $oi cancel+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "
this.Close+./

o4+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

this.Close+./

1nfortunately' calling +lose will return Aialog@esult.+ancel from the 5howAialog method by default. #or other buttons' we%d li$e to be able to return other members of the Aialog@esult enumeration:
en/m 9ialo*2es/lt " )&ort, Cancel, .. res/lt o, callin* Form.Close() 6*nore, Bo, Bone, .. e,a/lt DL, 2etry, Mes, -

The 0bort' *gnore' !o' and @etry values are used mostly by essageBo(.5how' but you should feel free to use them for your own custom forms. The one we want to return from 5howAialog when the 9D button is pressed is' of course' 9D. +hec$ing the return value
L>M

from 5howAialog is a shortcut for chec$ing the Aialog@esult property of the form itself' something you can do instead:
L>M

*n contrast with #orm.5how'

essageBo(.5how is modal' not modeless' introducing an inconsistency between the two methods with the same

name.

l*.Sho!9ialo*();
0ialog"esult res 1 dlg.0ialog"esult/

i,( res 11 9ialo*2es/lt.DL ) " .Q /ser #resse l*.Sho!9ialo*()

DL Q. -

By default' the Aialog@esult property of any form is !one. To return something other than +ancel from 5howAialog' you set the form%s Aialog@esult property before closing the form:
$oi o4+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

this.0ialog"esult 1 0ialog"esult.JP/

this.Close();

$oi cancel+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " .. Close !ill set the 9ialo*2es/lt to Cancel, so settin* .. it ex#licitly is '/st *oo manners this.9ialo*2es/lt 1 9ialo*2es/lt.Cancel; this.Close(); -

When you set the form%s Aialog@esult to something other than !one' a modal form interprets that to mean that it should close. +alling +lose in this case isn%t even necessary' reducing our code to the following for modal forms:
$oi $oi this.0ialog"esult 1 0ialog"esult.Cancel/

this.0ialog"esult 1 0ialog"esult.JP/

o4+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

cancel+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

With this code in place' clic$ing on the 9D or +ancel button dismisses a form such as the one shown in #igure 3.H. This action returns the correct result and' using properties' e(poses whatever information the user entered during the lifetime of the form. Figure .#. 1 ,ample Form 7sed as a Dialog @,ee Plate .A

1nfortunately' we don%t have )uite all the behavior we need from our 9D and +ancel buttons. *n #igure 3.H notice that the 9D button is not drawn as the default button. The defa&lt b&tton is the one invo$ed when the "nter $ey is pressed' and it%s typically drawn with a thic$er border than nondefault buttons. *n addition' although you can%t see this in a picture' the +ancel button isn%t invo$ed when the "5+ $ey is pressed. "nabling this behavior is a matter of designating in the form itself which buttons should be invo$ed when "nter and "5+ are pressed. Jou do this by setting the form%s 0cceptButton and +ancelButton properties:
$oi 6nitiali7eCom#onent() " ... ... $oi o4+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " this.9ialo*2es/lt 1 9ialo*2es/lt.DL; $oi cancel+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " this.9ialo*2es/lt 1 9ialo*2es/lt.Cancel; -

this.'ccept)utton 1 this.o6)utton/ this.Cancel)utton 1 this.cancel)utton/

!otice that we used the Aesigner to set these two properties. This is handy because the 8roperty Browser shows a drop/down list of all the buttons currently on the form to choose from. 0s it turns out' after you%ve set the form%s +ancelButton property' you don%t need to set the Aialog@esult property in the +ancel button%s +lic$ event handler' ma$ing the +ancel button%s +lic$ event handler itself unnecessary. This wor$s because when you set the form%s +ancelButton property' the Aesigner sets the Aialog@esult property on the +ancel button itself. Because it%s normally a button that dismisses a form' the Button class provides a Aialog@esult property to designate the result that pressing this button will have on the form. By default' the value of this property is Aialog@esult.!one' but the Aesigner sets to

+ancel the Aialog@esult property of the button designated as the +ancelButton on the form. 6owever' the Aesigner does not set the form%s 0cceptButton Aialog@esult property in the same manner. 7uc$ily' if you set the Aialog@esult property of the 9D button to Aialog@esult.9D yourself' you can dismiss the form without having the 9D or +ancel button clic$ event handler at all:
L3M L3M

There%s an open debate in the Win#orms community as to which is a bug: that the Aesigner sets the Aialog@esult of the +ancelButton' or that

the Aesigner doesn%t set the Aialog@esult of the 0cceptButton. 0s for me' * thin$ it%s a bug that the Aesigner doesn%t do the same thing for both buttons.

$oi

6nitiali7eCom#onent() " ... ...

this.o6)utton.0ialog"esult 1 0ialog"esult.JP/ this.cancel)utton.0ialog"esult 1 0ialog"esult.Cancel/

... this.)cce#t+/tton 1 this.o4+/tton; this.Cancel+/tton 1 this.cancel+/tton; ... .. o4+/tton%Clic4 han ler not nee e .. cancel+/tton%Clic4 han ler not nee e

5o even though it%s possible to implement the client event handlers for the 9D and +ancel buttons' often you can get away with simply setting the form%s 0cceptButton and +ancelButton properties and setting the Aialog@esult property of the 9D button. This techni)ue gives you all the data e(change behavior you%ll need in a modal form 3e(cept data validation' which * cover later in this chapter4.

8odeless Form Data


odeless forms re)uire a different strategy from modal forms to communicate user/ updated data to the form%s client. #or one thing' setting the Aialog@esult property of a modeless form doesn%t automatically dismiss it as it does for a modal form. #or another thing' because 5how returns immediately' the client usage model is different. #inally' modeless forms acting as dialogs usually have 0pply and +lose buttons' so data entered into the form can be used before the modeless form even goes away. What%s needed is a way to notify a client of a modeless form when the 0ccept button is pressed. 7uc$ily' standard .!"T events can be used for this purpose:
LHM

LHM

#or more information about .!"T delegates and events' see 0ppendi( B: Aelegates and "vents.

class Gro#erties9ialo* : Form " ...


// E#ent to fire when 'ccept is pressed pu!lic e#ent E#ent,andler 'ccept/

$oi -

acce#t+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

// Fire e#ent when 'ccept is pressed if+ 'ccept 21 null . 'ccept+this- E#ent'rgs.Empty./

$oi close+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " this.Close(); -

*n this e(ample' notice that 8ropertiesAialog e(poses a public event called 0ccept using the standard "vent6andler delegate signature. When the 0ccept button is pressed' the modeless form fires the 0ccept event to notify any interested parties that the 0ccept button has been pressed. The client of the form can subscribe to the event when the form is created:
.. Client creates, connects to, an sho!s mo eless ,orm $oi sho!Gro#erties%Clic4(o&'ect sen er, ($ent)r*s e) " Gro#erties9ialo* l* 1 ne! Gro#erties9ialo*();
dlg.'ccept 71 new E#ent,andler+Properties8'ccept./

l*.Sho!(); // Client handles e#ent from form to access accepted #alues #oid Properties8'ccept+o!9ect sender- E#ent'rgs e. 4

Gro#erties9ialo* l* 1 (Gro#erties9ialo*)sen er; Messa*e+ox.Sho!( l*.SomeGro#erty);


5

When the form is created but before it%s shown' notice that the client subscribes to the 0ccept event. When the 0ccept button is pressed' the notification shows up in the client%s event handler. By convention' the form passes itself when it fires the event so that the receiver of the event can use a simple cast operation to get bac$ the reference to the form. The only thing left to do is to ma$e the modeless form%s +lose button call the form%s +lose method' and you%ve got yourself a modeless form.

#ata Validation
Jou should never trust your users. * don%t mean you can%t trust them to pay 3a separate issue that * won%t go into here4P * mean you can%t trust the data that your users enter. They may

not give you all the data that you need' or they may not give you data in the correct format. 7ist bo(es' radio buttons' and all the other controls that give users choices do so to ma$e sure that they provide data in the correct format. 6owever' sometimes you need to validate free/form data entry' such as what a user types into a te(t bo(. #or that' you handle a control%s -alidating event:
#oid applicantNameTe*t)o*8Lalidating+o!9ect sender- CancelE#ent'rgs e. 4

.. Chec4 ,or existence o, a##lication name i,( a##licantBame0ext+ox.0ext.Len*th 11 ? ) " Messa*e+ox.Sho!("Glease enter a name", "(rror");
e.Cancel 1 true/ 5

The -alidating event is called when the focus is moved from one control on the form that has the +auses-alidation property set to true to another control that has the +auses-alidation property set to true.for e(ample' from a Te(tBo( control to the 9D button. The -alidating event gives the handler the chance to cancel the moving of the focus by setting the +ancel"vent0rgs.+ancel property to true. *n this e(ample' if the user doesn%t enter a name into the te(t bo(' then the -alidating event handler notifies the user of the transgression and cancels the event' $eeping the focus on the te(t bo( containing invalid data. *f the -alidating event is not canceled' the form will be notified via the -alidated event:
#oid applicantNameTe*t)o*8Lalidated+o!9ect sender- E#ent'rgs e. 4 5

Messa*e+ox.Sho!("Bice name, " ; a##licantBame0ext+ox.0ext, "0han4s3");

"ach control has +auses-alidation set to true by default. To allow the user to cancel the form without entering valid data' you must set the +auses-alidation property to false for your +ancel or +lose button:
$oi 6nitiali7eCom#onent() " ... ...

this.cancel)utton.CausesLalidation 1 false/

/egular %3pressions and =alidation


9ne handy tool for data validation that%s not specific to Win#orms but is provided by .!"T is the @ege( class from the 5ystem.Te(t.@egular"(pressions namespace. The @ege( class provides a regular e(pression interpreter. 0 reg&lar e%pression is a general/purpose way to

describe the format of string data so that' among other things' a string can be chec$ed to ma$e sure that it fits a re)uired format. The regular e(pression language is beyond the scope of this boo$' but let%s loo$ at an e(ample. 0 string to chec$ that the format of a phone number fits the 1.5. format' including area code' parentheses' spaces' and dashes' would loo$ li$e the following:
LFM LFM

#or an overview of regular e(pression in .!"T' read &@egular "(pressions in .!"T'& by

ichael Weinhardt and +hris 5ells'

Windo s

Developer' 11C0>' http:CCwww.wd/mag.comCdocumentsCsRIFHICwin0>1>dC


R\(\ "N-\) \ "N-:\ "H-S

This regular e(pression brea$s down as follows:

The leading &]& means to start chec$ing the string from the beginning. Without this' any leading characters that don%t match the regular e(pression will be ignored' something that could lead to improperly formatted phone numbers. The &O3& means to match against a literal &3& character. The &O& prefi( is necessary to escape the &3&' which otherwise would be treated specially by the @ege( class. The &Od^3_& means to match three digits. The &O4 & means to match a &4& character' followed by a space character. The &Od^3_/Od^H_& means to match three more digits' followed by a &/& character' followed by four more digits. The trailing &`& means to match the string all the way to the end so that no other characters can come after the phone number.

This regular e(pression can be used in a -alidating event handler to chec$ for a 1.5. phone number:
$oi a##licantGhoneBo0ext+ox%Cali atin*( o&'ect sen er, Cancel($ent)r*s e) "

"ege* re 1 new "ege*+M VN+Nd4@5N. Nd4@5?Nd4S5O ./ if+ 2re.;s$atch+applicantPhoneNoTe*t)o*.Te*t. . 4

Messa*e+ox.Sho!( "Glease enter a <S #hone n/m&er: (xxx) xxx:xxxx", "(rror"); e.Cancel 1 tr/e;

*f the string entered into the phone number te(t bo( does not match the regular e(pression in its entirety' the *s atch method of the @ege( class will return false' letting the handler indicate to the user that the data is not in the correct format. Ta$en together' regular

e(pressions and validation provide a powerful tool to chec$ a wide range of input strings provided by the user.

Data Format (otification


0s much as * lean on the message bo( in my test development' * prefer not to use it for actual applications. #or one thing' users are li$ely to forget the acceptable format for a phone number after the message bo( goes away. 9ne alternative is to use a status bar' but status bars tend to be ignored because they%re at the bottom of the screen' far away from what the user is loo$ing at. 0 better way is to use the "rror8rovider component' which shows the user that there%s a problem and provides a tooltip with e(tra information' as shown in #igure 3.F.
LSM LSM

0ccording to legend'

icrosoft did a usability study awarding people `F0 if they would loo$ under their chair' putting the notification for this

award in the status bar. The `F0 went unclaimed during the testing.

Figure .'. ,ample 7se of the %rrorPro&ider Component

When the user attempts to change focus from the empty te(t bo(' we use an instance of the "rror8rovider component to set an error associated with that te(t bo(' causing the icon to be displayed to the right of the te(t bo( and ma$ing the tooltip available for more information. To implement this behavior' you drag an "rror8rovider component onto the form and handle the -alidating event:
$oi a##licantBame0ext+ox%Cali atin*(o&'ect sen er, Cancel($ent)r*s e) "
error 1 Please enter a name /

string error 1 null/

i,( a##licantBame0ext+ox.0ext.Len*th 11 ? ) " e.Cancel 1 tr/e; errorPro#iderB.SetError++Control.sender- error./

!otice the call to "rror8rovider.5et"rror. The first argument is the control the error is associated with' which we get from the sender argument to the -alidating event. The second argument is the error string' which is used as the tooltip. *f there is no problem' the error is null' which causes the error indicator for that control to go away' showing that there%s no error.

Thorough =alidation
0s useful as the -alidating event is' especially when combined with the "rror8rovider component' there is one validation issue you%ll have to deal with separately. Because the -alidating event is triggered only when focus is moved from one control to another' if a control has invalid data but never receives focus' it will never be validated. #or e(ample' the form in #igure 3.F has three te(t bo(es. "ven if you were to handle the -alidating event for all three te(t bo(es' the user could still enter valid data into the first one 3assuming it gets focus first4 and press the 9D button' causing the form to close and return Aialog@esult.9D. The problem is that the other two te(t bo(es will never get focus' will never receive the -alidating event' and therefore will never get a chance to cancel the acceptance of the form. 9ne way to deal with this problem is to ma$e sure that all controls start with valid data' re)uiring the user to set focus to invalidate the data and triggering the validation events when focus is lost. 6owever' there are lots of cases when you can%t fill in valid initial data. What%s a good default for a phone number? 9r e/mail address? 9r mother%s maiden name? #or these cases' you%ll need to write the code manually to validate the form in the 9D button%s +lic$ event handler:
$oi o4+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " .. Cali ate each irect chil control man/ally ,oreach( Control control in this.Controls() ) " control.Foc/s(); i,( 3this.Cali ate() ) " this.9ialo*2es/lt 1 9ialo*2es/lt.Bone; &rea4; -

This code wal$s the list of controls in the form%s control collection' setting each one in turn to have the focus. By itself' this will not trigger the -alidating event' unfortunately' so we also must to trigger that event by calling the form%s -alidate method. The #orm.-alidate method does not validate all the controls in the form. *nstead' it validates only the control that <ust lost focus. *f validation fails on any one control' we change the form%s

Aialog@esult property 3preset to Aialog@esult.9D by the 9D button%s Aialog@esult property4' and that stops the form from closing. *f you%ve got controls that contain other controls' such as controls contained by group bo(es' you must be a little more thorough about chec$ing each control for child controls to ma$e sure that they%re validated' too:
// "etrie#e all controls and all child controls etc. // $a6e sure to send controls !ac6 at lowest depth first // so that most child controls are chec6ed for things !efore // container controls- e.g.- a Te*t)o* is chec6ed !efore a // Kroup)o* control Control%( Ket'llControls+. 4 'rrayCist allControls 1 new 'rrayCist+./ Wueue >ueue 1 new Wueue+./ >ueue.En>ueue+this.Controls./ while+ >ueue.Count = A . 4 Control.ControlCollection controls 1 +Control.ControlCollection.>ueue.0e>ueue+./ if+ controls 11 null XX controls.Count 11 A . continue/ foreach+ Control control in controls . 4 allControls.'dd+control./ >ueue.En>ueue+control.Controls./ 5 5 return +Control%(.allControls.To'rray+typeof+Control../

$oi o4+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " .. Cali ate each control man/ally
foreach+ Control control in Ket'llControls+. . 4

.. Cali ate this control control.Foc/s(); i,( 3this.Cali ate() ) " this.9ialo*2es/lt 1 9ialo*2es/lt.Bone; &rea4; 5

The Get0ll+ontrols method gets all of a form%s controls' as well as all the controls% child controls' all those controls% children' and so on. This function is handy whenever you need to dig through the complete set of controls on a form' not <ust when you%re validating. Jou%ll see it used a few more times in this chapter. The 9D or 0pply button%s +lic$ event handler is also a good place to do any other $ind of validation that can%t be done using the validation events' such as chec$ing multiple related fields at the same time. *f for any reason you don%t want the form to close' set the form%s Aialog@esult to !one' and it won%t.

'm"lementing ,el"
0s useful as the "rror8rovider user interface is 3at least compared with the status bar4' it would be nice to provide help to our users that didn%t ta$e the form of a reprimand. *t would also be useful to give them help without ma$ing them try something that fails. Win#orms supports these goals in several ways.

Tooltips
9ne simple way is to provide each control with a tooltip so that when the user hovers the mouse pointer over the control' relevant instructions appear' as shown in #igure 3.S. Figure ... 7sing Tooltips

Jou can use the ToolTip component to add tooltips to any control in a form. 0fter there is a ToolTip component on the form' each control sprouts a new property that shows up as &ToolTip on toolTip1& in the 8roperty Browser. 0ny new property that an ob<ect adds to another ob<ect on a form is called an e%tender propert#' because the ob<ect doing the e(tending is providing additional functionality to the e(tended ob<ect. 5etting the ToolTip e(tender property for a control gives it a tooltip as provided by the ToolTip component.
LIM LIM

"(tender properties are covered in detail in +hapter 9: Aesign/Time *ntegration.

7sing the %rrorPro&ider for :eneral +nformation


The problem with tooltips displayed in this way is that the user may not $now that they%re available 3when was the last time you hovered your pointer over a te(t bo( loo$ing for help?4. 7uc$ily' "rror8rovider is really good at providing a visual indicator' so it could be used with a different icon to show something li$e #igure 3.I.
L8M

L8M

* got the sample icon from +ommonIOGraphicsOiconsO+omputerOW9F BT0H.*+9 in my -5.!"T installation directory. #eel free to use

whatever icon ma$es you happy.

Figure .0. Com9ining the ToolTip Component with the %rrorPro&ider Component

*f you li$e this $ind of thing' it can be implemented using two error providers: one with a friendly information icon' and a second one with a mean error icon 3as set using the "rror8rovider%s *con property4. The information provider is used when the form is first loaded as well as when there%s no error. 9therwise' the error provider is used' as shown here:
$oi Loan)##lication9ialo*%Loa (o&'ect sen er, ($ent)r*s e) "

// Ise tooltips to populate the information pro#ider foreach+ Control control in Ket'llControls+. . 4 string toolTip 1 toolTipB.KetToolTip+control./ if+ toolTip.Cength 11 A . continue/ infoPro#ider.SetError+control- toolTip./ 5

$oi a##licantBame0ext+ox%Cali atin*(o&'ect sen er, Cancel($ent)r*s e) " strin* tool0i# 1 tool0i#5.Eet0ool0i#((Control)sen er); i,( ((0ext+ox)sen er).0ext.Len*th 11 ? ) "
// Show the error when there is no te*t in the te*t !o* errorPro#ider.SetError++Control.sender- toolTip./ infoPro#ider.SetError++Control.sender- null./

e.Cancel 1 tr/e; else "


// Show the info when there is te*t in the te*t !o* errorPro#ider.SetError++Control.sender- null./ infoPro#ider.SetError++Control.sender- toolTip./

Eust as the ToolTip component adds the ToolTip e(tender property to each control on the form' the "rror8rovider adds an "rror property to each control. 5etting a control%s "rror property in the 8roperty Browser is the e)uivalent of calling 5et"rror for that control. 6owever' the "rror property is not a good place to store a message' because clearing the message is the only way to hide the "rror8rovider for a particular control. *nstead' given that the ToolTip property never needs clearing' the e(ample uses it whenever a message should be displayed: when the mouse is hovered over a control' when the information provider is showing' or when the error provider is showing. This has the added benefit of $eeping hard/coded strings out of the code and in a place that can easily be made locali=able' as discussed in +hapter 10: @esources.

Handling the Help "utton and F1


0lthough the little icons on the forms are useful' you may not want to use this $ind of nonstandard interface to provide help messages in your forms. The standard way to provide this $ind of information is to use the help button 3the )uestion mar$4 in the upper/right corner of the form' which is enabled by setting the 6elpButton property of the form to true. When that button is pressed' the cursor changes' and when the user clic$s on a control' the 6elp@e)uested event is fired to the form:
$oi Loan)##lication9ialo*%Hel#2e8/este ( o&'ect sen er, Hel#($ent)r*s e) " .. Con$ert screen coor inates to client coor inates Goint #t 1 this.Goint0oClient(e.Mo/seGos); .. Loo4 man/ally ,or control /ser clic4e on .. BD0(: EetChil )tGoint oesnFt eal !ell !ith .. containe controls, s/ch as controls in a *ro/# &ox Control controlBee in*Hel# 1 n/ll; ,oreach( Control control in Eet)llControls() ) " i,( control.+o/n s.Contains(#t) ) " controlBee in*Hel# 1 control; &rea4; .. Sho! hel# strin* hel# 1 tool0i#5.Eet0ool0i#(controlBee in*Hel#); i,( hel#.Len*th 11 ? ) ret/rn; Messa*e+ox.Sho!(hel#, "Hel#");
e.,andled 1 true/

!otice that the 6elp@e)uested handler uses both of the 6elp"vent0rgs properties:
class Hel#($ent)r*s : ($ent)r*s "

#/&lic &ool Han le " *et; set; #/&lic Goint Mo/seGos " *et; -

ouse8os is the screen coordinates where the user clic$ed' and 6andled lets us stop the 6elp@e)uested event from going any further if we handle it. *n the e(ample' we%re converting ouse8os' provided in screen coordinates' to client coordinates and wal$ing the list of controls loo$ing for the one the user clic$ed on. *f we find it' we put the control%s tooltip into a message bo( and stop the event from propagating elsewhere. The help button is useful to most users' but $eyboard/oriented Windows users will be more familiar with the #1 $ey' which is meant to communicate to the application that help is re)uested on whatever is currently active' which is normally the control with focus. 8ressing #1 also results in the 6elp@e)uested event being fired. 6owever' you%ll notice that the 6elp"vent0rgs class provides no indication of how the event was fired. 5o if we want to do something such as open an 6T 7 file when #1 is pressed' we must chec$ whether it was a mouse button that triggered the event:
$oi Loan)##lication9ialo*%Hel#2e8/este (o&'ect sen er, Hel#($ent)r*s e) "
// ;f no mouse !utton was clic6ed- FB got us here if+ Control.$ouse)uttons 11 $ouse)uttons.None . 4 5 // ,elp !utton got us here else 4 5

.. o#en a hel# ,ile...

.. sho! the messa*e &ox...

Because we $now that a mouse clic$ triggers the 6elp@e)uested event when it comes from the help button' we need to $now whether any mouse buttons were pressed when the 6elp@e)uested event was fired. To do this' we chec$ the +ontrol. ouseButtons property' which provides the state of the mouse buttons during the current event. *f no buttons were pressed to fire this event' the user got to the handler using the #1 $eyP otherwise' the user got here using the help button.

7sing HT8$ Help


When you implement #1' it is not hard to launch an 1@7 to show an 6T 7 page. 6owever' when using the help button' users are accustomed to seeing help messages in shadowed tooltips' $nown as pop(&p "elp' as shown in #igure 3.8. Figure .2. 7sing HelpPro&ider to +mplement the Help "utton

*mplementing pop/up help isn%t supported by the ToolTip component because it doesn%t show shadows and doesn%t provide a programmatic way to re)uest that a tooltip to be shown. 6owever' the 6elp class supports both of these features with the 5how8opup method:
$oi Loan)##lication9ialo*%Hel#2e8/este (o&'ect sen er, Hel#($ent)r*s e) " i,( Control.Mo/se+/ttons 11 Mo/se+/ttons.Bone ) " .. o#en a hel# ,ile... .. Hel# &/tton *ot /s here else " .. Loo4 ,or control /ser clic4e on Goint #t 1 this.Goint0oClient(e.Mo/seGos); ,oreach( Control control in Eet)llControls() ) " i,( control.+o/n s.Contains(#t) ) " .. Sho! hel#
string help 1 toolTipB.KetToolTip+control./

i,( hel#.Len*th 11 ? ) ret/rn; e.Han le &rea4; 1 tr/e;

,elp.ShowPopup+this- help- e.$ousePos./

The 6elp class is a wrapper around the 6T 7 6elp functions provided by Windows and provides the following interesting methods:
class Hel# " #/&lic static $oi

Sho!Hel#(Control #arent, strin* /rl);

#/&lic static $oi Sho!Hel#( Control #arent, strin* /rl, Hel#Ba$i*ator comman , o&'ect #aram); #/&lic static $oi Sho!Hel#(

Control #arent, strin* /rl, strin* 4ey!or ); #/&lic static $oi Sho!Hel#( Control #arent, strin* /rl, Hel#Ba$i*ator na$i*ator); #/&lic static $oi Sho!Hel#6n ex(Control #arent, strin* /rl);

#/&lic static $oi Sho!Go#/#(Control #arent, strin* ca#tion, Goint location);

The 5how8opup method that we used in the preceding e(ample ta$es a control to act as a parent' the string to show' and a point' in screen coordinates' on which to center the resulting tooltip. To implement the #1 $ey by opening an 6T 7 file' you can opt to use one of the 5how6elp functions:
$oi Loan)##lication9ialo*%Hel#2e8/este (o&'ect sen er, Hel#($ent)r*s e) " .. F5 i,( Control.Mo/se+/ttons 11 Mo/se+/ttons.Bone ) " strin* ,ile 1 Path.KetFullPath+ loan'pplication0ialog.htm ./
,elp.Show,elp+this- file./

e.Han le ... -

1 tr/e;

!otice that we%re using the 8ath.Get#ull8ath method 3from the 5ystem. *9 namespace4 to turn a relative path name into a full path name. The 1@7 argument to the 5how6elp methods can be a full file path or a full 1@7' but 5how6elp doesn%t seem to li$e relative path names. 1sing this techni)ue' #1 will ta$e users to a page of 6T 7 describing the form as a whole. 6owever' users pressing #1 would probably prefer help that is specific to the control that%s currently activeP in other words' if they press #1 while in the 7oan 0mount field' they%d li$e to see help about the 7oan 0mount field. #or that to happen against a file in the local file system re)uires moving from 6T 7 to icrosoft%s compiled 6T 7 6elp format.

Compiled HT8$ Help


When it was clear that 6T 7 files were more fle(ible than the Win6elp help file format' icrosoft decided to ma$e the switch from Win6elp to something 6T 7/based. 6owever' Win6elp had a number of advantages over raw 6T 7' including tools for inde(ing' searching' and having multiple pages in a single file. The result of merging the fle(ibility of 6T 7 with the convenience of Win6elp yielded 6T 7 6elp' which consists of a set of functions' a set of tools' and a file format that compiles all pages into a single file with a .chm e(tension. The details of how to build 6T 7 6elp files are beyond the

scope of this boo$' so * recommend downloading the 6T 7 6elp Wor$shop from the icrosoft Aeveloper !etwor$ site to e(periment with it yourself.
L9M L9M

http:CCmsdn.microsoft.com

The instructions for creating a minimal 6T 7 6elp file with the 6T 7 6elp Wor$shop are as follows: 1. @un the 6T 7 6elp Wor$shop. >. +reate a new pro<ect. This is the list of files that goes into creating a .chm file. 3. +reate a new 6T 7 file. 0dd some te(t to the UbodyV tag and save the file. This file will become a topic page. H. a$e sure that the 8ro<ect tab is selected' and clic$ on the 0ddC@emove Topic #iles button. 0dd the 6T 7 file you created and saved in step 3. This action adds the topic file to the pro<ect. F. +lic$ on the +ontents tab and choose +reate a !ew +ontents #ile. This enables the table of contents. S. a$e sure that the +ontents tab is selected' and clic$ on the *nsert a 8age button. 0dd the 6T 7 file from the previous steps' and ma$e sure that the "ntry Title field has a value before pressing 9D. This adds an entry to the table of contents. I. +lic$ on the *nde( tab and choose +reate a !ew *nde( #ile. This enables the inde(. #eel free to add a $eyword or two to populate the inde(. 8. +lic$ on the 8ro<ect tab again' and clic$ on the +hange 8ro<ect 9ptions button. +hoose the +ompiler tab. "nable the +ompile #ull/Te(t 5earching *nformation option. This enables search. 9. +ompile and view. When you%ve got an 6T 7 6elp file' you can integrate it into your form using the 6elp class by passing the name of the .chm file to the 5how6elp function. #urthermore' if you%d li$e to scroll to a particular subtopic inside a topic' you can do so by first using the 6T 7 UaV tag to name a subtopic:
=3:: loana##lication ialo*.htm ::> =html> =hea > =title>loan a##lication ialo*=.title> =.hea > =&o y> =h5><a name1 name ='pplicant Name</a==.h5> Glease enter a name. =h5><a name1 phoneno ='pplicant Phone #</a==.h5> Glease enter a #hone n/m&er. =h5><a name1 loanamount ='pplicant Coan 'mount</a==.h5>

Glease enter a loan amo/nt. =.&o y> =.html>

When you%ve done that' you can map the name of the subtopic to the control when #1 is pressed:
$oi Loan)##lication9ialo*%Hel#2e8/este (o&'ect sen er, Hel#($ent)r*s e) " .. F5 i,( Control.Mo/se+/ttons 11 Mo/se+/ttons.Bone ) "
string su!topic 1 null/ if+ this.'cti#eControl 11 this.applicantNameTe*t)o*. 4 su!topic 1 name / 5 else if+ this.'cti#eControl 11 this.applicantPhoneNoTe*t)o* . 4 su!topic 1 phoneNo / 5 else if+ this.'cti#eControl 11 this.applicantCoan'mountTe*t)o* . 4 su!topic 1 loan'mount / 5 ,elp.Show,elp+this- dialogs.chm loan'pplication0ialog.htm# 7 su!topic./

e.Han le ... -

1 tr/e;

!ow when #1 is pressed and focus is on a specific control' the topic is brought up in the help viewer window' and the specific subtopic is scrolled into view' as shown in #igure 3.9. Figure .4. ,howing the loan1mount ,u9topic

6owever' now that we%re bac$ to mapping between controls and strings 3subtopics' in this case4' this is a perfect use for a component that provides e(tender properties. The e(tender

properties would allow you to set the help information for each control using the 8roperty Browser' $eeping that information out of the code. The component that provides e(tender properties to manage this information is 6elp8rovider.

7sing the HelpPro&ider Component


6elp8rovider actually implements both topic navigation support for the #1 $ey and pop/up help for the help button. 6elp8rovider is a wrapper around the 6elp class for a specific file' so it wor$s well only for 6T 7 6elp. 0fter dropping a 6elp8rovider component onto your form' you should set its 6elp!amespace property to the name of the file it should manage' such as dialogs.chm. 6elp8rovider adds the following properties to each control on a form:
strin* Hel#Ley!or .. 9e,a/lts to "" Hel#Ba$i*ator Hel#Ba$i*ator; .. 9e,a/lts to )ssociate6n ex strin* Hel#Strin*; .. 9e,a/lts to "" &ool Sho!Hel#; .. 9e,a/lts to tr/e

When #1 is pressed' an empty 6elpDeyword results in the 6elp5tring being shown using pop/up help. 9therwise' #1 causes the 6elpDeyword to be passed to 5how6elp and used based on the 6elp!avigator property' which can be one of the following:
en/m Hel#Ba$i*ator " )ssociate6n ex, Fin , 6n ex, .. What Sho!Hel#6n ex oes Ley!or 6n ex, 0a&leD,Contents, 0o#ic, .. 0he e,a/lt ,or Sho!Hel# -

#or e(ample' if 6elp!avigator is Topic' then 6elpDeyword is the name of the topic to show.say' loan0pplicationAialog.htm. 5how6elp is a Boolean that determines whether the 6elp8rovider should handle the 6elp@e)uested event for the control. 5etting 5how6elp to false allows you to handle the 6elp@e)uested event manually' as we%ve been doing so far. 5o after dropping a 6elp8rovider component onto our sample form' we don%t have to handle the 6elp@e)uested event at all. *nstead' given the 6elp!amespace property set to dialogs.chm' we can set the 6elp8rovider properties on each control in the form 3as shown in Table 3.14. This action causes #1 and the help button to be handled automatically.

Ta9le .1. ,ample HelpPro&ider ,ettings


Control ,elpPeyword ,elpNa#igator ,elpString Show,elp

applicant!ame Te(tBo( applicant8hone !oTe(tBo( applicant7oan 0mount

loan0pplication Aialog.htm ,name loan0pplication Aialog.htm ,phone!o loan0pplication Aialog.htm ,loan0mount

Topic Topic Topic

&8lease enter a name& &8lease enter a phone number& &8lease enter a loan amount&

True True True

,howing Help Contents, +nde3, and ,earch


Aialogs don%t often have menus.let alone 6elp menus with +ontents' *nde(' and 5earch menu items.but while we%re on the topic of integrating help with forms' * thought it would be a good idea to mention how to implement these help menu items. Jou can do this most easily by using the 6elp class:
$oi Loan)##lication9ialo*%Hel#2e8/este (o&'ect sen er, Hel#($ent)r*s e) " $oi hel#ContentsMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "
,elp.Show,elp+this- dialogs.chm - ,elpNa#igator.Ta!leJfContents./

$oi $oi hel#SearchMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "


./ ,elp.Show,elp+this- dialogs.chm - ,elpNa#igator.Find-

hel#6n exMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "

,elp.Show,elp;nde*+this- dialogs.chm ./

Where Are We?


This chapter dealt with topics that are often dialog/related: getting data in and out' validating data' and letting users $now about the re)uired data format 3including providing access to online help4. But none of these topics is specific to &dialogs& 3which is a slippery term to get hold of anyway4. 6owever' in this chapter and +hapter >: #orms' we%ve covered almost everything a programmer needs to $now about forms. *f there are things that you haven%t seen that interest you' such as drag and drop and form locali=ation' you%ll want to

read on' especially +hapter 8: +ontrols 3for drag and drop4 and +hapter 10: @esources 3for form locali=ation4.

Chapter #. Drawing "asics


0s handy as forms are' especially when laden with controls' sometimes the built/in controls aren%t sufficient to render the state of your application. *n that case' you need to draw the state yourself. The drawing may be to the screen' to a file' or to a printer' but wherever you%re drawing to' you%ll be dealing with the same primitives.colors' brushes' pens' and fonts.and the same $inds of things to draw: shapes' images' and strings. This chapter starts by e(amining the basics of drawing to the screen and the building bloc$s of drawing.
L1M L1M

The standard controls that come with Win#orms are listed in 0ppendi( A: 5tandard Win#orms +omponents and +ontrols.

!ote that all the drawing techni)ues discussed in this chapter and in the ne(t two chapters relate e)ually as well to controls as they do to forms. #or information about building custom controls' see +hapter 8: +ontrols. 9ne more thing worth noting before we begin is that the 5ystem.Arawing namespace is implemented on top of GA*; 3Graphics Aevice *nterface;4' the successor to GA*. The original GA* has been a mainstay in Windows since there was a Windows' providing an abstraction over screens and printers to ma$e writing G1*/style applications easy. GA*; is a Win3> A77 3gdiplus.dll4 that ships with Windows T8 and is available for older versions of Windows. GA*; is also an unmanaged +;; class library that wraps gdiplus.dll. Because the 5ystem.Arawing classes share many of the same names with the GA*; +;; classes' you may very well stumble onto the unmanaged classes when loo$ing for the .!"T classes in the online documentation. The concepts are the same' but the coding details are very different between unmanaged +;; and managed anything else' so $eep an eye out.
L>M L>M

GA* programming certainly isn%t easy when compared with 5ystem.Arawing programming' but it%s orders of magnitude easier than supporting

printers and video display adapters by hand' which was what A95 programmers had to do to put bread on the table.

#rawing to the Screen


!o matter what $ind of drawing you%re doing' the underlying abstraction that you%re dealing with is the Graphics class from the 5ystem.Arawing namespace. The Graphics class provides the abstract surface on which you%re drawing' whether the results of your drawing operations are displayed on the screen' stored in a file' or spooled to the printer. The

Graphics class is too large to show here' but we%ll come bac$ to it again and again throughout the chapter. 9ne way to obtain a graphics ob<ect is to use +reateGraphics to create a new one associated with a form:
&ool ra!(lli#se 1 ,alse;

$oi ra!(lli#se+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " .. 0o**le !hether or not to ra! the elli#se ra!(lli#se 1 3 ra!(lli#se;
Kraphics g 1 this.CreateKraphics+./ try 4

5 finally 4 g.0ispose+./ 5

i,( ra!(lli#se ) " .. 9ra! the elli#se *.Fill(lli#se(+r/shes.9ar4+l/e, this.Client2ectan*le); else " .. (rase the #re$io/sly ra!n elli#se *.Fill(lli#se(System+r/shes.Control, this.Client2ectan*le); -

0fter we have a graphics ob<ect' we can use it to draw on the form. Because we%re using the button to toggle whether or not to draw the ellipse' we either draw an ellipse in dar$ blue or use the system color as the bac$ground of the form. 0ll that%s fine' but you may wonder what the try/catch bloc$ is for. Because the graphics ob<ect holds an underlying resource managed by Windows' we%re responsible for releasing the resource when we%re finished' even in the face of an e(ception' and that is what the try/finally bloc$ is for. The Graphics class' li$e many classes in .!"T' implements the *Aisposable interface. When an ob<ect implements the *Aisposable interface' that%s a signal for the client of that ob<ect to call the *Aisposable Aispose method when the client is finished with the ob<ect. This lets the ob<ect $now that it%s time to clean up any resources it%s holding' such as a file or a database connection. *n this case' the Graphic class%s implementation of *Aisposable Aispose can release the underlying graphics ob<ect that it%s maintaining. To simplify things in +,' the try/catch bloc$ can be replaced with a &sing bloc$:
$oi ra!(lli#se+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

using+ Kraphics g 1 this.CreateKraphics+. . 4

5 // g.0ispose called automatically here

*.Fill(lli#se(+r/shes.9ar4+l/e, this.Client2ectan*le);

The +, using bloc$ wraps the code it contains in a try bloc$ and always calls the *Aisposable Aispose method at the end of the bloc$ for ob<ects created as part of the using clause. This is a convenient shortcut for +, programmers. *t%s a good practice to get into and something you%ll see used e(tensively in the rest of this boo$.

Handling the Paint %&ent


0fter we%ve got the Graphics resources managed properly' we have another issue: When the form is resi=ed or covered and uncovered' the ellipse is not automatically redrawn. To deal with this' Windows as$s a form 3and all child controls4 to redraw newly uncovered content via the 8aint event' which provides a 8aint"vent0rgs argument:
class Gaint($ent)r*s " #/&lic 2ectan*le Cli#2ectan*le " *et; #/&lic Era#hics Era#hics " *et; &ool $oi ra!(lli#se 1 ,alse; ra!(lli#se+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " ra!(lli#se 1 3 ra!(lli#se;

#oid 0rawingForm8Paint+o!9ect sender- PaintE#ent'rgs e. 4

i,( 3 ra!(lli#se ) ret/rn;


Kraphics g 1 e.Kraphics/ 5

*.Fill(lli#se(+r/shes.9ar4+l/e, this.Client2ectan*le);

By the time the 8aint event is fired' the bac$ground of the form has already been drawn' so any ellipse that was drawn during the last 8aint event will be goneP this means that we must draw the ellipse only if the flag is set to true. 6owever' even if we set the flag to draw the ellipse' Windows doesn%t $now that the state of the flag has changed' so the 8aint event won%t be triggered and the form won%t get a chance to draw the ellipse. To avoid the need to draw the ellipse in the button%s +lic$ event as well as the form%s 8aint event' we must re)uest a 8aint event and let Windows $now that the form needs to be redrawn.
L3M L3M

0 form or control can draw its own bac$ground by overriding the 9n8aintBac$ground method.

Triggering the Paint %&ent


To re)uest a 8aint event' we use the *nvalidate method:

$oi

ra!(lli#se+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " ra!(lli#se 1 3 ra!(lli#se;

this.;n#alidate+true./ // 's6 Windows for a Paint e#ent // for the form and its children

!ow' when the user toggles the flag' we call *nvalidate to let Windows $now that a part of the form needs to be redrawn. 6owever' because drawing is one of the more e(pensive operations' Windows will first handle all other events.such as mouse movements' $eyboard entry' and so on.before firing the 8aint event' <ust in case multiple areas of the form need to be redrawn at the same time. To avoid this delay' use the 1pdate method' which forces Windows to handle the 8aint event immediately. Because invalidating and updating the entire client area of a form are so common' forms also have a @efresh method that combines the two:
$oi ra!(lli#se+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " ra!(lli#se 1 3 ra!(lli#se;

// Can do one or the other this.;n#alidate+true./ // 's6 Windows for a Paint e#ent // for the form and its children this.Ipdate+./ // Force the Paint e#ent to happen now // Jr can do !oth at once this."efresh+./ // ;n#alidate+true. 7 Ipdate

6owever' if you can wait' it%s best to let Windows handle the 8aint event in its own sweet time. *t%s delayed for a reason: *t%s the slowest thing that the system does. #orcing all paints to happen immediately eliminates an important optimi=ation. *f you%ve been following along with this simple e(ample' you%ll be pleased to see that pressing the button toggles whether or not the ellipse is shown on the form and that covering and uncovering the form draws as e(pected. 6owever' if you resi=e the form' you%ll be disappointed' as shown in #igures H.1 and H.>. Figure #.1. %llipse Form 9efore /esi-ing

Figure #.2. %llipse Form after /esi-ing

!otice that in #igure H.>' it seems as if the ellipse is being drawn several times' but incompletely' as the form is resi=ed. What%s happening is that as the form is being e(panded' Windows is drawing only the newly e(posed rectangle' assuming that the e(isting rectangle doesn%t need to be redrawn. 0lthough we%re redrawing the entire ellipse during each 8aint event' Windows is ignoring everything outside the clip region'that part of the form that needs redrawing.and that leads to the strange drawing behavior. 7uc$ily' you can set a st#le to re)uest that Windows redraw the entire form during a resi=e:
#/&lic 9ra!in*Form() " .. 2e8/ire ,or Win o!s Form 9esi*ner s/##ort 6nitiali7eCom#onent();
// Trigger a Paint e#ent when the form is resi&ed this.SetStyle+ControlStyles."esi&e"edraw- true./

#orms 3and controls4 have several drawing styles 3you%ll see more in +hapter S: 0dvanced Arawing4. The @esi=e@edraw style causes Windows to redraw the entire client area whenever the form is resi=ed. 9f course' this is less efficient' and that%s why Windows defaults to the original behavior.

Colors
5o far' *%ve been drawing the ellipse in my form using a built/in dar$ blue brush. 0 br&s"' as you%ll see' is for filling the interior of a shape' whereas a pen is used to draw the edge of

a shape. "ither way' suppose *%m not )uite happy with the dar$ blue brush. *nstead' *%d li$e a brush of one of the more than 1S million colors that doesn%t come prebuilt for me' and this means that * first need to specify the color in which *%m interested. +olor is modeled in .!"T via the +olor structure:
str/ct Color " .. Bo color #/&lic static rea only Color (m#ty; .. +/ilt:in colors #/&lic static Color )lice+l/e " *et; .. ... #/&lic static Color Mello!Ereen " *et; .. Gro#erties #/&lic &yte ) " *et; #/&lic &yte + " *et; #/&lic &yte E " *et; #/&lic &ool 6s(m#ty " *et; #/&lic &ool 6sLno!nColor " *et; #/&lic &ool 6sBame Color " *et; #/&lic &ool 6sSystemColor " *et; #/&lic strin* Bame " *et; #/&lic &yte 2 " *et; .. Metho s #/&lic static Color From)r*&(int al#ha, Color &aseColor); #/&lic static Color From)r*&(int al#ha, int re , int *reen, int &l/e); #/&lic static Color From)r*&(int ar*&); #/&lic static Color From)r*&(int re , int *reen, int &l/e); #/&lic static Color FromLno!nColor(Lno!nColor color); #/&lic static Color FromBame(strin* name); #/&lic ,loat Eet+ri*htness(); #/&lic ,loat EetH/e(); #/&lic ,loat EetSat/ration(); #/&lic int 0o)r*&(); #/&lic Lno!nColor 0oLno!nColor();

#undamentally' a +olor ob<ect represents four values: the amount of red' green' and blue color and the amount of opacity. The red' green' and blue elements are often referred to together as @GB 3red/green/blue4' and each ranges from 0 to >FF' with 0 being the smallest amount of color and >FF being the greatest amount of color. The degree of opacity is specified by an alp"a value' which is sometimes seen together with @GB as 0@BG 30lpha/@BG4. The alpha value ranges from 0 to >FF' where 0 is completely transparent and >FF is completely opa)ue. *nstead of using a constructor' you create a +olor ob<ect by using the #rom0rbg method' passing brightness settings of red' green' and blue:

Color Color Color Color Color

re 1 *reen 1 &l/e 1 !hite 1 &lac4 1

Color.From)r*&(@AA, ?, ?); .. @AA re , ? &l/e, ? *reen Color.From)r*&(?, @AA, ?); .. ? re , @AA &l/e, ? *reen Color.From)r*&(?, ?, @AA); .. ? re , ? &l/e, @AA *reen Color.From)r*&(@AA, @AA, @AA); .. !hite Color.From)r*&(?, ?, ?); .. &lac4

*f you%d li$e to specify the degree of transparency as well' you pass an alpha value:
Color &l/e@AGercentD#a8/e 1 Color.From)r*&(GHHQB/S, ?, ?, @AA); Color &l/eKAGercentD#a8/e 1 Color.From)r*&(GHHQ@/S , ?, ?, @AA);

The three 8/bit color values and the 8/bit alpha value ma$e up the four parts of a single value that defines the 3>/bit color that modern video display adaptors can handle. *f you prefer to pass the four values combined into the single 3>/bit value' you can do that with another of the overloads' although it%s fairly aw$ward and therefore usually avoided:
.. ) 1 5T5, 2 1 ?, E 1 ?, + 1 @AA Color &l/eKAGercentD#ache 1 Color.From)r*&(?BADAHBYFYH);

<nown Colors
9ften' the color you%re interested in already has a well/$nown name' and this means that it will already be available from the static fields of +olor that define $nown colors' from the Dnown+olor enumeration' and by name:
Color &l/e5 1 Color.+l/eCiolet; Color &l/e@ 1 Color.FromLno!nColor(Lno!nColor.+l/eCiolet); Color &l/eN 1 Color.FromBame("+l/eCiolet");

*n addition to 1H1 colors with names such as 0liceBlue and 9ld7ace' the Dnown+olor enumeration has >S values describing the current colors assigned to various parts of the Windows 1*' such as the color of the border on the active window and the color of the default bac$ground of a control. These colors are handy when you%re doing custom drawing and you%d li$e to match the rest of the system. The system color values of the Dnown+olor enumeration are shown here:
en/m Lno!nColor " .. Bonsystem colors eli e ... )cti$e+or er, )cti$eCa#tion, )cti$eCa#tion0ext, )##Wor4s#ace, Control,

Control9ar4, Control9ar49ar4, ControlLi*ht, ControlLi*htLi*ht, Control0ext, 9es4to#, Eray0ext Hi*hli*ht, Hi*hli*ht0ext, Hot0rac4, 6nacti$e+or er, 6nacti$eCa#tion, 6nacti$eCa#tion0ext, 6n,o, 6n,o0ext, Men/, Men/0ext, Scroll+ar, Win o!, Win o!Frame, Win o!0ext,

*f you%d li$e to use one of the system colors without creating your own instance of the +olor class' you can access them already created for you and e(posed as properties of the 5ystem+olors class:
seale class SystemColors " .. Gro#erties #/&lic static Color )cti$e+or er " *et; #/&lic static Color )cti$eCa#tion " *et; #/&lic static Color )cti$eCa#tion0ext " *et; #/&lic static Color )##Wor4s#ace " *et; #/&lic static Color Control " *et; #/&lic static Color Control9ar4 " *et; #/&lic static Color Control9ar49ar4 " *et; #/&lic static Color ControlLi*ht " *et; #/&lic static Color ControlLi*htLi*ht " *et; #/&lic static Color Control0ext " *et; #/&lic static Color 9es4to# " *et; #/&lic static Color Eray0ext " *et; #/&lic static Color Hi*hli*ht " *et; #/&lic static Color Hi*hli*ht0ext " *et; #/&lic static Color Hot0rac4 " *et; #/&lic static Color 6nacti$e+or er " *et; #/&lic static Color 6nacti$eCa#tion " *et; #/&lic static Color 6nacti$eCa#tion0ext " *et; #/&lic static Color 6n,o " *et; #/&lic static Color 6n,o0ext " *et; #/&lic static Color Men/ " *et; #/&lic static Color Men/0ext " *et; #/&lic static Color Scroll+ar " *et; #/&lic static Color Win o! " *et; #/&lic static Color Win o!Frame " *et; -

#/&lic static Color Win o!0ext " *et; -

The following two lines yield +olor ob<ects with the same color values' and you can use whichever one you li$e:
Color color5 1 Color.FromLno!nColor(Lno!nColor.Eray0ext); Color color@ 1 SystemColors.Eray0ext;

Color Translation
*f you have a color in one of three other formats.6T 7' 97"' or Win3>.or you%d li$e to translate to one of these formats' you can use +olorTranslator' as shown here for 6T 7:
Color html+l/e 1 Color0ranslator.FromHtml("U????,,"); strin* html+l/e0oo 1 Color0ranslator.0oHtml(html+l/e);

When you have a +olor' you can get its alpha' red' blue' and green values as well as the color%s name' whether it%s a $nown color or a system color. Jou can also use these values to fill and frame shapes' which re)uire brushes and pens' respectively.

Print

+#)ail

Add "ote

Add Boo1-ar1

Windo/s .or-s Progra--ing in C> By Chris Sells Slots : $ Table of Contents

Chapter S. 0rawing )asics

Brushes
The 5ystem.Arawing.Brush class serves as a base class for several $inds of brushes' depending on your needs. #igure H.3 shows the five derived brush classes provided in the 5ystem.Arawing and 5ystem.Arawing.Arawing>A namespaces.

Figure #. . ,ample "rushes

#igure H.3 was created with the following code:


$oi +r/shesForm%Gaint(o&'ect sen er, Gaint($ent)r*s e) " Era#hics * 1 e.Era#hics; int x 1 ?; int y 1 ?; int !i th 1 this.Client2ectan*le.Wi th; int hei*ht 1 this.Client2ectan*le.Hei*ht.A; +r/sh !hite+r/sh 1 System.9ra!in*.+r/shes.White; +r/sh &lac4+r/sh 1 System.9ra!in*.+r/shes.+lac4; /sin*( )rush !rush 1 new Solid)rush+Color.0ar6)lue. ) " *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); *.9ra!Strin*(&r/sh.0oStrin*(), this.Font, !hite+r/sh, x, y); y ;1 hei*ht; strin* ,ile 1 V"c:\!in o!s\Santa Fe St/cco.&m#"; /sin*( )rush !rush 1 new Te*ture)rush+new )itmap+file.. ) " *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); *.9ra!Strin*(&r/sh.0oStrin*(), this.Font, !hite+r/sh, x, y); y ;1 hei*ht; /sin*(
)rush !rush 1 new ,atch)rush+ ,atchStyle.0i#ot- Color.0ar6)lue- Color.White.

) " *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); *.9ra!Strin*(&r/sh.0oStrin*(), this.Font, &lac4+r/sh, x, y);

y ;1 hei*ht;
)rush !rush 1 new CinearKradient)rush+ new "ectangle+*- y- width- height.Color.0ar6)lueColor.WhiteSH.Af. ) "

/sin*(

*.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); *.9ra!Strin*(&r/sh.0oStrin*(), this.Font, &lac4+r/sh, x, y); y ;1 hei*ht; GointIJ #oints 1 ne! GointIJ " Goint(x, y), Goint(x ; !i th, y), Goint(x ; !i th, y ; hei*ht), Goint(x, y ; hei*ht) -; /sin*( )rush !rush 1 new PathKradient)rush+points. ) " *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); *.9ra!Strin*(&r/sh.0oStrin*(), this.Font, &lac4+r/sh, x, y); y ;1 hei*ht; ne! ne! ne! ne!

,olid "rushes
0 5olidBrush is constructed with a color used to fill in the shape being drawn. 0s a convenience' because solid color brushes are heavily used' the Brushes class contains 1H1 Brush properties' one for each of the named colors in the Dnown+olor enumeration. These properties are handy because they%re resources cached and managed by .!"T itself' ma$ing them a bit easier to use than brushes you have to create yourself:
LHM LHM

*n fact' if you attempt to dispose of one of the .!"T/provided resources' such as pens' brushes' and so on' you%ll eventually get an e(ception'

either when you dispose of it in the first place or later when you try to use it again after it%s been disposed of.

.. Mana*e &y .B(0 +r/sh !hite+r/sh 1 System.9ra!in*.+r/shes.White; .. Mana*e &y yo/r #ro*ram /sin*( +r/sh myWhite+r/sh 1 ne! Soli +r/sh(Color.White) ) "...-

5imilarly' >1 of the >S system colors from the Dnown+olor enumeration are provided in the 5ystemBrushes class. This is handy if you want to use one of the system colors to create a brush but prefer to let Win#orms handle the underlying resource. The brushes that aren%t available by name from the 5ystemBrushes properties are still available using the #rom5ystem+olor method' which returns a brush that%s still managed by .!"T:
LFM LFM

GrayTe(t' *nactive+aptionTe(t' *nfoTe(t'

enuTe(t' and Window#rame are more commonly used for pens' not brushes' and that is why

they%re not represented in the class 5ystemBrushes but are represented in the 5ystem8ens class.

.. Callin* 9is#ose on this &r/sh !ill ca/se an exce#tion +r/sh &r/sh 1 System)rushes.FromSystemColor+SystemColors.;nfoTe*t.;

Te3ture "rushes
0 Te(tureBrush is constructed with an image. By default' the image is used repeatedly to tile the space inside the shape being drawn. Jou can change this behavior by choosing a member of the Wrap ode enumeration:
en/m Wra#Mo e " Clam#, .. ra! only once 0ile, .. e,a/lt 0ileFli#O, .. ,li# ima*e hori7ontally alon* O axis 0ileFli#M, .. ,li# ima*e $ertically alon* M axis 0ileFli#OM, .. ,li# ima*e alon* O an M axes -

#igure H.H shows the various modes. Figure #.#. =arious Te3ture"rush Wrap8ode =alues

Hatch "rushes
0 6atchBrush is used to fill space using one of several built/in two/color patterns' where the two colors are used to draw the foreground and the bac$ground of the pattern. #igure H.F shows the FS hatches in the 6atch5tyle enumeration using blac$ as the foreground color and white as the bac$ground color. Figure #.'. 1&aila9le Hatch "rush ,t!les ,hown with "lac5 Foreground and White "ac5ground

$inear :radient "rushes


0 7inearGradientBrush is used to draw a smooth blending between two end points and between two colors. The gradations are drawn at a specified angle' as defined either by passing a float or by passing one of four 7inearGradient ode values:
en/m LinearEra ientMo e " Hori7ontal, .. ? e*rees Certical, .. T? e*rees For!ar 9ia*onal, .. HA e*rees +ac4!ar 9ia*onal, .. 5NA e*rees -

The angle is used to set up a blend' which governs the transition between colors over the area of the brush along the angle of the line. Jou can set this blend either directly or indirectly. *n the direct techni)ue' you use a Blend property' which determines positions and factors of fall/out between the two colors. To set the blend indirectly' you use a foc&s point for the end color and a fall/out rate toward the start color' as shown in #igure H.S.

Figure #... (ormal, Triangle, and "ell $inear :radient "rushes

!otice that the normal linear gradient brush transitions between the start and end colors' whereas the triangle version transitions from the start color to the end color at some specified focus 3in this e(ample' it is set right in the middle4. #urthermore' the bell shape transitions toward the end color using a normal bell curve distribution. The following is the code that draws the first three brushes 3notice the use of the 5etBlendTriangular5hape and 5et5igmaBell5hape methods to ad<ust the blend4:
/sin*( LinearEra ient+r/sh &r/sh 1 ne! LinearEra ient+r/sh( this.Client2ectan*le, Color.White, Color.+lac4, LinearEra ientMo e.Hori7ontal) ) " .. Bormal: ,oc/s set at the en *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); *.9ra!Strin*("Bormal", this.Font, &lac4+r/sh, x, y); y ;1 hei*ht; .. 0rian*le .. Set ,oc/s in the mi le *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); *.9ra!Strin*("0rian*le", this.Font, &lac4+r/sh, x, y); y ;1 hei*ht;
!rush.Set)lendTriangularShape+A.Hf./

.. +ell .. Set ,oc/s in the mi le *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); *.9ra!Strin*("+ell", this.Font, &lac4+r/sh, x, y); y ;1 hei*ht;
!rush.SetSigma)ellShape+A.Hf./

... -

0t the bottom of #igure H.S' we%re still transitioning from white to blac$' but we%re transitioning through red in the middle. This is because we too$ over the blending with an instance of a +olorBlend ob<ect that lets us set custom colors and positions:
.. C/stom colors
Color)lend !lend 1 new Color)lend+./ !lend.Colors 1 new Color%( 4 Color.White- Color."ed- Color.)lac6- 5/ !lend.Positions 1 new float%( 4 A.Af- A.Hf- B.Af 5/ !rush.;nterpolationColors 1 !lend/

*.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht);

Path :radient "rushes


*n the unli$ely event that your linear gradient brush is not defined for the entire shape you%re drawing' the brush will be tiled <ust li$e a te(ture brush' as governed by the Wrap ode. *f you want to get even fancier than linear gradients along a single angle' you can use the 8athGradientBrush' as shown in #igure H.I. Figure #.0. Four ,ample 7ses of the Path:radient"rush Class

The 8athGradientBrush is defined by a set of points that define the surrounding edges of the path' a center point' and a set of colors for each point. By default' the color for each edge point is white' and for the center point is blac$. The gradient color transitions happen along each edge defined by the points toward the center. The triangle brush was created this way:

GointIJ triGoints 1 ne! GointIJ " ne! Goint(!i th.@, ?), ne! Goint(?, hei*ht), ne! Goint(!i th, hei*ht), -; /sin*( GathEra ient+r/sh &r/sh 1 ne! GathEra ient+r/sh(triGoints) ) " int x 1 ?; int y 1 ?; *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); -

!otice that we defined the three surrounding points in the 8oint array but didn%t define the center point e(plicitly. The center point is calculated based on the surrounding pointsP but it doesn%t need to be in the midpoint between all points' as shown by the diamond brush and the following code:
GointIJ iamon Goints 1 ne! GointIJ " ... -;

using+ PathKradient)rush !rush 1 new PathKradient)rush+diamondPoints. . 4

&r/sh.Wra#Mo e 1 Wra#Mo e.0ile;


!rush.CenterPoint 1 new Point+A- height/G./

int x 1 ?; int y 1 hei*ht; *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht);


5

!otice that we use the +enter8oint property to set the gradient endpoint to be along the left edge of the diamond. The center of a path gradient brush doesn%t even have to be inside the polygon described by the points' if you don%t want it to be. !otice also the use of the Wrap ode property. By default' this is set to +lamp' which causes the brush to draw only once in the upper/left corner. The points on the brush are relative to the client area' not to where they%re being used to fill' so we must set the Wrap ode if we want the brush to draw anywhere but in the upper/left corner. 0nother way to handle this is to apply a transform on the graphics ob<ect before drawing' a techni)ue described in +hapter S: 0dvanced Arawing. 0lthough it%s possible to describe a circle with a lot of points' it%s far easier to use a Graphics8ath ob<ect instead. 0 Graphics8ath is really a data structure that contains =ero or more shapes 3the Graphics8ath class is discussed in more detail later in this chapter4. *t%s useful for describing an area for drawing' <ust as we%re doing with the set of points describing our brush. The points are used by the 8athGradientBrush to create a Graphics8ath internally 3hence the name of this brush4' but we can create and use a Graphics8ath directly:
using+ KraphicsPath circle 1 new KraphicsPath+. . 4

circle.)

(lli#se(?, ?, !i th, hei*ht);

/sin*( GathEra ient+r/sh &r/sh 1 ne! GathEra ient+r/sh(circle) ) " &r/sh.Wra#Mo e 1 Wra#Mo e.0ile; &r/sh.S/rro/n Colors 1 ne! ColorIJ " Color.White -; .. e,a/lt &r/sh.CenterColor 1 Color.+lac4; .. e,a/lts to !hite int x 1 !i th; int y 1 hei*ht; *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht); 5

0fter we create an empty Graphics8ath ob<ect' notice the addition of an ellipse to the path before we use it to create a brush. The center of whatever set of shapes is in the path is used as the brush%s center point' <ust as you%d e(pect' but the center color defaults to white when we use a Graphics8athP that%s why the code manually sets the +enter+olor property to blac$. !otice also the use of the 5urround+olors property' which is an array of colors' one for each point on the gradient path. *f there are more points than colors 3as is clearly the case when we%re providing only a single color for all the points around the edge of a circle4' the last color in the array is used for all remaining points. #or e(ample' this code draws a red gradient from the first point of the triangle but uses blue for the other two points' as shown in #igure H.8:
/sin*( GathEra ient+r/sh &r/sh 1 ne! GathEra ient+r/sh(triGoints) ) "
!rush.SurroundColors 1 new Color%( 4 Color."ed- Color.)lue 5/

int x 1 ?; int y 1 ?; *.Fill2ectan*le(&r/sh, x, y, !i th, hei*ht);

Figure #.2. 1 Path:radient"rush with *ne /ed ,urrounding Point and Two "lue *nes

7i$e linear gradient brushes' path gradient brushes allow you to ad<ust the blend as well as the colors used to transition between start and end points.

$ens
Whereas the Brush classes are used to fill shapes' the 8en class is used to frame shapes. The interesting members are shown here:
seale class Gen : Marshal+y2e,D&'ect, 6Clonea&le, .. Constr/ctors #/&lic Gen(+r/sh &r/sh); #/&lic Gen(+r/sh &r/sh, ,loat !i th); #/&lic Gen(Color color); #/&lic Gen(Color color, ,loat !i th); .. Gro#erties #/&lic Gen)li*nment )li*nment " *et; set; #/&lic +r/sh +r/sh " *et; set; #/&lic Color Color " *et; set; #/&lic ,loatIJ Com#o/n )rray " *et; set; #/&lic C/stomLineCa# C/stom(n Ca# " *et; set; #/&lic C/stomLineCa# C/stomStartCa# " *et; set; #/&lic 9ashCa# 9ashCa# " *et; set; #/&lic ,loat 9ashD,,set " *et; set; #/&lic ,loatIJ 9ashGattern " *et; set; #/&lic 9ashStyle 9ashStyle " *et; set; #/&lic LineCa# (n Ca# " *et; set; #/&lic LineWoin LineWoin " *et; set; #/&lic ,loat MiterLimit " *et; set; #/&lic Gen0y#e Gen0y#e " *et; #/&lic LineCa# StartCa# " *et; set; #/&lic ,loat Wi th " *et; set; .. 0rans,ormation mem&ers eli e ... .. Metho s #/&lic $oi SetLineCa#(...); 69is#osa&le "

8ens have several interesting properties' including a width' a color or a brush' start and end cap styles' and a dash pattern for the line itself. 9ne note of interest is that the width of a pen is specified in the units of the underlying Graphics being drawn on 3more information about Graphics units is available in +hapter S: 0dvanced Arawing4. 6owever' no matter what the underlying units' a pen width of 0 always translates into a width of 1 physical unit on the underlying Graphic surface. This lets you specify the smallest visible pen width without worrying about the units of a particular surface. Jou%ll notice that the 8en class is sealed. This means that it can%t be used as a base class for further penli$e functionality. *nstead' each pen has a type that governs its behavior' as determined by the 8enType enumeration:

en/m Gen0y#e " Soli Color, .. Create ,rom a color or a Soli +r/sh 0ext/reFill, .. Create ,rom a 0ext/re+r/sh HatchFill, .. Create ,rom a Hatch+r/sh LinearEra ient, .. Create ,rom a LinearEra ient+r/sh GathEra ient, .. Create ,rom a GathEra ient+r/sh -

*f you%re interested in common' solid/color pens' the 1H1 named pens are provided as static 8en properties on the 8ens class' and 1F system pens are provided as static 8en properties on the 5ystem8ens class' providing the same usage as the corresponding Brushes and 5ystemBrushes classes. 0s with 5ystemBrushes' the #rom5ystem+olor method of the 5ystem8ens class returns a pen in one of the system colors that%s managed by .!"T.

$ine Caps
*n addition to their brushli$e behavior' pens have behavior at ends and <oints and along their length that brushes don%t have. #or e(ample' each end can have a different style' as determined by the 7ine+ap enumeration shown in #igure H.9. Figure #.4. %3amples from the $ineCap %numeration

0ll these lines were generated with a blac$ pen of width 1> passed to the Graphics.Araw7ine method. The white line of width 1 in the middle is drawn using a separate call to Graphics.Araw7ine to show the two end points that define the line. "ach blac$ pen is defined with the "nd+ap property set to a value from the 7ine+ap enumeration:
/sin*( Gen #en 1 ne! Gen(Color.+lac4, 5@) ) " #en.(n Ca# 1 LineCa#.Flat; .. e,a/lt *.9ra!Line(#en, x, y ; hei*htQ@.N, x ; !i thQ@.N, y ; hei*htQ@.N);

*.9ra!Line(!hiteGen, x, y ; hei*htQ@.N, x ; !i thQ@.N, y ; hei*htQ@.N); ... -

The default line cap style is flat' which is what all the 5tart+ap properties are set to. Jou%ll notice some familiar line cap styles' including flat' round' s)uare' and triangle' which have no anchor' as well as arrow' diamond' round' and s)uare' which have anchors. 0n anc"or indicates that part of the line cap e(tends beyond the width of the pen. The difference between s)uare and flat' on the other hand' dictates whether the line cap e(tends beyond the end of the line 3as s)uare does' but flat does not4. Jou can manage these $inds of drawing behaviors independently by using the 7ine+ap.+ustom enumeration value and setting the +ustom5tart+ap or +ustom"nd+ap field to a class that derives from the +ustom7ine+ap class 3from the 5ystem.Arawing.Arawing>A namespace4. The custom line cap in #igure H.9 shows a pen created using an instance of the 0d<ustable0rrow+ap class' the only custom end cap class that .!"T provides:
/sin*( Gen #en 1 ne! Gen(Color.+lac4, 5@) ) "
pen.EndCap 1 CineCap.Custom/ // width and height of @ and unfilled arrow head pen.CustomEndCap 1 new 'd9usta!le'rrowCap+@f- @f- false./

... -

Dashes
*n addition to the ends having special styles' a line can have a dash style' as defined by the Aash5tyle enumeration' shown in #igure H.10. Figure #.16. %3amples 7sing the Dash,t!le %numeration

"ach of the lines was created by setting the Aash5tyle property of the pen. The Aash5tyle.+ustom value is used to set custom dash and space lengths' where each length is

a multiplier of the width. #or e(ample' the following code draws the increasing length dashes shown in #igure H.10 with a constant space length:
/sin*( Gen #en 1 ne! Gen(Color.+lac4, 5@) ) "
pen.0ashStyle 1 0ashStyle.Custom/ // Set increasing dashes and constant spaces pen.0ashPattern 1 new float%( 4 Bf- Bf- Gf- Bf- @f- Bf- Sf- Bf- 5/

*.9ra!Line( #en, x ; 5?, y ; hei*htQ@.N, x ; !i th : @?, y ; hei*htQ@.N); -

*f you%d li$e to e(ercise more control over your custom dash settings' you can set the Aash+ap property on the pen to any of the values in the Aash+ap enumeration' which is a subset of the values in the 7ine+ap enumeration with only #lat 3the default4' @ound' and Triangle. To e(ercise more control over the line itself' in addition to dash settings' you can define compo&nd pens using the +ompound0rray property. This allows you to provide lines and spaces in parallel to the lines being drawn instead of perpendicularly' as dash settings do. #or e(ample' #igure H.11 was drawn with a pen set up this way:
/sin*( Gen #en 1 ne! Gen(Color.+lac4, @?) ) "

// Set percentages of width where line starts- then space starts// then line starts again- etc. in alternating pattern pen.Compound'rray 1 new float%( 4 A.Af- A.GHf- A.SHf- A.HHf- A.FHf- B.Af- 5/

*.9ra!2ectan*le(#en, ne! 2ectan*le(...)); -

Figure #.11. 1 ,ingle /ectangle Drawn with a Pen 7sing a Compound 1rra!

1lignments
ost of the e(amples' including #igure H.11' have shown pens of width greater than 1. When you draw a line of width greater than 1' the )uestion is' where do the e(tra pi(els go .above the line being drawn' below it' or somewhere else? The default pen alignment is

centered' which means that half the width goes inside the shape being drawn and the other half goes outside. The alignment can also be inset' which means that the entire width of the pen is inside the shape being drawn' as shown in #igure H.1>. Figure #.12. Pen 1lignment *ptions

*n #igure H.1>' both ellipses are drawn using a rectangle of the same dimensions 3as shown by the red line4' but the different alignments determine where the width of the line is drawn. There are actually several values in the 8en0lignment enumeration' but only +enter and *nset are currently supported' and *nset is used only for closed shapes 3an open figure has no &inside&4.

Boins
9ne final consideration you%ll have when drawing figures that have angles is what to do with the line at the angle. *n #igure H.13' the four values in the 8enEoin enumeration have been set in the 8en class%s 7ineEoin property before the rectangles were drawn 3again' a white line of width 1 is used to show the shape being drawn4. Figure #.1 . ,ample PenBoin =alues

!otice in #igure H.13 that each corner provides a different <oin. The one e(ception is iter+lipped' which changes between Bevel and iter dynamically based on the angle of the corner and the limit set by the iter7imit property.

Creating Pens from "rushes


5o far in this section on pens' all the e(amples have used solid/color pens. 6owever' you can also create a pen from a brush. #or e(ample' #igure H.1H shows an image you first encountered in +hapter 1: 6ello' Windows #orms. Figure #.1#. Creating a Pen from a $inear:radient"rush

The pen used to draw the lines in #igure H.1H was created from a 7inearGradientBrush:
/sin*( LinearEra ient+r/sh &r/sh 1 ne! LinearEra ient+r/sh( this.Client2ectan*le,

Color.(m#ty, .. i*nore Color.(m#ty, .. i*nore HA) ) " Color+len &len 1 ne! Color+len (); &len .Colors 1 ne! ColorIJ " Color.2e , Color.Ereen, Color.+l/e -; &len .Gositions 1 ne! ,loatIJ " ?, .A,, 5 -; &r/sh.6nter#olationColors 1 &len ;
using+ Pen pen 1 new Pen+!rush. . 4

...
5

The ability to create a pen from a brush lets you use any effect you can create using the multitude of brushes provided by 5ystem.Arawing.

Sha"es
!ow that you $now how to frame and fill shapes with pens and brushes' you might be interested in the shapes that are available. #igure H.1F shows them. Figure #.1'. The "asic ,hapes

0ll the shapes in #igure H.1F were edged using a ArawT(( function from the Graphics ob<ect for the form.for e(ample' Araw0rc and ArawBe=ier. The shapes that can be filled were drawn using a #illT(( function' such as #ill+losed+urve and #ill"llipse. !ot all of the shapes could be filled because not all of them are closed shapesP for e(ample' there is no #ill+urve. 6owever' all the open shapes 3e(cept the Be=ier4 have closed/shape e)uivalentsP for e(ample' a filled arc is called a pie.

0lso' notice the use of the 7ines shape. This shape could be drawn using multiple calls to the Araw7ine function' but three of the shapes.line' rectangle' and Be=ier.have helpers that draw more of them at once. *n addition to being convenient' these helpers handle the appropriate mitering at intersections that you%d otherwise have to do by hand. #or e(ample' the Graphics ob<ect provides all the following functions for drawing rectangles: Araw@ectangle' Araw@ectangles' #ill@ectangle' and #ill@ectangles.

Cur&es
ost of the shapes are specified as you%d e(pect. Jou specify the rectangle and the ellipse using an (' y' width' and height' or a @ectangle ob<ect. Jou specify the arc and the pie as with a rectangle' but you also include a start and a length of s eep' both specified in degrees 3the shown arc and pie start at 180 degrees and sweep for 180 degrees4. The lines and polygon are specified with an array of points' as are the curves' but the curves are a little different. The curve 3also $nown as a cardinal spline4 acts <ust li$e a set of lines' e(cept as a point is approached' there%s a curve instead of a sharp point. *n addition to a set of points' the curve is specified using a tension' which is a value that determines how &curvy& the curve is around the points. 0 tension of 0 indicates no curve' and a tension of 0.F is the default. The tension can get as high as allowed by the floating point type. #igure H.1S shows some common variations. Figure #.1.. Cur&es Drawn with =arious =alues of Tension

#igure H.1S shows the same set of points 3as indicated by the blac$ dots and inde( number4 drawn using the Araw+urve function with three different values of tension. 0s the tension increases' so does the amount of curve at each point.

1nli$e normal curves' Be=ier curves are specified with e(actly four points: one start point' followed by two control points' followed by an end point. *f the ArawBe=iers function is used to draw multiple curves' the end point of the preceding curve becomes the start point of the ne(t. #igure H.1I shows three Be=ier curves drawn using the same set of points' but in different orders. Figure #.10. Three "e-ier Cur&es Drawn 7sing the ,ame ,et of Points in Different *rders

*n each case' the Be=ier is drawn between the start point and the end point' but the two control points are used to determine the shape of the curve by e(erting more &control& over the curve as they get farther away.

,moothing 8odes
When drawing shapes' you may want the smooth rendering you%ve seen in the really cool applications. The shapes in #igures H.1F' H.1S' and H.1I were all drawn without any $ind of &smoothing'& as evidenced by the <agged edges. The <agged edges are caused by the swift transition between the color of the shape being drawn and the color of the bac$ground. 0 techni)ue $nown as antialiasing uses a smoother transition between the shape color and the bac$ground color' in much the same way that a gradient brush provides a smooth transition from one color to another. To turn on antialiasing for shapes subse)uently drawn on the Graphics ob<ect' you set the 5moothing ode property:
*.Smoothin*Mo e 1 Smoothin*Mo e.)nti)lias;

The default value of the 5moothing ode property is 5moothing ode.!one. *n addition to the 0nti0lias value' 5moothing ode has three other values: Aefault' 6igh5peed' and

6ighYuality. These are merely aliases for !one' !one' and 0nti0lias' depending on your system settings. #igure H.18 shows the difference between using and not using antialiasing. Figure #.12. The %ffect of Changing the ,moothing8ode from 1nti1lias to (one

!otice that setting the 5moothing ode has no effect on the te(t drawn on the Graphics ob<ect. Jou set the rendering effects of te(t using the Te(t@endering6int property' which * discuss in +hapter F: Arawing Te(t.

,a&ing and /estoring :raphics ,ettings


5etting the 5moothing ode in the preceding section is the first time we%ve changed a property on the Graphics ob<ect that affects subse)uent operations. Jou can also set other properties that affect subse)uent operations' and we%ll cover those topics as appropriate. When you change a property of a Graphics ob<ect in a method other than the 8aint event handler itself' it%s a good idea to reset it on the Graphics ob<ect before the method returns:
$oi 9ra!Somethin*(Era#hics *) "

// Sa#e old smoothing mode Smoothing$ode old$ode 1 g.Smoothing$ode/

.. Ma4e thin*s ra! smoothly *.Smoothin*Mo e 1 Smoothin*Mo e.)nti)lias; .. 9ra! thin*s...


// "estore smoothing mode g.Smoothing$ode 1 old$ode/

This can )uic$ly become painful when there are multiple properties to restore. 7uc$ily' you can save yourself the trouble by ta$ing a snapshot of a Graphics ob<ect state in a Graphics5tate ob<ect from the 5ystem.Arawing. Arawing>A namespace:

$oi

// Sa#e old graphics state KraphicsState oldState 1 g.Sa#e+./

9ra!Somethin*(Era#hics *) "

.. Ma e thin*s ra! smoothly *.Smoothin*Mo e 1 Smoothin*Mo e.)nti)lias; .. 9ra! thin*s...


// "estore old graphics state g."estore+oldState./

The 5ave method on the Graphics class returns the current state of the properties in the Graphics ob<ect. The call to @estore ta$es a Graphics5tate ob<ect and sets the Graphics ob<ect to the state cached in that ob<ect. The code shows a pair of calls to 5ave and @estore' but it%s not necessary to $eep them in balance' something that%s handy for switching a lot between a couple of states.

$aths
*n addition to using the basic shapes' you can compose and draw shapes together using a path. 0 pat"' modeled via the Graphics8ath class' is very much li$e a Graphics ob<ect' in that it%s a logical container of =ero or more shapes 3called fig&res or s&bpat"s4. The difference 3in addition to the fact that a Graphics ob<ect is bac$ed by a surface such as a screen or a printer4 is that the figures can be started and ended arbitrarily. This means that you can compose one or more complicated figures from a set of basic shapes. Jou collect figures into a path so that you can frame or fill them as a unit using a single brush or pen' which is applied when the path is drawn. #or e(ample' #igure H.19 shows a rounded rectangle 3a shape that the Graphics ob<ect can%t draw for you directly4. Figure #.14. 1 /ounded /ectangle Composed of 1rc Figures in a :raphicsPath *9ject

*magine a method called Get@ounded@ect8ath that ta$es a rectangle and a radius of an arc describing the curve. +alling the function returns a path' which can be filled and framed using the Graphics methods #ill8ath and #rame8ath:

Era#hics * 1 e.Era#hics; int !i th 1 this.Client2ectan*le.Wi th; int hei*ht 1 this.Client2ectan*le.Hei*ht; 2ectan*le rect 1 ne! 2ectan*le(5?, 5?, !i th : @?, hei*ht : @?);
using+ KraphicsPath path 1 Ket"ounded"ectPath+rect- width/BA. . 4 g.FillPath+)rushes.Rellow- path./ g.0rawPath+Pens.)lac6- path./ 5

"ven though the rounded rectangle path is composed of eight shapes 3four arcs and four lines4' the entire path is filled with one brush and framed with one pen. 6ere is the implementation of the method that composes the rounded rectangle:
Era#hicsGath Eet2o/n e 2ectGath(2ectan*le rect, int ra i/s) " int iameter 1 @ Q ra i/s; 2ectan*le arc2ect 1 ne! 2ectan*le(rect.Location, ne! Si7e( iameter, iameter));
KraphicsPath path 1 new KraphicsPath+./

.. to# le,t

path.'dd'rc+arc"ect- BYA- DA./

.. to# ri*ht arc2ect.O 1 rect.2i*ht :


path.'dd'rc+arc"ect- GFA- DA./

iameter;

.. &ottom ri*ht arc2ect.M 1 rect.+ottom :


path.'dd'rc+arc"ect- A- DA./

iameter;

.. &ottom le,t arc2ect.O 1 rect.Le,t;


path.CloseFigure+./

path.'dd'rc+arc"ect- DA- DA./

ret/rn #ath;

This function adds four arcs to the path.one at each of the corners of the rectangle. "ach shape added to the path will be filled or framed as appropriate when the path is drawn or filled. *n fact' notice that no pen or brush is used to add each shape. The pen or brush is provided when the path is drawn' not when the shapes are added. 0lso' notice that none of the lines is added e(plicitly. The first three lines are added implicitly by the path itself. 0s each new unclosed shape is added' the starting point of the new shape is <oined to the ending point of the last unclosed shape' creating a connected figure. 0fter the last arc is added' we call the +lose#igure method to <oin the ending point of that arc to the starting point of the first arc. *f +lose#igure had not been called' we%d still have a closed figure when the path was filled and framed' but the line connecting the top/

left arc with the bottom/left arc would be missing. 9n the other hand' adding a closed shape' such as a rectangle or an ellipse' will close itself' so there%s no need to call +lose#igure. *f' after calling +lose#igure' we were to add another shape' then another figure would be started for us implicitly. *f you%d li$e to start a new figure without closing the current figure' you can do so by calling 5tart#igure. #igure H.>0 shows what would happen if 5tart#igure were called after the second arc at the top right is added to the path. !otice that there would be two figures in the path' the first one unclosed because the second figure was started without closing the first. Figure #.26. ,tarting a (ew Figure in a Path Without Closing the Current Figure

8aths can add any of the shapes that the Graphics class can draw or fill. *n fact' paths are handy because they can be used to create closed figures that aren%t normally closed. #or e(ample' the following function returns a closed Be=ier' another shape that the Graphics class doesn%t provide directly:
Era#hicsGath EetClose +e7ierGath(2ectan*le rect, GointIJ #oints) "
KraphicsPath path 1 new KraphicsPath+./ path.'dd)e&iers+points./ path.CloseFigure+./

ret/rn #ath; -

Fill 8odes
When you compose a path of multiple figures that overlap' by default the overlap will be subtractive. #or e(ample' the following code produces the donut in #igure H.>1:
Era#hicsGath Eet9on/tGath(2ectan*le rect, int hole2a i/s) " Era#hicsGath #ath 1 ne! Era#hicsGath();
path.'ddEllipse+rect./

Goint centerGoint 1 ne! Goint(...); 2ectan*le hole2ect 1 ne! 2ectan*le(...); #ath.StartFi*/re(); .. not nee e &eca/se an elli#se !ill close itsel,
path.'ddEllipse+hole"ect./

ret/rn #ath;

Figure #.21. Figures That *&erlap Completel! 1ct ,u9tracti&el!

6owever' notice that when the donut is resi=ed' as in #igure H.>>' only the overlapping parts subtract from each other. Figure #.22. *&erlapping Figures and the 1lternate Fill8ode

This behavior is governed by the #ill ode property on the 8ath' of type #ill ode. The #ill ode enumeration has two values: 0lternate and Winding. 0lternate' the default' changes how shapes are filled by noticing when lines cross. 5witching to Winding mode' in this case' would fill both circles' because Winding mode changes how shapes are filled based on a complicated scheme of line segment direction that wouldn%t be invo$ed in our case. Jou can also set the #ill ode on a polygon and a closed curve' but the default 0lternate #ill ode is the overwhelming favorite and is seldom changed.

'mages
0s useful as curves and lines are' most modern applications also include the need to load and display professionally produced' prepac$aged images. 0lso' some applications themselves produce images that can be saved to a file for later display. Both $inds of applications are supported by the two $inds of images in .!"T: bitmaps and metafiles. 0 bitmap is a set of pi(els at certain color values stored in a variety of standard raster formats such as Graphics *nterchange #ormat 3G*#4 3.gif files4 and Eoint 8icture "(perts

Group 3E8"G4 3.<pg files4' as well as Windows/specific formats such as Windows bitmap 3.bmp files4 and Windows icon 3.ico files4. 0 metafile is a set of shapes that ma$e up a vector format' such as a Graphics8ath' but can also be loaded from Windows metafile 3.wmf4 and enhanced Windows metafile 3.emf4 formats.

$oading and Drawing +mages


Bitmaps as well as metafiles can be loaded from files in the file system as well as files embedded as resources. 6owever' you must use the appropriate class. The Bitmap class 3from the 5ystem.Arawing namespace4 handles only raster formats' and the etafile class 3from the 5ystem.Arawing.*maging namespace4 handles only vector formats. Both the Bitmap class and the etafile class derive from a common base class' the *mage class. *mage ob<ects are what you deal with most of the time' whether it%s drawing them into a Graphics ob<ect or setting a #orm ob<ect%s Bac$ground property.
LSM LSM

#or details of loading images from resources' see +hapter 10: @esources.

The easiest way to load the image is to pass a file name to the appropriate class%s constructor. 0fter an image has been loaded' it can be drawn using the Graphics.Araw*mage method:
/sin*( Meta,ile !m, 1 ne! Meta,ile(V"@9)22DWN.WMF") ) " .. 9ra! the ,/ll ima*e, /nscale an .. cli##e only to the Era#hics o&'ect *.9ra!6ma*e(!m,, ne! GointF(?, ?)); /sin*( +itma# &m# 1 ne! +itma#(V"Soa# +/&&les.&m#") ) " *.9ra!6ma*e(&m#, ne! GointF(5??, 5??)); /sin*( +itma# ico 1 ne! +itma#(V"GD6B05?.6CD") ) " *.9ra!6ma*e(ico, ne! GointF(@??, @??)); -

Arawing an image using a point will cause the image to be rendered at its native si=e and clipped only by the Graphics ob<ect. Jou can be e(plicit about this desire by calling Araw*mage1nscaled' but it acts no differently than passing only a point to Araw*mage.

,caling, Clipping, Panning, and ,5ewing


Arawing an image unscaled' although useful' is somewhat boring. 9ften' you%d li$e to perform operations on the image as it%s drawn to achieve effects. #or e(ample' to scale an

image as it%s drawn to fit into a rectangle' you pass a rectangle instead of a point to Araw*mage:
.. Scale the ima*e to the rectan*le 2ectan*le est2ect 1 ...; *.9ra!6ma*e(&m#, est2ect);

Going the other way' if you%d li$e to clip an image but leave it unscaled' you can use the Araw*mage method' which ta$es both a source and a destination rectangle of the same si=e 3#igure H.>3 shows the difference4:
.. Cli# the ima*e to the estination rectan*le 2ectan*le src2ect 1 ...; 2ectan*le est2ect 1 src2ect; *.9ra!6ma*e(&m#, est2ect, src2ect, *.Ga*e<nit);

Figure #.2 . ,caling an +mage =ersus Clipping an +mage

The code that does the clipping specifies a source rectangle to ta$e from the image and a destination rectangle on the graphics ob<ect. Because both rectangles were the same si=e' there was no scaling' but this techni)ue allows any chun$ of the image to be drawn 3and scaled4 to any rectangle on the graphics ob<ect. This techni)ue also allows for panning. Jou simply ta$e a chun$ out of the rectangle that%s not at the upper left 3as shown in #igure H.>H4:
+itma# &m# 1 ne! +itma#(V"C:\W6B9DWS\Soa# +/&&les.&m#"); Si7e o,,set 1 ne! Si7e(?, ?); .. ) '/ste &y &/ttons $oi #anel5%Gaint(o&'ect sen er, Gaint($ent)r*s e) " Era#hics * 1 e.Era#hics;
"ectangle dest"ect 1 this.panelB.Client"ectangle/ "ectangle src"ect 1

new "ectangle+ offset.Width- offset.,eight- dest"ect.Width- dest"ect.,eight./ g.0raw;mage+!mp- dest"ect- src"ect- g.PageInit./

Figure #.2#. 1 Form That Pans an +mage in Four Directions

!ot only can you scale an image 3or part of an image4 to a rectangle' but you can also scale an image 3or part of an image4 to an arbitrary parallelogram. 5everal of the Araw*mages overloads ta$e an array of three 8oint# ob<ects that describe three points on a parallelogram' which in turn acts as the destination 3the fourth point is e(trapolated to ma$e sure that it%s a real parallelogram4. 5caling to a nonrectangular parallelogram is called s!e ing because of the s$ewed loo$ of the results. #or e(ample' here%s a way to s$ew an entire image 3as shown in #igure H.>F4:
+itma# &m# 1 ne! +itma#(V"C:\W6B9DWS\Soa# +/&&les.&m#"); Si7e o,,set 1 ne! Si7e(?, ?); .. ) '/ste &y &/ttons $oi #anel5%Gaint(o&'ect sen er, Gaint($ent)r*s e) " Era#hics * 1 e.Era#hics;
"ectangle rect 1 this.panelB.Client"ectangle/ Point%( points 1 new Point%@(/ points%A( 1 new Point+rect.Ceft 7 offset.Width- rect.Top 7 offset.,eight./ points%B( 1 new Point+rect."ight- rect.Top 7 offset.,eight./ points%G( 1 new Point+rect.Ceft- rect.)ottom ? offset.,eight./ g.0raw;mage+!mp- points./

Figure #.2'. 1n %3ample of ,5ewing an +mage

/otating and Flipping


Two other $inds of transformation that you can apply to an image are rotating and flipping. Rotating an image allows it to be turned in increments of 90 degrees.that is' 90' 180' or >I0. Flipping an image allows an image to be flipped along either the T or the J a(is. Jou can perform these two transforms together using the values from the @otate#lipType enumeration' as shown in #igure H.>S. Figure #.2.. The /otating and Flipping T!pes from the /otateFlipT!pe %numeration

!otice in #igure H.>S that the @otate!one#lip!one type is the original image. 0ll the others are rotated or flipped or both. To rotate only' you pic$ a type that includes #lip!one. To flip only' you pic$ a type that includes @otate!one. The values from the @otate#lipType enumeration affect an image itself using the @otate#lip method:
.. 2otate T? e*rees &itma#5.2otateFli#(2otateFli#0y#e.2otateT?Fli#Bone); .. Fli# alon* the O axis &itma#@.2otateFli#(2otateFli#0y#e.2otateBoneFli#O);

The effects of rotation and flipping are cumulative. #or e(ample' rotating an image 90 degrees twice will rotate it a total of 180 degrees.

/ecoloring
@otating and flipping aren%t merely effects applied when drawingP rather' these operations affect the contents of the image. Jou can also transform the contents using an *mage0ttributes ob<ect that contains information about what $ind of transformations to ma$e. #or e(ample' one of the things you can do with an *mage0ttributes class is to map colors:
$oi #anel@%Gaint(o&'ect sen er, Gaint($ent)r*s e) " Era#hics * 1 e.Era#hics; /sin*( +itma# &m# 1 ne! +itma#(V"6B0L%BD.+MG") ) "
// Set the image attri!ute:s color mappings

Color$ap%( color$ap 1 new Color$ap%B(/ color$ap%A( 1 new Color$ap+./ color$ap%A(.JldColor 1 Color.Cime/ color$ap%A(.NewColor 1 Color.White/ ;mage'ttri!utes attr 1 new ;mage'ttri!utes+./ attr.Set"emapTa!le+color$ap./

.. 9ra! /sin* the color ma# 2ectan*le rect 1 ne! 2ectan*le(?, ?, &m#.Wi th, &m#.Hei*ht); rect.D,,set(...); .. Center the ima*e
g.0raw;mage +!mp- rect- A- A- rect.Width- rect.,eight- g.PageInit- attr./

This code first sets up an array of +olor ap ob<ects' each of which contains the old color to transform from and the new color to transform to. The color map is passed to a new *mage0ttribute class via the 5et@emapTable. The *mage0ttribute ob<ect is then passed to the Araw*mage function' which does the color mapping as the image is drawn. #igure H.>I shows an e(ample. Figure #.20. 1n %3ample of 8apping Color.$ime to Color.White

!otice that in addition to mapping the colors' the sample code uses the Width and 6eight properties of the Bitmap class. The Bitmap class' as well as the *mage base class and the etafile class' provides a great deal of information about the image. 0nother useful piece of information is the color information at each pi(el. #or e(ample' instead of hard/coding lime as the color' we could use the pi(el information of the bitmap itself to pic$ the color to replace:
ColorMa#IJ colorMa# 1 ne! ColorMa#I5J; colorMa#I?J 1 ne! ColorMa#();
color$ap%A(.JldColor 1 !mp.KetPi*el+A- !mp.,eight ? B./

colorMa#I?J.Be!Color 1 Color.White;

*n this case' we%re mapping whatever color is at the bottom left as the pi(el to replace. *n addition to replacing colors' the *mage0ttributes ob<ect can contain information about

remapping palettes' setting gamma correction values' mapping color to grayscale' and other color/related options as well as the wrap mode 3as with brushes4.

Transparenc!
9f course' simply mapping to white or any other color isn%t useful if the image needs to be drawn on top of something else that you%d li$e to show through. #or this case' there%s a special color called Transparent that allows the mapped color to disappear instead of being replaced with another color:
ColorMa#IJ colorMa# 1 ne! ColorMa#I5J; colorMa#I?J 1 ne! ColorMa#(); colorMa#I?J.Dl Color 1 &m#.EetGixel(?, &m#.Hei*ht : 5); colorMa#I?J.Be!Color 1 Color.0rans#arent;

#igure H.>8 shows the effects of using +olor.Transparent. Figure #.22. 7sing Color.Transparent in a Color 8ap

0gain' * used the bottom/left pi(el as the color to replace' which is the convention used by other parts of .!"T. *n fact' if you%re going to always draw a bitmap with a transparent color and if the color to be made transparent is in the bitmap itself in the bottom/left pi(el' you can save yourself the trouble of building a color map and instead use the a$eTransparent method:
.. Ma4e the &ottom:le,t #ixel the trans#arent color &m#.Ma4e0rans#arent(); *.9ra!6ma*e(&m#, rect);

*f the pi(el you%d li$e to use as the transparency color isn%t in the bottom left of the bitmap' you can also use the a$eTransparent overload' which ta$es a color as an argument. +alling a$eTransparent actually replaces the pi(els of the transparency color with the +olor.Transparent value. 5ome raster formats' such as the G*# and Windows icon formats' allow you to specify a transparency color value as one of their legal values. 6owever' even

if the raster format itself doesn%t support a transparency color' all Bitmap ob<ects' regardless of the raster format' support the a$eTransparent method.

1nimation
Eust as some raster formats support transparency as a native color' some also support animation. 9ne in particular is the G*# format. The way that images e(pose support for animation is by supporting more than one image in a single file. G*#s support animation by supporting more than one image in a time dimension' but other formats 3such as T*## files4 can support different resolutions or multiple images as pages. Jou can count how many pages are in each &dimension& by calling the Get#rame+ount method with #rameAimension ob<ects e(posed by properties from the #rameAimension class:
.. Will thro! exce#tions i, ima*e ,ormat oesnFt s/##ort .. m/lti#le ima*es alon* re8/este imension int timeFrames 1 *i,.EetFrameCo/nt(Frame9imension.0ime); int #a*eFrames 1 *i,.EetFrameCo/nt(Frame9imension.Ga*e); int resol/tionFrames 1 *i,.EetFrameCo/nt(Frame9imension.2esol/tion);

To select which frame to be displayed when the image is drawn is merely a matter of selecting the &active& frame along a dimension:
int ,rame 1 H; .. Bee s to &e &et!een ? an ,rame co/nt :5 *i,.Select)cti$eFrame(Frame9imension.0ime, ,rame); *.9ra!6ma*e(*i,, this.Client2ectan*le);

*n addition to the multiple frames' the G*# format encodes timing information for each frame. 6owever' that%s where things get tric$y. Because different image formats support different information' the *mage class e(poses &e(tra& information via its Get8roperty*tem method. The Get8roperty*tem method ta$es a numeric *A and returns a generic 8roperty*tem ob<ect. The *As themselves are defined only in a GA*; header file and the 8roperty*tem ob<ect%s -alue property. The -alue property e(poses the actual data as an array of bytes that needs to be interpreted' ma$ing usage from .!"T difficult. #or e(ample' here%s how to get the timings for a G*# file:
.. Eet &ytes escri&in* each ,rameFs time elay int Gro#erty0a*Frame9elay 1 ?xA5??; .. From E iGl/s6ma*in*.h Gro#erty6tem #ro# 1 *i,.EetGro#erty6tem(Gro#erty0a*Frame9elay);
!yte%( !ytes 1 prop.Lalue/

.. Con$ert &ytes into an array o, time elays int ,rames 1 *i,.EetFrameCo/nt(Frame9imension.0ime); intIJ elays 1 ne! intI,ramesJ; ,or( int ,rame 1 ?; ,rame 31 ,rames; ;;,rame ) "

.. Con$ert each H:&yte ch/n4 into an inte*er elaysI,rameJ 1 +itCon$erter.0o6ntN@(&ytes, ,rame Q H); -

0fter you have the time delays' you can start a timer and use the 5elect0ctive#rame method to do the animation. *f you do it that way' ma$e sure to convert the delays to milliseconds 31C1000ths of a second4' which is what .!"T timers li$e' from centiseconds 31C100ths of a second4' which is what G*# time delays are specified in. 9r <ust use the *mage0nimator helper class' which can do all this for you:
.. Loa animate E6F +itma# *i, 1 ne! +itma#(V"C:\S)MGL(%)B6M)06DB%CDGM.E6F"); $oi )nimationForm%Loa (o&'ect sen er, ($ent)r*s e) "
// Chec6 whether image supports animation if+ ;mage'nimator.Can'nimate+gif. . 4 // Su!scri!e to e#ent indicating the ne*t frame should !e shown ;mage'nimator.'nimate+gif- new E#ent,andler+gif8FrameChanged../ 5

#oid gif8FrameChanged+o!9ect sender- E#ent'rgs e. 4

i,( this.6n$o4e2e8/ire ) " .. 0ransition ,rom !or4er threa to <6 threa this.+e*in6n$o4e( ne! ($entHan ler(*i,%FrameChan*e ), ne! o&'ectIJ " sen er, e -); else "
// Trigger Paint e#ent to draw ne*t frame this.;n#alidate+./

$oi

// Ipdate image:s acti#e frame ;mage'nimator.IpdateFrames+gif./

)nimationForm%Gaint(o&'ect sen er, Gaint($ent)r*s e) "

.. 9ra! ima*eFs c/rrent ,rame Era#hics * 1 e.Era#hics; *.9ra!6ma*e(*i,, this.Client2ectan*le);

The *mage0nimator $nows how to pull the timing information out of an image and call you bac$ when it%s time to show a new one' and that is what calling *mage0nimator.0nimate does. When the event is fired' invalidating the rectangle being used to draw the animated G*# triggers the 8aint event. The 8aint event sets the ne(t active frame using the *mage0nimator.1pdate#rames method and draws the active frame. #igures H.>9' H.30' and H.31 show an image being animated. Figure #.24. ,ample 1nimation, First Frame

Figure #. 6. ,ample 1nimation, 8iddle Frame

Figure #. 1. ,ample 1nimation, $ast Frame

The only thing that%s a bit tric$y is that the animated event is called bac$ on a wor$er thread' not on the main 1* thread. Because it%s not legal to ma$e any method calls on 1* ob<ects' such as the form' from a thread other than the creator thread' we used the Begin*nvo$e method to transition bac$ from the wor$er thread to the 1* thread to ma$e the call. This techni)ue is discussed in gory detail in +hapter 1H: ultithreaded 1ser *nterfaces.

Drawing to +mages
+ertain $inds of applications need to create images on/the/fly' often re)uiring the capability to save them to a file. The $ey is to create an image with the appropriate starting parameters' which for a Bitmap means the height' width' and pi(el depth. The image is then used as the &bac$ing store& of a Graphics ob<ect. *f you%re interested in getting the pi(el depth from the screen itself' you can use a Graphics ob<ect when creating a Bitmap:
.. Eet c/rrent Era#hics o&'ect ,or is#lay Era#hics is#layEra#hics 1 this.CreateEra#hics(); .. Create +itma# to ra! into &ase on existin* Era#hics o&'ect 6ma*e ima*e 1 ne! +itma#(rect.Wi th, rect.Hei*ht, is#layEra#hics);

0fter you have an image' you can use the Graphics #rom*mage method to wrap a Graphics ob<ect around it:
Era#hics ima*eEra#hics 1 Era#hics.From6ma*e(ima*e);

0fter you%ve got a Graphics ob<ect' you can draw on it as you would normally. 9ne thing to watch out for' however' is that a Bitmap starts with all pi(els set to the Transparent color. That may well be e(actly what you want' but if it%s not' then a )uic$ #ill@ectangle across the entire area of the Bitmap will set things right. 0fter you%ve done the drawing on the Graphics ob<ect that represents the image' you can draw that image to the screen or a printer' or you can save it to a file' using the 5ave method of the *mage class:
ima*e.Sa$e(V"c:\ima*e.#n*");

1nless otherwise specified' the file is saved in 8!G format' regardless of the e(tension on the file name. *f you prefer to save it in another format' you can pass an instance of the *mage#ormat class as an argument to the 5ave method. Jou create an instance of the *mage#ormat class using the G1*A 3Globally 1ni)ue *A4 of the format' but the *mage#ormat class comes with several properties prebuilt for supported formats:
seale class 6ma*eFormat " */i );

.. Constr/ctors #/&lic 6ma*eFormat(E/i .. Gro#erties #/&lic E/i E/i


pu!lic pu!lic pu!lic pu!lic pu!lic pu!lic pu!lic pu!lic pu!lic pu!lic static static static static static static static static static static

" *et; )mp 4 get/ 5 Emf 4 get/ 5 E*if 4 get/ 5 Kif 4 get/ 5 ;con 4 get/ 5 Upeg 4 get/ 5 $emory)mp 4 get/ 5 Png 4 get/ 5 Tiff 4 get/ 5 Wmf 4 get/ 5

;mageFormat ;mageFormat ;mageFormat ;mageFormat ;mageFormat ;mageFormat ;mageFormat ;mageFormat ;mageFormat ;mageFormat

0s an e(ample of creating images on/the/fly and saving them to a file' the following code builds the bitmap shown in #igure H.3>:
$oi sa$e+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

2ectan*le rect 1 ne! 2ectan*le(?, ?, 5??, 5??);


// Ket current graphics o!9ect for display using+ Kraphics displayKraphics 1 this.CreateKraphics+. . // Create !itmap to draw into !ased on e*isting Kraphics o!9ect using+ ;mage image 1 new )itmap+rect.Width- rect.,eight- displayKraphics. . // Wrap Kraphics o!9ect around image to draw into using+ Kraphics imageKraphics 1 Kraphics.From;mage+image. . 4

ima*eEra#hics.Fill2ectan*le(+r/shes.+lac4, rect); Strin*Format ,ormat 1 ne! Strin*Format(); ,ormat.)li*nment 1 Strin*)li*nment.Center; ,ormat.Line)li*nment 1 Strin*)li*nment.Center; ima*eEra#hics.9ra!Strin*("9ra!in* to an ima*e", ...);
// Sa#e created image to a file image.Sa#e+M c3NtempNimage.png ./

Figure #. 2. %3ample of Drawing to an +mage

+cons
Before * wrap up the images section' * want to mention two $inds of images for which .!"T provides special care: icons and cursors. Jou can load a Windows icon 3.ico4 file into a Bitmap ob<ect' as with any of the other raster formats' but you can also load it into an *con ob<ect. The *con class is largely a direct wrapper class around the Win3> 6*+9! type and is provided mainly for interoperability. 1nli$e the Bitmap or etafile classes' the *con class doesn%t derive from the base *mage class:
seale class 6con : Marshal+y2e,D&'ect, 6Seriali7a&le, 6Clonea&le, 69is#osa&le " .. Constr/ctors #/&lic 6con(strin* ,ileBame); #/&lic 6con(6con ori*inal, Si7e si7e); #/&lic 6con(6con ori*inal, int !i th, int hei*ht); #/&lic 6con(Stream stream); #/&lic 6con(Stream stream, int !i th, int hei*ht); #/&lic 6con(0y#e ty#e, strin* reso/rce);

.. Gro#erties #/&lic 6ntGtr Han le " *et; #/&lic int Hei*ht " *et; #/&lic Si7e Si7e " *et; #/&lic int Wi th " *et; .. Metho s #/&lic static 6con FromHan le(6ntGtr han le); #/&lic $oi Sa$e(Stream o/t#/tStream); #/&lic +itma# 0o+itma#(); -

When setting the *con property of a #orm' for e(ample' you%ll use the *con class' not the Bitmap class. *cons support construction from files and resources as well as from raw streams 3if you want to create an icon from data in memory4 and e(pose their 6eight and Width. #or interoperability with Win3>' *cons also support the 6andle property and the #rom6andle method. *f you%ve got an *con and you%d li$e to treat it as a Bitmap' you can call the ToBitmap method and the data will be copied to a new Bitmap ob<ect. 0fter you%ve loaded an icon' you can draw it to a Graphics ob<ect using the Araw*con or Araw*con1nstretched method:
6con ico 1 ne! 6con("GD6B05?.6CD"); *.9ra!6con(ico, this.Client2ectan*le); .. Stretch *.9ra!6con<nstretche (ico, this.Client2ectan*le); .. 9onFt stretch

5everal icons used by the system come prepac$aged for you as properties of the 5ystem*cons class for your own use' as shown in #igure H.33. Figure #. . +con Properties from the ,!stem+cons Class as ,hown under Windows >P

Cursors

The other Win3> graphics type that Win#orms provides for is the +ursor type' which' li$e *con' doesn%t derive from the *mage base class:
seale class C/rsor : 69is#osa&le, 6Seriali7a&le " .. Constr/ctors #/&lic C/rsor(strin* ,ileBame); #/&lic C/rsor(6ntGtr han le); #/&lic C/rsor(Stream stream); #/&lic C/rsor(0y#e ty#e, strin* reso/rce); .. Gro#erties #/&lic static 2ectan*le Cli# " *et; set; #/&lic static C/rsor C/rrent " *et; set; #/&lic 6ntGtr Han le " *et; #/&lic static Goint Gosition " *et; set; #/&lic Si7e Si7e " *et; .. Metho s #/&lic 6ntGtr Co#yHan le(); #/&lic $oi 9ra!(Era#hics *, 2ectan*le tar*et2ect); #/&lic $oi 9ra!Stretche (Era#hics *, 2ectan*le tar*et2ect); #/&lic static $oi Hi e(); #/&lic static $oi Sho!();

0 +ursor is a graphic that represents the position of the mouse on the screen. *t can ta$e on several values based on the needs of the window currently under the cursor. #or e(ample' by default' the cursor is an arrow to indicate that it should be used to point. 6owever' when the cursor passes over a te(t editing window' it often turns into an */beam to provide for better positioning between characters. 0 cursor can be loaded from a file 3a.cur4 or from one of the system/provided cursors in the +ursors class' as shown in #igure H.3H. Figure #. #. ,!stem Cursors from the Cursors Class

Jou can draw a cursor manually using the Araw or Araw5tretched method of the +ursor class' but most of the time' you draw a cursor by setting it as the current cursor using the static +urrent property of the +ursor class. 5etting the current cursor will remain in effect only during the current event handler and only when the cursor is over windows of that application. +hanging the cursor won%t stop another window in another application from changing it to something it finds appropriate. #or e(ample' the following changes the application%s cursor to the Wait+ursor during a long operation:
$oi Form5%Clic4(o&'ect sen er, ($ent)r*s e) " try "
// Change the cursor to indicate that we:re waiting Cursor.Current 1 Cursors.WaitCursor/

,inally " -

.. 9o somethin* that ta4es a lon* time ...


// "estore current cursor Cursor.Current 1 this.Cursor/

// Cursor restored after this e#ent handler anyway...

!otice the use of the form%s +ursor property to restore the current cursor after the long operation completes. "very form and every control has a +ursor property. This cursor

becomes the default when the mouse passes over the window. #or e(ample' the following code sets a form%s default cursor to the +ross:
$oi 6nitiali7eCom#onent() " ... ...

this.Cursor 1 System.Windows.Forms.Cursors.Cross/

!otice the use of *nitiali=e+omponent to set the #orm%s cursor' indicating that this is yet another property that can be set in the 8roperty Browser' which shows a drop/down list of all the system/provided cursors to choose from.

Where Are We?


*n this chapter' we%ve discussed the basics of drawing' including colors' brushes' pens' shapes' paths' and images. 9f course' that%s not all you%ll need to $now about drawing in your Win#orms applications and controls. +hapter F is dedicated to fonts and drawing strings.

Chapter '. Drawing Te3t


8robably the most useful thing to draw in any application is te(t. 5ometimes you%ll draw te(t yourself' and sometimes it will be drawn for you by the controls you%re using. !o matter who does the drawing' you often can specify the font used to draw the te(t' and that%s what the first part of this chapter is about. The second part deals with drawing te(t yourself into a Graphics ob<ect or a Graphics8ath.

Fonts
0 font is an instance of the #ont class' which includes a font family' a si=e' and a font style. 0nd' as you might e(pect' a font famil# is an instance of the #ont#amily class' which is a group of typefaces that differ only in style. 0 t#peface is a named collection of drawing stro$es that ma$e up the outlines of the characters' such as those you%re reading right now. *t%s the typeface name that you%re used to seeing in the &#ont& menu of most programs. The font st#le constitutes the variations within a typeface' such as bold' italics' and underline. 5o a typeface would be &0rial'& a font family would include &0rial @egular& and &0rial Bold'& and a font would be &1>/point 0rial Bold.& #onts can be measured in several si=es' including pi(els' points' ems' and design units. 0 pi%el is a point of light on a screen or a point of in$ on a printer. 8i(els are often pac$ed into inches for measurement. #or e(ample' the resolution of video display adapters and printers is typically specified in dots per inc" 3dpi4' where a dot is the same as a pi(el. 8i(els are device/dependent' so a pi(el on a I>/dpi display bears no si=e relationship to a pi(el on a 300/dpi printer. 0 point' on the other hand' is 1CI>nd of an inch no matter what device it%s drawn on' and the Graphics ob<ect will scale appropriately as te(t is drawn. +onverting between points and pi(els re)uires $nowing the dpi of the device you%re drawing on' which is conveniently available via the Graphics.ApiJ property:
L1M L1M

There%s also a Graphics.ApiT property' but that%s used for measuring width. 8oints specify the height of a font.

/sin*( Era#hics * 1 this.CreateEra#hics() ) "

// ' BG?point font will !e BE pi*els high on a DE?dpi monitor float dpi 1 g.0piR/ float points 1 BGf/ float pi*els 1 +points Q dpi./FGf/

... -

The em unit of measure is so named because metal typesetters used capital as the guide against which all other letters were measured. was used because it too$ up the most vertical and hori=ontal space. The number of points that a font is specified represents &one em& for that font.

#inally' design &nits are a font designer%s way to specify a font family%s dimensions no matter what the resolution of the rendering device or the si=e of the rendered font. #or e(ample' 0rial has a height of >'0H8 design units. The design units are used to scale a font family to a point si=e when individual stro$es of the font are rendered 3more on this later4. The #ont class itself is shown here:
seale class Font : Marshal+y2e,D&'ect, 6Clonea&le, 6Seriali7a&le, 69is#osa&le " .. Constr/ctors #/&lic Font(...); .. Se$eral o$erloa s .. Gro#erties #/&lic &ool +ol " *et; #/&lic FontFamily FontFamily " *et; #/&lic &yte E iCharSet " *et; #/&lic &ool E iCerticalFont " *et; #/&lic int Hei*ht " *et; #/&lic &ool 6talic " *et; #/&lic strin* Bame " *et; #/&lic ,loat Si7e " *et; #/&lic ,loat Si7e6nGoints " *et; #/&lic &ool Stri4eo/t " *et; #/&lic FontStyle Style " *et; #/&lic &ool <n erline " *et; #/&lic Era#hics<nit <nit " *et; .. Metho s #/&lic static Font FromH c(6ntGtr h c); #/&lic static Font FromH,ont(6ntGtr h,ont); #/&lic static Font FromLo*Font(o&'ect l,); #/&lic static Font FromLo*Font(o&'ect l,, 6ntGtr h c); #/&lic ,loat EetHei*ht(); #/&lic ,loat EetHei*ht(,loat #i); #/&lic ,loat EetHei*ht(System.9ra!in*.Era#hics *ra#hics); #/&lic 6ntGtr 0oH,ont(); #/&lic $oi 0oLo*Font(o&'ect lo*Font); #/&lic $oi 0oLo*Font(o&'ect lo*Font, Era#hics *ra#hics); -

Creating Fonts
Jou can create a #ont ob<ect by specifying' at a minimum' the typeface and the si=e in points:
/sin*( Font ,ont 1 ne! Font(")rial", 5@) ) " ... -

*f you specify a font that%s not available' you%ll get an instance of the 5 5ans 5erif font in whatever si=e you specify. *f you%d li$e to specify the font in a unit other than points' you can do so using an overload of the #ont constructor that ta$es a value from the Graphics1nit enumeration:

en/m Era#hics<nit " 9is#lay, .. 5.KAth o, an inch (5.5??th o, an inch ,or #rinters) 9oc/ment, .. 5.N??th o, an inch 6nch, .. 5 inch Millimeter, .. 5 millimeter Gixel, .. 5 e$ice: e#en ent #ixel Goint, .. 5.K@n o, an inch Worl , .. isc/sse in Cha#ter X: ) $ance 9ra!in* -

"(cept for Graphics1nit.8i(el and Graphics1nit.World' all the units are really <ust variations of a point' because they%re all specified in device/independent units. 1sing these units' all the following specify 1>/point 0rial:
L>M L>M

0 dpi of 9S is assumed' which yields 1S pi(els for a 1>/point font.

.. CanFt /se Era#hics<nit.9is#lay ,or creatin* ,onts, .. &eca/se 9is#lay $aries &ase on !here sha#es are ra!n Font ,ont5 1 ne! Font(")rial", 5@, Era#hics<nit.Goint); Font ,ont@ 1 ne! Font(")rial", 5X, Era#hics<nit.Gixel); Font ,ontN 1 ne! Font(")rial", ?.5XXXXXK,, Era#hics<nit.6nch); Font ,ontH 1 ne! Font(")rial", A?, Era#hics<nit.9oc/ment); Font ,ontA 1 ne! Font(")rial", H.@NNNNH,, Era#hics<nit.Millimeter);

*f you%d li$e to specify a style other than regular' you can do so by passing a combination of the values from the #ont5tyle enumeration:
en/m FontStyle " +ol , 6talic, 2e*/lar, .. e,a/lt Stri4eo/t, <n erline, -

#or e(ample' the following will create 0rial Bold *talic:


Font ,ont 1 ne! Font(")rial", 5@,
FontStyle.)old X FontStyle.;talic);

*f the font family you%re specifying with the typeface argument to the #ont constructor doesn%t support the styles you specify' a run/time e(ception will be thrown. *f you%ve got a font but you don%t li$e the style' you can create a #ont based on another #ont. This is handy when you%d li$e to base a new font on an e(isting font but need to ma$e a minor ad<ustment:
Font ,ont 1 ne! Font(this.Font,
FontStyle.)old X FontStyle.;talic);

Font Families

When creating a font' you use the typeface name to retrieve a font family from the list of fonts currently installed on the system. The typeface name is passed to the constructor of the #ont#amily class. The #ont#amily class is shown here:
seale class FontFamily : Marshal+y2e,D&'ect, 69is#osa&le " .. Constr/ctors #/&lic FontFamily(EenericFontFamilies *enericFamily); #/&lic FontFamily(strin* name); #/&lic FontFamily(strin* name, FontCollection ,ontCollection); .. Gro#erties #/&lic static #/&lic static #/&lic static #/&lic static #/&lic strin* FontFamilyIJ Families " *et; FontFamily EenericMonos#ace " *et; FontFamily EenericSansSeri, " *et; FontFamily EenericSeri, " *et; Bame " *et; -

.. Metho s #/&lic int EetCell)scent(FontStyle style); #/&lic int EetCell9escent(FontStyle style); #/&lic int Eet(mHei*ht(FontStyle style); #/&lic static FontFamilyIJ EetFamilies(Era#hics *ra#hics); #/&lic int EetLineS#acin*(FontStyle style); #/&lic strin* EetBame(int lan*/a*e); #/&lic &ool 6sStyle)$aila&le(FontStyle style);

+reating a #ont from a #ont#amily loo$s li$e this:


FontFamily ,amily 1 ne! FontFamily(")rial"); Font ,ont 1 ne! Font(,amily, 5@, FontStyle.+ol Y FontStyle.6talic);

+reating a #ont from a #ont#amily ob<ect is useful if you%d li$e to pic$ a font family based on general characteristics instead of a specific typeface name. 0 #ont#amily can be constructed with one of the Generic#ont#amilies enumeration values:
en/m EenericFontFamilies " Monos#ace, .. Co/rier Be! SansSeri,, .. Microso,t Sans Seri, Seri,, .. 0imes Be! 2oman -

+onstructing a #ont#amily using a value from Generic#ont#amilies is useful if you%d li$e to avoid the ris$ that a more specific font won%t be available on the system. *n fact' the #ont#amily class even provides properties that you can use directly for each of these #ont#amilies:
.. 0he har !ay Font ,ont5 1 ne! Font(new FontFamily+KenericFontFamilies.Serif., 5@); .. 0he easy !ay Font ,ont@ 1 ne! Font(FontFamily.Keneric$onospace, 5@);

*f' instead of hard/coding a font family 3even a generic one4' you%d li$e to let users pic$ their favorite' you need to present them a 1* that lets them pic$ from the font families they have installed. The #ont#amily class provides the #amilies property for determining the currently installed font families:
foreach+ FontFamily family in FontFamily.Families . 4

.. Can ,ilter &ase on a$aila&le styles i,( 3,amily.6sStyle)$aila&le(FontStyle.+ol ) ) contin/e;


5

,amiliesList+ox.6tems.)

(,amily.Bame);

Jou can also construct a #ont ob<ect from an 6A+' an 6#9!T' or a 79G#9!T' all of which are features that support interoperability with Win3>.

Font Characteristics
0fter you%ve got a #ont ob<ect' you can interrogate it for all $inds of properties' such as its family' its name 3which will be the same as the family name4' and a couple of GA* properties for Win3> interoperability. ost importantly' you%ll probably want to $now about a font%s style' using either the 5tyle property of type #ont5tyle or using individual properties:
.. 0he har !ay &ool &ol 5 1 (this.Font.Style T FontStyle.)old) .. 0he easy !ay &ool &ol @ 1 this.Font.)old;
11 FontStyle.)old;

0nother important characteristic of a #ont is its dimensions. The width of a character in a specific font varies from character to character' unless you%ve used a monospace font such as +ourier !ew' in which all characters are padded as necessary so that they%re the same width. The Graphics ob<ect provides the easure5tring method for measuring the ma(imum si=e of a string of characters of a specific font:
/sin*( Era#hics * 1 this.CreateEra#hics() ) "
Si&eF si&e 1 g.$easureString+ ,owdy - this.Font./ float length 1 si&e.Width/

...

When it%s called this way' the si=e returned from easure5tring assumes that the string is clipped to a single lineP this means that the width will vary with the width of the string' but the height will be a constant. Because the Graphics ob<ect can wrap multiline strings to a rectangle' you can also measure the rectangle needed for a multiline string. Jou do this by calling the easure5tring method and specifying a ma(imum layout rectangle for the string to live in:
L3M L3M

0lthough individual character heights vary' the vertical space reserved for them does not.

Si7eF layo/t)rea 1 this.Client2ectan*le.Si7e; .. Be! line character F\nF !ill ,orce text to next line strin* s 1 "a strin* that !ill\nta4e at least t!o lines";
Si&eF si&e 1 g.$easureString+s- this.Font- layout'rea./

The Width property returned in the 5i=e# ob<ect is the width of the longest wrapped line' and the 6eight is the number of lines needed to show the string multiplied by the height of the font 3up to the ma(imum height specified in the layout area4. The height used as the multiplier isn%t the height of the font as specified. #or e(ample' 1> points would be 1S pi(els at 9S dpi' but that%s not the value that%s used. *nstead' the height is appro(imately 11FZ of that' or about 18.H pi(els for a 1>/point font at 9S dpi. This e(panded value is e(posed from the #ont.Get6eight method and is meant to ma(imi=e readability when lines of te(t are drawn one after another. #or e(ample' if you wanted to handle wrapping yourself' you could lay out te(t one line at a time' incrementing the y value by the result of #ont. Get6eight:
,oreach( strin* line in m/ltiline.S#lit(F\nF) ) " ,loat !i th 1 man/alGanel.Client2ectan*le.Wi th; ,loat hei*ht 1 man/alGanel.Client2ectan*le.Hei*ht : y; 2ectan*leF layo/t2ect 1 ne! 2ectan*leF(?, y, !i th, hei*ht); .. 0/rn o,, a/to:!ra##in* (!eFre oin* it man/ally) Strin*Format ,ormat 1 ne! Strin*Format(Strin*FormatFla*s.BoWra#); *.9ra!Strin*(line, this.Font, +r/shes.+lac4, layo/t2ect, ,ormat); ...
// Ket ready for the ne*t line y 71 this.Font.Ket,eight+g./

*n this code' we split the string into multiple lines for embedded new line characters' <ust as Araw5tring does when it does the wrapping for us. We also set up a 5tring#ormat 3more about that later4 that turns off wrappingP otherwise' Araw5tring will wrap at word boundaries for us. 0fter we draw the string at our chosen rectangle' we increment y by the result of #ont.Get6eight so that the ne(t line of te(t is far enough below the te(t we <ust drew to ma$e it pleasing to read. #igure F.1 shows what Araw5tring would do with a multiline string automatically' and what our manual code does. Figure '.1. 1utomatic Word)Wrap Performed 9! Draw,tring Compared with 8anual Word)Wrap 7sing Font.:etHeight

*n addition to the strings' #igure F.1 shows the rectangles obtained by measuring each string: one rectangle when Araw5tring wraps the te(t for us' one rectangle per line when we do it ourselves. !otice also that the rectangle produced by easure5tring is a bit bigger than it needs to be to draw the te(t. This is especially evident in the overlapping rectangles shown on the manual side. easure5tring is guaranteed to produce a si=e that%s big enough to hold the string but sometimes will produce a si=e that%s larger than it needs to be to meet that guarantee.

Font Height
While we%re on the sub<ect of font height' it turns out that there are a lot of ways to measure the height of a font. The #ont class provides not only the Get6eight method but also the 5i=e property' which is the base si=e provided in the units passed to the #ont ob<ect%s constructor 3the Graphics1nit value specified at construction time is available via the #ont%s 1nit property4. 0s * mentioned' the height of a font is determined from the base si=e. The height of the font is further bro$en down into three parts called cell ascent* cell descent* and leading 3so named because typesetting used to be done with lead4. Two of these three measures are available in design units from the #ont#amily class 3available via the #ont%s #ont#amily property4 and are shown in #igure F.>. Together' these three values ma$e up the line spacing' which is also provided as a property on the #ont#amily and is used to calculate the font%s height and leading 3leading isn%t available directly4.
LHM LHM

The #ont also provides a 6eight property' but it should be avoided in favor of the Get6eight method. The Get6eight method scales to a

specified Graphics ob<ect' whereas the 6eight property scales only to the current video adapter%s dpi' ma$ing it pretty worthless for anything e(cept the nontransformed video adapter.

Figure '.2. The Parts of a Font Famil!?s Height

The line spacing is e(pressed in design units but is used at run time to determine the result of calling #ont.Get6eight. The magic of the conversion between design units and pi(els is managed by one more measure available from the #ont#amily class: the em height. The em "eig"t is a logical value but is e)uivalent to the font%s si=e in points' so scaling between design units and pi(els is performed using the proportion between the font%s si=e and the font family%s em height. #or e(ample' the scaling factor between 0rial%s em height 3>'0H84 and its 1>/point pi(el height 31S at 9S dpi4 is 1>8. Aividing 0rial%s line spacing 3>'3FF4 by 1>8 yields 18.398HH' which is the same as the result of calling Get6eight on 1>/point 0rial at 9S dpi. Table F.1 shows the various measures of font and font family height. Ta9le '.1. Font and FontFamil! ,i-es @,ample Font +s 1rial 12 Point at 4. dpiA
$easure Inits E*ample 0escription

#ont#amily.Get"m6eight #ont#amily.Get+ell0scent #ont#amily.Get+ellAescent

Aesign 1nits Aesign 1nits Aesign 1nits

>'0H8 1'8FH H3H >'3FF

Base si=e' e)uivalent to 5i=e 6eight above base line 6eight below base line +ell0scent ; +ellAescent ; 7eading' normally about 11FZ of "m6eight "(tra space below bottom of +ellAescent for readability' not e(posed by any property

#ont#amily.Get7ine5pacing Aesign 1nits

7eading

Aesign 1nits

SI

#ont.5i=e

Graphics1nit passed 1S pi(els Base si=e' e)uivalent to to #ont ctor 3defaults "m6eight

Ta9le '.1. Font and FontFamil! ,i-es @,ample Font +s 1rial 12 Point at 4. dpiA
$easure Inits E*ample 0escription

to 8oint4 #ont.5i=e*n8oints 8oints 1> points Base si=e in points' e)uivalent to 5i=e and "m6eight 18.398HH ")uivalent to 7ine5pacing scaled to either Graphics ob<ect or dpi 19 ")uivalent to 7ine5pacing scaled to system dpi and rounded to ne(t/highest integer value 1sed to convert design units to physical or logical values for rendering

#ont.Get6eight

8i(els

#ont.6eight

8i(els

5caling #actor

Aesign 1nit or 8i(els

1>8

Strings
9f course' deciding on a font is only half the fun. The real action is drawing strings after the font%s been pic$ed. #or that' you use the Araw5tring method of the Graphics ob<ect:
/sin*( Font ,ont 1 ne! Font(")rial", 5@) ) " .. 0his !ill !ra# at ne! line characters -

g.0rawString+ line BNnline G - font- )rushes.)lac6- BA- BA./

The Araw5tring method ta$es' at a minimum' a string' a font' a brush to fill in the font characters' and a point. Araw5tring starts the drawing at the point and $eeps going until it hits the edges of the region in the Graphics ob<ect. This includes translating new line characters as appropriate but does not include wrapping at word boundaries. To get the wrapping' you specify a layout rectangle:
/sin*( Font ,ont 1 ne! Font(")rial", 5@) ) " .. 0his !ill a/tomatically !ra# lon* lines an .. it !ill !ra# at ne! line characters *.9ra!Strin*("a lon* strin*...", ,ont, +r/shes.+lac4, this.Client"ectangle); -

Formatting

*f you%d li$e to turn off wrapping or set other formatting options' you can do so with an instance of the 5tring#ormat class:
seale class Strin*Format : Marshal+y2e,D&'ect, 6Clonea&le, 69is#osa&le " .. Constr/ctors #/&lic Strin*Format(...); .. $ario/s o$erloa s .. Gro#erties #/&lic Strin*)li*nment )li*nment " *et; set; #/&lic int 9i*itS/&stit/tionLan*/a*e " *et; #/&lic Strin*9i*itS/&stit/te 9i*itS/&stit/tionMetho " *et; #/&lic Strin*FormatFla*s FormatFla*s " *et; set; #/&lic static Strin*Format Eeneric9e,a/lt " *et; #/&lic static Strin*Format Eeneric0y#o*ra#hic " *et; #/&lic Hot4eyGre,ix Hot4eyGre,ix " *et; set; #/&lic Strin*)li*nment Line)li*nment " *et; set; #/&lic Strin*0rimmin* 0rimmin* " *et; set; .. Metho s #/&lic ,loatIJ Eet0a&Sto#s(re, Sin*le ,irst0a&D,,set); #/&lic $oi Set9i*itS/&stit/tion( int lan*/a*e, Strin*9i*itS/&stit/te s/&stit/te); #/&lic $oi SetMeas/ra&leCharacter2an*es(Character2an*eIJ ran*es); #/&lic $oi Set0a&Sto#s(,loat ,irst0a&D,,set, ,loatIJ ta&Sto#s);

0 5tring#ormat ob<ect lets you set all $inds of interesting te(t characteristics' such as the tab stops and the alignment 3vertically and hori=ontally4 as well as whether to wrap:
.. 0/rn o,, a/to:!ra##in*
StringFormat format 1 new StringFormat+StringFormatFlags.NoWrap./ *.9ra!Strin*("...", ,ont, &r/sh, rect, format);

The 5tring#ormat#lags enumeration provides a number of options:


en/m Strin*FormatFla*s " ?, .. Bo ,la*s ( e,a/lt) 9irection2i*ht0oLe,t, .. 9ra! text ri*ht:to:le,t 9irectionCertical, .. 9ra! text /#:to: o!n 9is#layFormatControl, .. Sho! ,ormat control character *ly#hs Fit+lac4+ox, .. Lee# all *ly#hs insi e layo/t rectan*le LineLimit, .. Sho! only !hole lines Meas/re0railin*S#aces, .. Meas/reStrin* incl/ es trailin* s#aces BoCli#, .. 9onFt cli# text #artially o/tsi e layo/t rectan*le BoFontFall&ac4, .. 9onFt ,all &ac4 ,or characters missin* ,rom Font BoWra#, .. 9onFt inter#ret \n or \t (im#lie !hen no rect #ro$i e ) -

!ote that a gl#p" is a symbol that conveys some $ind of information. #or e(ample' the T in the upper/right corner of a window is a glyph that indicates that the window can be closed.

Jou can combine and set one or more 5tring#ormat#lags on a 5tring#ormat ob<ect by using either the 5tring#ormat constructor or the #ormat#lags property. #or e(ample' the following draws te(t down/to/up and disables automatic wrapping:
Strin*Format ,ormat 1 ne! Strin*Format(); ,ormat.FormatFla*s 1 Strin*FormatFla*s.9irectionCertical Y Strin*FormatFla*s.BoWra#; *.9ra!Strin*("...", ,ont, &r/sh, rect, ,ormat);

When te(t doesn%t fit into the allotted space' you have a few options. *f the string is too tall' you have three choices. Jou can clip to the layout rectangle' letting partial lines show' which is the default. Jou can show only complete lines if they fit inside the layout rectangle' which is the behavior you get with 5tring#ormat#lags.7ine7imit. #inally' you can decide to show complete lines even if they lie outside the layout rectangle' which is what you get with 5tring#ormat#lags.!o+lip. +ombining 7ine7imit with !o+lip is not useful' because the behavior is the same as 7ine7imit. The three options are shown in #igure F.3. Figure '. . The %ffect of the $ine$imit ,tringFormatFlags =alue

,tring Trimming *f' on the other hand' the string is too long' you can decide what happens by setting the Trimming property of the 5tring#ormat ob<ect to one of the 5tringTrimming enumeration values:
en/m Strin*0rimmin* " Bone, .. Bo trimmin* (acts li4e Wor ,or sin*le lines) Character, .. 0rim to nearest character (the e,a/lt) (lli#sisCharacter, .. 0rim to nearest character an sho! elli#sis Wor , .. 0rim to nearest !or (lli#sisWor , .. 0rim to nearest !or an sho! elli#sis (lli#sisGath, .. 0rim ,ile #ath &y #/ttin* elli#sis in the mi le -

#igure F.H shows the results of applying the 5tringTrimming values when you draw a string. Figure '.#. %3amples of the ,tringTrimming %numeration

Ta9 ,tops 5omething else of interest in #igure F.H is the use of tabs to line up the string' instead of forcing the te(t to be in a monospaced font and lining up the te(t with spaces. Tabs are set using the 5etTab5tops method of the 5tring#ormat class:
Strin*Format ,ormat 1 ne! Strin*Format();
Si&eF si&e 1 g.$easureString+ StringTrimming.EllipsisCharacter.ToString+.- this.Font./ format.SetTa!Stops+A- new float%( 4 si&e.Width 7 BA 5./

This call to 5etTab5tops sets a single tab stop to be the width of the longest string' plus a pleasing amount of leading. When tab stops are specified and when 5tring#ormat#lags.!oWrap is absent from the 5tring#ormat ob<ect' then the tab character causes the characters that follow to be drawn starting at the tab stop offset 3unless the string has already passed that point4. *f the 5tring#ormat ob<ect has not been given any tab stops' then the tab character will not be interpreted. *f Araw5tring is called without any 5tring#ormat ob<ect at all' it will build one internally that defaults to four times the si=e of the fontP for e(ample' a 1>/point font will have tab stops every H8 points. There are several ways to specify tab stops logically. #or e(ample' imagine that you%d li$e a tab stop at every H8 units' as Araw5tring does by default when no 5tring#ormat is provided. Jou might also imagine that you%d li$e to specify only a certain number of tab stops at specific locations. #inally' you might imagine that you%d li$e to have an array of tab stops' but use an offset determined at run time to calculate the actual tab stops. 0ll these techni)ues are supported' but you must use a single 5etTab5tops method' and that ma$es things somewhat unintuitive. The array of floating point values passed to set the tab stops represents the spaces between successive tab stops. The first value in this array is added to the first argument to 5etTab5tops to get the first tab stop' and each successive value is added to the preceding value to get the ne(t tab stop. #inally' when more tabs are found than tab stops' the last value of the array is added repeatedly to get successive tab stops. Table F.> shows various arguments passed to 5etTab5tops and the resultant offsets for each stop.

Jou may have noticed the GetTab5tops method on the 5tring#ormat class' but unfortunately it hands bac$ only the same tab stop settings handed to 5etTab5tops in the first place. *t would have been handy to get bac$ the resultant tab stops so that you could ma$e sure you%ve set them correctly. Ta9le '.2. ,ample 1rguments to ,etTa9,top and /esultant Ta9 ,tops
'rguments to SetTa!Stop "esultant Ta! Stops 0escription

0' ^ 100 _ 0' ^ 100' 0 _ 0' ^ F0' IF' 100 _

100' >00' 300' W 100' 100' 100' W F0' 1>F' >>F' 3>F' H>F' W

Tab stops every 100 units 9ne tab stop at 100 units 0 tab stop at F0' 1>F' and >>F and then one every 100 units 0 tab stop at F0' 1>F' and >>F units 9ne tab stop at 1F0 units and then one every 100 units 9ne tab stop at 1F0 units 0 tab stop at 100' 1IF' and >IF and then one every 100 units 0 tab stop at 100' 1IF' and >IF units

0' ^ F0' IF' 100' 0 _ F0' 1>F' >>F' >>F' >>F' W F0' ^ 100 _ F0' ^ 100' 0 _ F0' ^ F0' IF' 100 _ F0' ^ F0' IF' 100' 0_ Hot5e! Prefi3es 1F0' >F0' 3F0' W 1F0' 1F0' 1F0' W 100' 1IF' >IF' 3IF' HIF' W 100' 1IF' >IF' >IF' >IF' W

*n addition to new lines and tab characters' Araw5tring can substitute other characters' including ampersands and digits. 5ubstitution of ampersands is a convenience for specifying Windows hot$eys for menu items and form fields. #or e(ample' by default the string &[#ile& will be output as &[#ile& 3but without the )uotation mar$s4. 6owever' you can specify that the ampersand be dropped or that the ne(t character be underlined' as governed by the 6ot$ey8refi( enumeration from the 5ystem.Arawing.Te(t namespace:
en/m Hot4eyGre,ix " Hi e, .. 9ro# all Z characters Bone, .. Sho! all Z characters ( e,a/lt) Sho!, .. 9ro# Z characters an /n erline next character -

#or e(ample' the following translates &[#ile& into &#ile& 3no )uotation mar$s4 as the string is drawn:
Strin*Format ,ormat 1 ne! Strin*Format();

,ormat.Hot4eyGre,ix 1 Hot4eyGre,ix.Sho!; *.9ra!Strin*("ZFile", ,ont, &r/sh, rect, ,ormat);

Digit ,u9stitution 9ne other substitution that Araw5tring can perform is for digits. ost languages have adopted the 0rabic digits 30' 1' >' 3' W4 when representing numbers' but some also have traditional representations. Which representation to show is governed by the method and language as determined by a call to the 5etAigit5ubstitution method on the 5tring#ormat class:
C/lt/re6n,o c/lt/re 1 ne! C/lt/re6n,o("th:0H"); .. 0hailan Strin*Format ,ormat 1 ne! Strin*Format(); ,ormat.Set9i*itS/&stit/tion( c/lt/re.LC69, Strin*9i*itS/&stit/te.0ra itional); *.9ra!Strin*("? 5 @...", ,ont, &r/sh, rect, ,ormat); 0hai

The substitution method is governed by the 5tringAigit5ubstitute 3and can be discovered with the Aigit5ubstitution ethod on the 5tring#ormat class4' as shown in #igure F.F. Figure '.'. ,tringDigit,u9stitute =alues as 1pplied to Thailand Thai

The integer language identifier comes from the 7+*A 3language and culture *A4 of an instance of the +ulture*nfo class. *t can be constructed with a two/part name: a two/letter country code followed by a two/letter language code' separated by a hyphen. The methods applied to the national and traditional languages of Thailand are shown in #igure F.F.
LFM LFM

The country code and language codes are defined by *59 standards.

1lignment *n addition to substitution' tabs' wrapping' and clipping' you can use 5tring#ormat to set te(t alignment 3both hori=ontally and vertically4 by setting the 0lignment and 7ine0lignment properties' respectively' using one of the 5tring0lignment enumeration values:
en/m Strin*)li*nment " Center, Bear, .. 9e#en s on ri*ht:to:le,t settin* Far, .. 9e#en s on ri*ht:to:le,t settin* -

!otice that instead of 7eft and @ight alignment' the 5tring0lignment enumeration values are !ear and #ar and depend on whether the @ightTo7eft string format flag is specified. The following centers te(t in a rectangle both hori=ontally and vertically:
,ormat.)li*nment 1 Strin*)li*nment.Center; .. Center hori7ontally ,ormat.Line)li*nment 1 Strin*)li*nment.Center; .. Center $ertically

Two combinations on a 5tring#ormat ob<ect are so commonly needed that they%re set up for you and are e(posed via the GenericAefault and GenericTypographic properties of the 5tring#ormat class. The GenericAefault 5tring#ormat ob<ect is what you get when you create a new 5tring#ormat ob<ect' so it saves you the trouble if that%s all you%re after. The GenericTypographic 5tring#ormat ob<ect is useful for showing te(t as te(t' not as part of drawing a 1* element. The properties you get from each are shown in Table F.3. 1ntialiasing 0ll the strings *%ve shown in the sample figures in this section have been nice and smooth. That%s because *%m using Windows T8 with +learType turned on. *f * turn that off' * go bac$ to the old' bloc$y way of loo$ing at things. 6owever' when *%m drawing strings' * don%t have to settle for what the user specifies. * can set the Te(t@endering6int property of the Graphics ob<ect before * draw a string to one of the Te(t@endering6int enumeration values' as shown in #igure F.S. Figure '... %3amples of the Te3t/enderingHint %numeration

Ta9le '. . The ,ettings of the "uilt)in ,tringFormat Classes


Keneric0efault KenericTypographic

5tring#ormat#lags R 0 0lignment R !ear 7ine0lignment R !ear

5tring#ormat#lags R 7ine7imit' !o+lip 0lignment R !ear 7ine0lignment R !ear

Ta9le '. . The ,ettings of the "uilt)in ,tringFormat Classes


Keneric0efault KenericTypographic

Aigit5ubstitution ethod R 1ser 6ot$ey8refi( R !one !o tab stops Trimming R +haracter

Aigit5ubstitution ethod R 1ser 6ot$ey8refi( R !one !o tab stops Trimming R !one

*n this case' 5ystemAefault shows what te(t loo$s li$e without any smoothing effects. The 5ingleBit8er8i(el setting does <ust what it says' although it%s clearly not useful for anything that needs to loo$ decent. The 0nti0lias and +learType settings are two different algorithms for smoothing that are meant to ma$e the te(t loo$ good: one for any monitor' and one specifically for 7+A displays. The grid fit versions of the algorithms use e(tra hints to improve the appearance' as you can see from the e(amples. 9f course' as the )uality improves' the rendering time also increases' and that%s why you can set the option as appropriate for your application. #urthermore' when drawing using one of the antialiasing algorithms' you can ad<ust the Te(t+ontrast property of a Graphics ob<ect. The contrast ranges from 0 to 1>' where 0 is the most contrast and 1> is the least' with H being the default. The contrast ma$es fonts at smaller point si=es stand out more against the bac$ground.

,trings and Paths


9ne more string/drawing tric$ that might interest you is the ability to add strings to graphics paths. Because everything that%s added to a path has both an outline and an interior that can be drawn separately' you can add strings to a path to achieve outline effects' as shown in #igure F.I:
.. Bee to #ass in 9G6 1 5?? ,or Era#hics<nit 11 9is#lay Era#hicsGath EetStrin*Gath( strin* s, ,loat #i, 2ectan*leF rect, Font ,ont, Strin*Format ,ormat) "
KraphicsPath path 1 new KraphicsPath+./ // Con#ert font si&e into appropriate coordinates float emSi&e 1 dpi Q font.Si&e;nPoints / FG/ path.'ddString+ s- font.FontFamily- +int.font.Style- emSi&e- rect- format./ return path/

$oi D/tlineFontsForm%Gaint(o&'ect sen er, Gaint($ent)r*s e) " Era#hics * 1 e.Era#hics; strin* s 1 "D/tline";

2ectan*leF rect 1 this.Client2ectan*le; Font ,ont 1 this.Font; Strin*Format ,ormat 1 Strin*Format.Eeneric0y#o*ra#hic; ,loat #i 1 *.9#iM;
using+ KraphicsPath path 1 KetStringPath+s- dpi- rect- font- format. . 4 g.0rawPath+Pens.)lac6- path./ 5

Figure '.0. 7sing a :raphicsPath *9ject to ,imulate an *utline)*nl! Font

!otice that even though * have +learType on and the Te(t@endering6int set to 5ystemAefault' the outline path was not drawn smoothly. 0s soon as the string was used to create a path' it stopped being te(t and became a shape' which is drawn smoothly or not based on the 5moothing ode property. 0lso' you%ll notice that * showed an e(ample of a really big font 3I>/point4. The string/as/path tric$ doesn%t wor$ very well at lower resolutions because of the translation of font family characters into a series of lines and curves. "ven more interesting uses of paths are available when you apply transforms' which you%ll read about in +hapter S: 0dvanced Arawing.

Where Are We?


We%ve finished up the basics of drawing that we started in +hapter H. *n +hapter S' we tal$ about advanced drawing topics' such as coordinate systems' regions' and transformations.

Chapter .. 1d&anced Drawing


+hapters H and F cover the basics of drawing' including colors' pens' brushes' shapes' paths' images' fonts' and string drawing. This chapter ta$es a loo$ at advanced topics such as page units' world transforms' regions' and optimi=ation techni)ues. 0nd as if that weren%t enough' +hapter I wraps up the tour of the 5ystem.Arawing namespace with a loo$ at printing.

$age &nits
5o far' we%ve been concentrating on drawing to the screen. By default' if you%re drawing in the 8aint event handler' you%re drawing in units of pi(els. "ven if you create a graphics ob<ect from a form using #orm.+reateGraphics' you%ll be drawing in units of pi(els. This is handy because the units of the user interface elements' such as the client rectangle and the position and si=es of the controls' are all in pi(els. 8i(els translate into real/world coordinates based on system settings for !ormal or 5mall versus 7arge or +ustom fonts' the resolution at which the display adapter is running' and the si=e of the monitor. Ta$ing all that into account' only some of which is available programmatically' it would be remar$ably difficult to display physically correct si=es on a monitor.for e(ample' the ruler you see at the top of a word processing program. 7uc$ily' because you can usually ad<ust all this using various systemwide and application/specific settings' people generally si=e things so that they are comfortable' and the real/world si=es are not so important. That is' they%re not important until you need to output to a specific physical si=e. #or e(ample' it%s not important that the ruler at the top of the document *%m typing this sentence into is currently showing an inch as 1 inches. What is important is the proportion of the dimensions of each line to the units shown as &inches& as compared to the width of each line as * type. The principle of WJ5*WJG 3What Jou 5ee *s What Jou Get4 dictates that * should be able to print something very similar to what *%m seeing on the screen. When my word processing program shows a line wrapping at a certain word when * get close to the S.F/inch area inside my margins 3standard 8.F/inch wide paper with a 1/inch margin on each side4' * want that same wrap to happen at the same word when * print the document. To ma$e that happen' we need to be able to write a program that can wrap te(t at units other than pi(els' as shown in #igure S.1.
L1M L1M

* measured it with a ruler from the physical world.

Figure ..1. 8anuall! Drawing in +nches

#igure S.1 shows a ruler mar$ed off in half/inch increments and te(t wrapped to a right margin of S.F inches. We can accomplish this by using the following function to manually convert coordinates and si=es to inches:
,loat 6nches0oGixels(,loat inches) " /sin*( Era#hics * 1 this.CreateEra#hics() ) " ret/rn inches Q *.9#iO; -

This function is used to calculate the width of the ruler' the half/inch tic$ mar$s' and the width of the te(t bo(. #or e(ample' the code that draws the outline of the ruler loo$s li$e this:
/sin*( Font r/lerFont 1 ne! Font("MS Sans Seri,", P.@A,) ) "
int pi*elsPer;nch 1 FG/ // ;nches float rulerFont,eight 1 rulerFont.Si&e;nPoints/pi*elsPer;nch/ // Specify units in inches "ectangleF ruler"ect 1 new "ectangleF+ A- AE.Hf- rulerFont,eight Q B.Hf./ // 0raw in pi*els

*.9ra!2ectan*le( Gens.+lac4, ... -

;nchesToPi*els+ruler"ect.Z.- ;nchesToPi*els+ruler"ect.R.;nchesToPi*els+ruler"ect.Width.- ;nchesToPi*els+ruler"ect.,eight../

The conversion from inches to pi(els is necessary because the units of the Graphics ob<ect passed to the 8aint event are pi(els' which represent the device &nits for the display adapter. 0ll units eventually need to be translated to device units for rendering' but this doesn%t mean that you need to specify drawing in device units. *nstead' the Graphics ob<ect is drawing using page &nits' which default to pi(els in the 8aint event but don%t need to stay that way. The 8age1nit and 8age5cale properties of the Graphics ob<ect allow you to specify different units in which to draw:
// Set page units and scale

g.PageInit 1 KraphicsInit.;nch/ g.PageScale 1 B/ // B unit is B inch

/sin*( Font r/lerFont 1 ne! Font("MS Sans Seri,", P.@A,) ) /sin*( Gen &lac4Gen 1 ne! Gen(Color.+lac4, ?) ) "
// ;nches float rulerFont,eight 1 rulerFont.Ket,eight+g./ // Specify units in inches "ectangleF ruler"ect 1 new "ectangleF+ A- AE.Hf- rulerFont,eight Q B.Hf./ // 0raw in inches g.0raw"ectangle+ !lac6Penruler"ect.Z- ruler"ect.Rruler"ect.Width- ruler"ect.,eight./

... -

Before the code does any drawing' the first thing it does is to set the page unit for the graphics ob<ect to Graphics1nit.*nch and the page scale to 1' which will turn every one unit' whether it%s specified for a position or a si=e' into 1 inch. !otice that we%re using floating point numbers to enable fractional inchesP the floating point numbers will be converted to device units by the Graphics ob<ect. The 8age1nit property can be any value from the Graphics1nit enumeration' so units can be in points or millimeters as well as pi(els or inches. The 8age5cale can be a floating point number' so if we had wanted to specify a scale of 0.1 when specifying a 8age1nit of *nch' then 1 unit would e)ual 0.1 inch' and 10 units would e)ual 1 inch.
L>M L>M

@ecall the Graphics1nit enumeration from +hapter H: Arawing Basics.

!ote the use of a new blac$ pen' in spite of the presence of the 8ens.Blac$ pen that was used in the earlier sample. 0ll the default pens default to 1 unit in width. When the unit was pi(els' that was fine' but when the unit is inches' a 1/unit pen became 1 inch wide. 8ens are specified in units that are interpreted when the pen is used. To avoid having a very wide pen' the code specifies 0 for the width of the pen' and that causes the pen to be 1 device unit wide no matter what the page unit is currently set to. 0lso note that the #ont ob<ect is not affected by the page units. *nstead' recall from +hapter F: Arawing Te(t that #onts are specified using a Graphics1nit argument passed to the constructor' and they default to Graphics1nit.8oint. #inally' notice that the code uses the Get6eight method of the #ont class' passing the Graphics ob<ect. 1nli$e the 6eight property' the Get6eight method is scaled appropriately to the current units of the Graphics ob<ect.

Con&erting Pi3els to Page 7nits


*f a method doesn%t ta$e a Graphics ob<ect as an argument' then it won%t be affected by the page units. #or e(ample' the +lient@ectangle of the form or control being drawn is always

specified in pi(els' ma$ing some consideration necessary when units other than pi(els are being used. To convert bac$ and forth between device and page units' the Graphics ob<ect provides the Transform8oints method:
/sin*( Era#hics * 1 this.CreateEra#hics() ) "
// Set page unit to inches g.PageInit 1 KraphicsInit.;nch/ g.PageScale 1 B/

GointFIJ &ottom2i*ht 1 ne! GointFIJ " ne! GointF(this.ClientSi7e.Wi th, this.ClientSi7e.Hei*ht) -;


// Con#ert client si&e to inches from pi*els g.TransformPoints+ CoordinateSpace.Page- CoordinateSpace.0e#ice- !ottom"ight./

...

The Transform8oints method can convert between any types of coordinates from the +oordinate5pace enumeration 3from the 5ystem. Arawing.Arawing>A namespace4. This code converts to page units 3set to inches in this e(ample4 from device units 3also $nown as pi(els4. The +oordinate5pace enumeration has the following values:
en/m Coor inateS#ace " 9e$ice, Ga*e, Worl , -

The value we haven%t yet discussed is +oordinate5pace.World' which is a whole other world of coordinates 3if you%ll e(cuse the pun4.

Transforms
8age units are useful for specifying things conveniently and letting the Graphics ob<ect sort it out' but there are all $inds of effects that can%t be achieved with such a simple transform. 0 transform is a mathematical function by which units are specified and then transformed into other units. 5o far' we%ve tal$ed about transforming from page units to device units' but a more general/purpose transformation facility is provided via the Transform property of the Graphics ob<ect' which is an instance of the atri( class from the 5ystem.Arawing.Arawing>A namespace:
seale class Matrix : Marshal+y2e,D&'ect, 69is#osa&le " .. Constr/ctors #/&lic Matrix(...); .. $ario/s o$erloa s .. Gro#erties #/&lic ,loatIJ (lements " *et; #/&lic &ool 6s6 entity " *et; #/&lic &ool 6s6n$erti&le " *et; #/&lic ,loat D,,setO " *et; -

#/&lic ,loat D,,setM " *et; .. Metho s #/&lic $oi #/&lic $oi #/&lic $oi #/&lic $oi #/&lic $oi #/&lic $oi #/&lic $oi #/&lic $oi #/&lic $oi #/&lic $oi #/&lic $oi 6n$ert(); M/lti#ly(...); 2eset(); 2otate(...); 2otate)t(...); Scale(...); Shear(...); 0rans,ormGoints(...); 0rans,ormCectors(...); 0ranslate(...); Cector0rans,ormGoints(GointIJ #ts);

The atri( class provides an implementation of a 3(3 mathematical matri%' which is a rectangular array of numbers. The specifics of what ma$e up a matri( in math are beyond the scope of this boo$' but the atri( class provides all $inds of interesting methods that let you use a matri( without doing any of the math.
L3M L3M

9f course' as with all technology' understanding the underlying principles is always helpful.

artin 6eller' my series editor' recommends

,ntrod&ction to )omp&ter /rap"ics' by Eames A. #oley' 0ndries -an Aam' and 5teven D. #einer 30ddison/Wesley' 19934'
for the details of matri( math as related to graphics programming.

The graphics transformation matri( is used to transform orld coordinates' which is what units involved with graphics operations are really specified in. Graphical units are passed as world coordinates' transformed by the transformation matri( into page units' and finally transformed again from page units to display units. 0s you%ve seen' the default page units for the screen are pi(els' and that%s why no page unit conversion happens without our changing the page unit or the scale or both. 5imilarly' by default' the transformation matri( is the identit# matri%' which means that it doesn%t actually do any conversions.

,caling
1sing an instance of a atri( ob<ect instead of page units' we could perform the simple scaling we did in the preceding e(ample:
// Set units to inches using a transform $atri* matri* 1 new $atri*+./ matri*.Scale+g.0piZ- g.0piR./ g.Transform 1 matri*/

/sin*( Font r/lerFont 1 ne! Font("MS Sans Seri,", Y.GHf / g.0piR) ) /sin*( Gen &lac4Gen 1 ne! Gen(Color.+lac4, ?) ) " ,loat r/lerFontHei*ht 1 r/lerFont.EetHei*ht(*); .. 6nches .. S#eci,y /nits in inches 2ectan*leF r/ler2ect 1 ne! 2ectan*leF( ?, ?, X.A,, r/lerFontHei*ht Q 5.A,); .. 9ra! in inches

*.9ra!2ectan*le( &lac4Gen, r/ler2ect.O, r/ler2ect.M, r/ler2ect.Wi th, r/ler2ect.Hei*ht); ...

This code creates a new instance of the atri( class' which defaults to the identity matri(. *nstead of directly manipulating the underlying 3(3 matri( numbers' the code uses the 5cale method to put the numbers in the right place to scale from inches to pi(els using the dpi settings for the current Graphics ob<ect. This transformation is e(actly the same result that we got by setting the page unit to inches and the page scale to 1' e(cept for one detail: the font. 0lthough the page unit and scale do not affect the si=e of fonts' the current transform affects everything' including fonts. This is why the point si=e being passed to the #ont%s constructor in the sample code is first scaled bac$ by the current dpi setting' causing it to come out right after the transformation has occurred. *%d show you the result of using the transform instead of page units' but because it loo$s <ust li$e #igure S.1' it%d be pretty boring.

,caling Fonts
The fact that the world transform wor$s with fonts as well as everything else ma$es scaling fonts an interesting use of the world transform all by itself. 1sually' fonts are specified by height only' but using a transforms allows a font%s height and width to be ad<usted independently of each other' as shown in #igure S.>. Figure ..2. ,caling Font Height +ndependentl! of Font Width

!otice that scaling can even be used in the negative direction' as shown on the far right of #igure S.>' although you%ll want to ma$e sure you specify the rectangle appropriately:
Matrix matrix 1 ne! Matrix();
matri*.Scale+?B- ?B./

*.0rans,orm 1 matrix; *.9ra!Strin*("Scale(:5, :5)", this.Font, +r/shes.+lac4, new "ectangleF+?* ? width- ?y ? height- width- height., ,ormat);

Because scaling by X1 in both dimensions causes all coordinates to be multiplied by X1' to get a rectangle at the appropriate place in the window re)uires negative coordinates. !otice that the width and height are still positive' however' because a rectangle needs positive dimensions to have positive area.

/otation

5caling by a negative amount can loo$ very much li$e rotation' but only in a limited way. 7uc$ily' matrices support rotation directly' as in this code sample' which draws a line rotated along a number of degrees 3as shown in #igure S.34:
,or( int i 1 ?; i =1 T?; i ;1 5? ) " Matrix matrix 1 ne! Matrix();
matri*."otate+i./

*.0rans,orm 1 matrix;
g.0rawCine+Pens.)lac6- A- A- GHA- A./

*.9ra!Strin*( i.0oStrin*(), this.Font, +r/shes.+lac4, text2ect, ,ormat);

Figure .. . $ine from @6, 6A to @2'6, 6A /otated 9! Degrees 6C46

!otice that rotation ta$es place starting to the right hori=ontally and proceeding cloc$wise. Both shapes and te(t are rotated' as would anything else drawn into the rotated graphics ob<ect. @otate wor$s well if you%re rotating around graphical elements with origins at 30' 04' but if you%re drawing multiple lines originating at a different origin' the results may prove unintuitive 3although mathematically sound4' as shown in #igure S.H. Figure ..#. $ine from @2', 2'A to @20', 2'A /otated 9! Degrees 6C46

To rotate more intuitively around a point other than 30' 04' use the @otate0t method 3as shown in #igure S.F4:
,or( int i 1 ?; i =1 T?; i ;1 5? ) " Matrix matrix 1 ne! Matrix();
matri*."otate't+i- new PointF+GH- GH../

*.0rans,orm 1 matrix; *.9ra!Line(Gens.+lac4, @A, @A, @KA, @A); *.9ra!Strin*( i.0oStrin*(), this.Font, +r/shes.+lac4, text2ect, ,ormat); -

Figure ..'. $ine from @2', 2'A to @20', 2'A /otated 9! Degrees 6C46 at @2', 2'A

Translation
*nstead of moving our shapes relative to the origin' as we did when drawing the lines' it%s often handy to move the origin itself by translating the matri( 3as demonstrated in #igure S.S4. Figure .... /ectangle@6, 6, 12', 12'A Drawn at Two *rigins

Translation is very handy when you%ve got a figure to draw that can ta$e on several positions around the display area. Jou can always draw starting from the origin and let the translation decide where the figure actually ends up:
$oi 9ra!La&ele 2ect(Era#hics *, strin* la&el) "
// 'lways draw at +A- A. and let the client // set the position using a transform "ectangleF rect 1 new "ectangleF+A- A- BGH- BGH./

Strin*Format ,ormat 1 ne! Strin*Format(); ,ormat.)li*nment 1 Strin*)li*nment.Center; ,ormat.Line)li*nment 1 Strin*)li*nment.Center; *.9ra!2ectan*le(Gens.+lac4, rect.O, rect.M, rect.Wi th, rect.Hei*ht); *.9ra!Strin*(la&el, this.Font, +r/shes.+lac4, rect, ,ormat); $oi 0ranslationForm%Gaint(o&'ect sen er, Gaint($ent)r*s e) " Era#hics * 1 e.Era#hics;
// Jrigin at +A- A. 0rawCa!eled"ect+g- Translate+A- A. ./ // $o#e origin to +BHA- BHA. $atri* matri* 1 new $atri*+./ matri*.Translate+BHA- BHA./ g.Transform 1 matri*/

0rawCa!eled"ect+g- Translate+BHA- BHA. ./

*n fact' this techni)ue can be used for any of the matri( transformation effects covered so far' in addition to the one yet to be covered: shearing.

,hearing
S"earing is li$e drawing on a rectangle and then pulling along an edge while holding the opposite edge down. 5hearing can happen in both directions independently. 0 shear of =ero represents no shear' and the &pull& is increased as the shear increases. The shear is the proportion of the opposite dimension from one corner to another. #or e(ample' the rectangle 30' 0' >00' F04 sheared 0.F along the ( dimension will have its top/left edge at 30' 04 but its bottom/left edge at 3>F' F04. Because the shear dimension is (' the top edge follows the coordinates of the rectangle' but the bottom edge is offset by the height of the rectangle multiplied by the shear value: bottom7eftT R height a (5hear R F0 a 0.F R >F 6ere%s the code that results in the middle sheared rectangle and te(t in #igure S.I:
2ectan*leF rect 1 ne! 2ectan*leF(?, ?, @??, A?); matrix 1 ne! Matrix();
matri*.Translate+GAA- A./ matri*.Shear+.Hf- Af./ // Shear in * dimension only

*.0rans,orm 1 matrix; *.9ra!Strin*("Shear(.A, ?)", this.Font, +r/shes.+lac4, rect, ,ormat); *.9ra!2ectan*le(Gens.+lac4, rect.O, rect.M, rect.Wi th, rect.Hei*ht);

Figure ..0. Drawing a Constant),i-e /ectangle at =arious ,hearing =alues

Com9ining Transforms
*n addition to a demonstration of shearing' the preceding code snippet offers another interesting thing to notice: the use of two operations.a translation and a shear.on the matri(. ultiple operations on a matri( are cumulative. This is useful because the translation allows you to draw the sheared rectangle in the middle at a translated 30' 04 without stepping on the rectangle at the right 3and the rectangle at the right is further translated out of the way of the rectangle in the middle4. *t%s a common desire to combine effects in a matri(' but be careful' because order matters. *n this case' because translation wor$s on coordinates and shear wor$s on si=es' the two operations can come in any order. 6owever' because scaling wor$s on coordinates as well as si=es' the order in which scaling and translation are performed matters very much:

Matrix matrix 1 ne! Matrix(); matrix.0ranslate(5?, @?); .. Mo$e ori*in to (5?, @?) matrix.Scale(@, N); .. Scale x.!i th an y.!i th &y @ an Matrix matrix 1 ne! Matrix(); matrix.Scale(@, N); .. Scale x.!i th an y.!i th &y @ an matrix.0ranslate(5?, @?); .. Mo$e ori*in to (@?, X?)

N N

*f you find that you%d li$e to reuse a atri( ob<ect but don%t want to undo all the operations you%ve done so far' you can use the @eset method to set it bac$ to the identity matri(. 5imilarly' you can chec$ whether it%s already the identity matri(:
Matrix matrix 1 ne! Matrix(); .. Starts as i entity matrix.2otate(...); .. 0o/che &y inh/man han s
if+ 2matri*.;s;dentity . matri*."eset+./ // )ac6 to identity

Transformation Helpers
*f you%ve been following along with this section on transformations' you may have been tempted to reach into the Graphics ob<ect%s Transform property and call atri( methods directly:
Matrix matrix 1 ne! Matrix(); matrix.Shear(.A,, .A,);
g.Transform 1 matri*/ // wor6s g.Transform.Shear+.Hf- .Hf./ // compiles- !ut doesn:t wor6

0lthough the Transform property will return its atri( ob<ect' it%s returning a copy' so performing operations on the copy will have no effect on the transformation matri( of the graphics ob<ect. 6owever' instead of creating atri( ob<ects and setting the Transform property all the time' you can use several helper methods of the Graphics class that affect the transformation matri( directly:
.. 0rans,ormation metho s o, the Era#hics class seale class Era#hics : Marshal+y2e,D&'ect, 69is#osa&le " ...
pu!lic pu!lic pu!lic pu!lic #oid #oid #oid #oid "esetTransform+./ "otateTransform+..../ ScaleTransform+..../ TranslateTransform+..../

These methods are handy for simplifying transformation code 3although you%ll notice that there%s no 5hearTransform method4:
// No new $atri* o!9ect re>uired g.TranslateTransform+GAA- A./

*.9ra!Strin*("(?, ?)", this.Font, +r/shes.+lac4, ?, ?);

Path Transformations

0s you%ve seen in previous chapters' Graphics8ath ob<ects are very similar to Graphics ob<ects' and the similarity e(tends to transformations. 0 Graphics8ath ob<ect can be transformed <ust as a Graphics ob<ect can' and that%s handy when you%d li$e some parts of a drawing' as specified in paths' to be transformed but not others. Because a path is a collection of figures to be drawn as a group' a transformation isn%t a property to be set and changedP instead' it is an operation that is applied. To transform a Graphics8ath' you use the Transform method:
Era#hicsGath CreateLa&ele 2ectGath(strin* la&el) " Era#hicsGath #ath 1 ne! Era#hicsGath(); ... .. ) rectan*le an strin* ret/rn #ath; $oi Gath0ranslationForm%Gaint(o&'ect sen er, Gaint($ent)r*s e) " Era#hics * 1 e.Era#hics; /sin*( Era#hicsGath #ath 1 CreateLa&ele 2ectGath("My Gath") ) " .. 9ra! at (?, ?) *.9ra!Gath(Gens.+lac4, #ath); .. 0ranslate all #oints in #ath &y (5A?, 5A?) Matrix matrix 1 ne! Matrix(); matrix.0ranslate(5A?, 5A?);
path.Transform+matri*./

*.9ra!Gath(Gens.+lac4, #ath); -

*n addition' Graphics8ath provides transformations that do flattening' widening' and warping via the #latten' Widen' and Warp methods' respectively 3as shown in #igure S.84. Figure ..2. Path Flattening, Widening, and Warping

"ach of these methods ta$es a atri( ob<ect in case you%d li$e to' for e(ample' translate and widen at the same time. 8assing the identity matri( allows each of the specific operations to happen without an additional transformation. The #latten method ta$es a flatness valueP the larger the value' the fewer the number of points used along a curve' and therefore the more &flat.& #igure S.8 shows an ellipse flattened by 10:

.. Gass the i entity matrix as the ,irst ar*/ment to .. sto# any trans,ormation exce#t ,or the ,lattenin*
path.Flatten+new $atri*+.- BA./

*.9ra!Gath(Gens.+lac4, #ath);

The Widen method ta$es a 8en whose width is used to widen the lines and curves along the path. #igure S.8 shows an ellipse widened by a pen of width 10:
using+ Pen widenPen 1 new Pen+Color.Empty /Q ignored Q/- BA. . 4 path.Widen+widenPen./ 5

*.9ra!Gath(Gens.+lac4, #ath);

9ne of the overloads of the Widen method ta$es a flatness value' in case you%d li$e to widen and flatten simultaneously' in addition to the matri( that it also ta$es for translation. The Warp method acts very li$e the s$ewing of an image discussed in +hapter H: Arawing Basics. Warp ta$es' at a minimum' a set of points that define a parallelogram that describes the target' and a rectangle that describes a chun$ of the source. *t uses these arguments to s$ew the source chun$ to the destination parallelogram. #igure S.8 shows the top half of an ellipse s$ewed left:
PointF%( destPoints 1 new PointF%@(/ destPoints%A( 1 new PointF+width/G- A./ destPoints%B( 1 new PointF+width- height./ destPoints%G( 1 new PointF+A- height/G./ "ectangleF src"ect 1 new "ectangleF+A- A- width- height/G./ path.Warp+destPoints- src"ect./

*.9ra!Gath(Gens.+lac4, #ath);

Regions
Whereas paths define a set of figures' with both a frame and an area' a region defines only an area. 0 region can be used for filling or' most importantly' clipping. 0 region is modeled in .!"T with the @egion class from the 5ystem.Arawing namespace:
seale class 2e*ion : Marshal+y2e,D&'ect, 69is#osa&le " .. Constr/ctors #/&lic 2e*ion(...); .. Metho s #/&lic $oi Com#lement(...); #/&lic $oi (xcl/ e(...); #/&lic static 2e*ion FromHr*n(6ntGtr hr*n); #/&lic 2ectan*leF Eet+o/n s(Era#hics *); #/&lic 6ntGtr EetHr*n(Era#hics *); #/&lic 2e*ion9ata Eet2e*ion9ata(); #/&lic 2ectan*leFIJ Eet2e*ionScans(Matrix matrix); #/&lic $oi 6ntersect(...); #/&lic &ool 6s(m#ty(Era#hics *); #/&lic &ool 6s6n,inite(Era#hics *); #/&lic &ool 6sCisi&le(...);

#/&lic #/&lic #/&lic #/&lic #/&lic

$oi $oi $oi $oi $oi

Ma4e(m#ty(); Ma4e6n,inite(); 0rans,orm(...); <nion(...); Oor(...);

Constructing and Filling a /egion


Because the underlying Win3> implementation also has a construct that represents a region 3managed using the Win3> 6@G! data type4' the @egion class can be translated bac$ and forth for interoperability reasons. *n addition to constructing a region from an 6@G!' you can construct regions from @ectangle ob<ects or' more generally' from Graphics8ath ob<ects:
/sin*( Era#hicsGath #ath 1 ne! Era#hicsGath() ) " #ath.) (lli#se(rect); #ath.Flatten(ne! Matrix(), 5N,); #ath.) Strin*("Flattene (lli#se", ...);
using+ "egion region 1 new "egion+path. . 4 g.Fill"egion+)rushes."ed- region./ 5

Jou might be curious about what might drive you to fill a region' especially given that paths can be drawn or filled but regions can only be filled. The answer is that you probably won%t be using regions to draw. Jou%ll probably be using regions to decide what not to draw.

Clipping to a /egion
"very Graphics ob<ect has a region to which all drawing is clippedP any drawing outside the clip region is ignored. By default' the clip region is an infinite region' and this means that it has no bounds and nothing inside the region being drawn will be thrown out. Windows itself will clip outside the region that isn%t part of the invalid region that triggered the 8aint event' but that%s a separate region from the region e(posed by the Graphics ob<ect. Jou can set the clip region on the Graphics ob<ect by setting the +lip property 3as shown in #igure S.94:
/sin*( Era#hicsGath #ath 1 ne! Era#hicsGath() ) " #ath.) (lli#se(this.Client2ectan*le); /sin*( 2e*ion re*ion 1 ne! 2e*ion(#ath) ) " .. Frame cli##in* re*ion (,or ill/stration only) *.9ra!Gath(Gens.2e , #ath);
// 0on:t draw outside the ellipse region g.Clip 1 region/

.. 9ra! a rectan*le 2ectan*le rect 1 this.Client2ectan*le; rect.D,,set(5?, 5?);

rect.Wi th :1 @?; rect.Hei*ht :1 @?; *.Fill2ectan*le(+r/shes.+lac4, rect); *.9ra!Strin*("2ectan*le cli##e to (lli#se", ...); -

Figure ..4. /ectangle Clipped to an %llipse /egion

*f you%d rather call a method than set a property when setting the clip region' you can use the 5et+lip method. *t has overloads that ta$e rectangles and paths and create the underlying clip region itself from those. *f you%d li$e to go bac$ to no clipping' you can use the @eset+lip method. There are also several clip/related methods on the @egion class that deal with intersecting and combining clip regions. 0ll these operate on the underlying methods of the @egion class itself' which supports various combination techni)ues.

/egion Com9ination *perations


@egions support several combination operations for creating more complicated regions from several combined simpler regions. These operations are complement' e(clude' intersect' union' and (or' as shown in #igure S.10. Figure ..16. /egion Com9ination *perations

"ach region combination method ta$es a path' a region' or a rectangle and combines it with the e(isting one. By default' a @egion with no constructor argument is infinite' but you can ma$e it empty by calling a$e"mpty. +reating a @egion with a constructor argument is li$e creating it as empty and then using the 1nion method to add a new shape to the region. The following are e)uivalent:

.. 6ntersect the easy !ay /sin*( 2e*ion re*ion 1 ne! 2e*ion(#ath5) ) " re*ion.6ntersect(#ath@); *.Fill2e*ion(+r/shes.2e , re*ion); .. 6ntersect the har !ay /sin*( 2e*ion re*ion 1 ne! 2e*ion() ) " .. 9e,a/lts to re*ion.6s6n,inite(*) 11 tr/e i,( 3re*ion.6s(m#ty(*) ) re*ion.Ma4e(m#ty(); re*ion.<nion(#ath5); .. ) a #ath re*ion.6ntersect(#ath@); .. 6ntersect !ith another #ath *.Fill2e*ion(+r/shes.2e , re*ion); -

Ta$en together' these combining operations provide a complete set of ways to combine regions for filling and clipping.

-"timi*ed #rawing
*f you%re drawing using page units' transformations' and regions' it%s li$ely that you%re heavy into drawing. *f that%s the case' you%ll be interested in ways to optimi=e your drawings for responsiveness and smooth operation. #irst and foremost' you%ll want to avoid drawing anything that doesn%t need drawing. Jou can do that in one of two ways: redraw only what needs to be redrawn' or don%t draw unnecessarily in the first place. #irst' invalidate only the portion of your drawing surface that needs to be refreshed. *n other words' when drawing the internal state of your form or control' don%t invalidate the whole thing if only a small part of the state has changed:
,loatIJ lotsD,B/m&ers; 2e*ion Eet2e*ionWhereB/m&er6sSho!n(int n/m&er) "...#/&lic ,loat DneB/m&er " set " lotsD,B/m&ersI5J 1 $al/e;
// 0on:t do this3 this.;n#alidate+./ // 0o this3 this.;n#alidate+Ket"egionWhereNum!er;sShown+B../

The *nvalidate function ta$es an optional rectangle or region as the first argument' so you%ll want to invalidate only the portion that needs redrawing' not the entire client area. !ow' when the 8aint event is triggered' all drawing outside the invalid rectangle will be ignored:
$oi B/m&ersForm%Gaint(o&'ect sen er, Gaint($ent)r*s e) " ,or( int i 1 ?; i 31 lotsD,B/m&ers.Len*th; ;;i ) "

0rawNum!er+g- i./ // Will draw only in in#alid region

0lso' there%s an optional second argument that says whether to invalidate children. *f the state of your children doesn%t need updating' don%t invalidate. What%s even better than having drawing operations ignored for efficiency? !ot drawing at all. 5ometimes the client area will be too small to show all of the state. When that happens' there%s no need to draw something that lies entirely outside the visible clip region. To determine whether that%s the case' you can use the *s-isible method of the Graphics ob<ect' which chec$s to see whether a point or any part of a rectangle is visible in the current clipped region:
LHM LHM

This often involves scrolling' which is covered in +hapter 8: +ontrols.

2ectan*le EetB/m&er2ectan*le(int i) "...$oi 9ra!B/m&er(Era#hics *, int i) "


// '#oid something that ta6es a long time to draw if+ 2g.;sLisi!le+KetNum!er"ectangle+i.. . return/

.. 9ra! somethin* that ta4es a lon* time... -

Be careful when doing the calculations that produce the region to invalidate or chec$ing to see whether a hun$ of data is in the invalid regionP it may ta$e more cycles to do the chec$ing than it does to simply do the drawing. 0s always when performance is what you%re after' your best bet is to profile various real/world scenarios.

Dou9le "uffering
0nother way to ma$e your graphics/intensive programs come out sweet and nice is to eliminate flic$er. Flic!er is caused by Windows showing things as they%re drawn.whether you%re drawing shapes bac$ to front or Windows is erasing the invalid region before you even get a chance to handle the 8aint event. To eliminate the problem of displaying the drawing while it%s happening' you can use a techni)ue $nown as double buffering.
LFM LFM

Jou can handle bac$ground painting manually by overriding the 9n8aintBac$ground method.

Do&ble b&ffering is the act of creating a second buffer for the graphics operations to ta$e place in and then' when they%re all finished' blowing all the bits onto the screen at once. Jou can enable double buffering in a form or a control by setting the AoubleBuffer style from the +ontrol5tyles enumeration to true:
#/&lic Form5() " .. 2e8/ire ,or Win o!s Form 9esi*ner s/##ort 6nitiali7eCom#onent();

.. Constr/ctor co e a,ter 6nitiali7eCom#onent call


this.SetStyle+ControlStyles.0ou!le)uffer- true./

Aouble buffering by itself solves only half the problem' however' because Windows does your painting in three phases. #irst' it erases the invalid region by painting it with a Windows/level bac$ground brush. 5econd' it sends the 8aintBac$ground event for your form or control to paint the bac$ground' something that your base class generally handles for you using the Bac$+olor and Bac$ground*mage properties. Jou can handle it yourself' though:
.. 0here is no Gaint+ac4*ro/n e$ent, only this $irt/al metho
protected o#erride #oid JnPaint)ac6ground+PaintE#ent'rgs e. 4

.. Ma4e s/re to #aint the entire client area or call the .. &ase class, or else yo/Fll ha$e st/,, ,rom &elo! sho!in* thro/*h ..&ase.DnGaint+ac4*ro/n (e); e.Era#hics.Fill2ectan*le(+r/shes.+lac4, this.Client2ectan*le);

The third phase of painting is the 8aint event handler. Aouble buffering' by default' collapses the drawing of the 8aintBac$ground and 8aint events into a single operation' but the initial erase phase will still show up as flic$er. To eliminate the erase phase' you must also set the 0ll8ainting*nWm8aint control style:
#/&lic Form5() " .. 2e8/ire ,or Win o!s Form 9esi*ner s/##ort 6nitiali7eCom#onent(); .. Constr/ctor co e a,ter 6nitiali7eCom#onent call
this.SetStyle+ControlStyles.0ou!le)uffer- true./ this.SetStyle+ControlStyles.'llPainting;nWmPaint- true./

.. Bee e -

this.SetStyle+ControlStyles.IserPaint- true./

,or controls that are

o/&le:&/,,ere

!otice the use of the 1ser8aint style. This is needed for controls that are double/buffered 3and doesn%t hurt anything for forms that are double/buffered4. 0lthough double buffering 3without the initial erasing of the bac$ground4 can ma$e all the difference in the user e(perience' double buffering re)uires enough memory to capture the entire visible region at the current color )uality. 0t 3> bits per pi(el' a >00(>00 region re)uires 1FSD in additional memory per drawing operation for that region. *n memory/ constrained systems' this e(tra memory usage could degrade instead of improve the user e(perience.

*ther Drawing *ptions


There are a few other drawing/related +ontrol5tyles you may be interested in:
.. 9ra!in*:relate control styles

en/m ControlStyles " )llGaintin*6nWmGaint, .. Colla#se ra!in* #hases into Gaint e$ent 9o/&le+/,,er, .. 9onFt sho! ra!in* /ntil Gaint e$ent ret/rns <serGaint, .. Control that #aints itsel, s#ecially D#a8/e, .. DnGaint+ac4*ro/n s4i##e , Gaint ra!s all client area 2esi7e2e ra!, .. 6n$ali ate entire client area on resi7e S/##orts0rans#arent+ac4Color, .. Sim/late trans#arent controls ... -

#or e(ample' it%s common for controls that need double buffering to want to automatically redraw when they%re resi=ed. #or this' you use the @esi=e@edraw style:
#/&lic Form5() " .. 2e8/ire ,or Win o!s Form 9esi*ner s/##ort 6nitiali7eCom#onent(); .. 9o/&le &/,,erin* this.SetStyle(ControlStyles.9o/&le+/,,er, tr/e); this.SetStyle(ControlStyles.)llGaintin*6nWmGaint, tr/e); this.SetStyle(ControlStyles.<serGaint, tr/e); .. 2e ra! !hen resi7e this.SetStyle+ControlStyles."esi&e"edraw- true./

The +ontrol5tyles settings apply at the point where Win#orms starts wrapping the functionality of Windows itself' which is the +ontrol base class 3#orms ultimately derive from +ontrol4. 5everal of the +ontrol5tyles settings have nothing to do with drawing but rather govern how the +ontrol class interacts with the underlying operating system. #or more information' see the reference documentation for the +ontrol5tyles enumeration.

Where Are We?


*f +hapters H' F' and S haven%t convinced you of .!"T%s very rich support for drawing' then +hapter I' on drawing to the printer' should do the tric$.

Chapter 0. Printing
1sually' drawing to the screen is pretty easy because screen settings are generally constant during the run of the application. Arawing to a printer' on the other hand' is more complicated because users may change the printer or the printer settings many times' even for a single document. 0lso' paper costs money and can%t be sent through the printer twice 3unless you don%t care what%s on the bac$4' so before users print their documents they want to see what they will loo$ li$e. The actual drawing is largely the same for a printer as it is for the screen' but the printer settings are the interesting part' and the settings are covered in this chapter.

$rint #ocuments
The basic unit of printing in Win#orms is the print document. 0 print doc&ment describes the characteristics of what%s to be printed' such as the title of the document' and provides the events at various parts of the printing process' such as when it%s time to print a page. .!"T models the print document using the 8rintAocument component from the 5ystem.Arawing. 8rinting namespace 3and available on the -5.!"T Toolbo(4:
class Grint9oc/ment : Com#onent, 6Com#onent, 69is#osa&le " .. Constr/ctors #/&lic Grint9oc/ment(); .. Gro#erties #/&lic Ga*eSettin*s 9e,a/ltGa*eSettin*s " *et; set; pu!lic string 0ocumentName 4 get/ set/ 5

#/&lic GrintController GrintController " *et; set; #/&lic GrinterSettin*s GrinterSettin*s " *et; set; .. ($ents #/&lic e$ent Grint($entHan ler +e*inGrint; #/&lic e$ent Grint($entHan ler (n Grint;
pu!lic e#ent PrintPageE#ent,andler PrintPage/

#/&lic e$ent [/eryGa*eSettin*s($entHan ler [/eryGa*eSettin*s; .. Metho s pu!lic #oid Print+./

The basic usage of a 8rintAocument ob<ect is to create an instance' subscribe to at least the 8rint8age event' call the 8rint method' and handle the 8rint8age event:
Print0ocument print0ocumentB/

$oi ...

6nitiali7eCom#onent() "

this.print0ocumentB 1 new Print0ocument+./ this.print0ocumentB.PrintPage 71 new PrintPageE#ent,andler+this.print0ocumentB8PrintPage./

...

$oi #rint+/tton%Clic4(o&'ect sen er, System.($ent)r*s e) " #rint9oc/ment5.9oc/mentBame 1 ,ileBame;


print0ocumentB.Print+./

#oid print0ocumentB8PrintPage+o!9ect sender- PrintPageE#ent'rgs e. 4

.. 9ra! to the #rinter Era#hics * 1 e.Era#hics; /sin*( Font ,ont 1 ne! Font("L/ci a Console", K@) ) " *.9ra!Strin*("Hello,\nGrinter", ,ont, ...); 5

The 8rint8age event is triggered by a call to the 8rintAocument ob<ect%s 8rint method. The 8rint8age event is responsible for actually rendering the state of the document to the printer surface using the Graphics ob<ect. The actual drawing is <ust li$e drawing on any other Graphics ob<ect' as discussed in +hapters H' F' and S. !otice that this sample sets the Aocument!ame property of the document. This string shows up in the )ueue for the printer so that the user can manage the document being printed.

$rint Controllers
The name of the print document also shows up in the dialog that the print document displays during printing. The 8rinting dialog lets the user cancel the print <ob as it%s being spooled to the printer' as shown in #igure I.1. Figure 0.1. The Printing Dialog shown 9! the PrintControllerWith,tatusDialog

The 8rinting dialog is provided by a print controller. The print controller' modeled as the 8rint+ontroller abstract base class and e(posed via the 8rint+ontroller property of the 8rintAocument ob<ect' actually manages the underlying printing process and fires the events as printing progresses. The core print controller is 5tandard8rint+ontroller' which provides the Graphics ob<ect that wraps the printer device' causing the drawing commands

to ma$e it to the printer itself. 6owever' the default print controller is an instance of the 8rint+ontrollerWith5tatusAialog class' which is the one that shows the 8rinting dialog. The 8rint+ontrollerWith5tatusAialog class doesn%t do anything e(cept show the dialogP it relies on 5tandard8rint+ontroller to communicate with the printer. *n fact' creating an instance of the 8rint+ontrollerWith5tatusAialog class re)uires an instance of the 5tandard8rint+ontroller class as a constructor argument. 5o' by default' the print control provided by the print document acts as if you%d written this code:
$oi
PrintController standard 1 new StandardPrintController+./ // Can change the title from Printing to something else PrintController status 1 new PrintControllerWithStatus0ialog+standard- Printing ./ print0ocumentB.PrintController 1 status/

#rint+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

#rint9oc/ment5.Grint();

*f you%d prefer to print without showing a dialog.for e(ample' when you%re printing in the bac$ground.you can use 5tandard8rint+ontroller directly:
$oi
// Suppress the Printing dialog PrintController standard 1 new StandardPrintController+./ print0ocumentB.PrintController 1 standard/

#rint+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

#rint9oc/ment5.Grint();

Print Pre&iew
0nother print controller that .!"T provides is 8review8rint+ontroller' which is used for previewing a document before it%s printed. #igure I.> shows a preview print controller being used to prepare a document for preview. Figure 0.2. The Pre&iewPrintController in use 9! the PrintPre&iewControl

8review8rint+ontroller is primarily used by 8rint8review+ontrol' which shows document previews one page at a time. 8rint8review+ontrol is available on the Toolbo( and uses the drawing commands performed in 8rintAocument%s 8rint8age event handler to display the client area for a standard print preview/style dialog' as shown in #igure I.3.

Figure 0. . The PrintPre&iewControl Hosted in a Custom Form

The client area in #igure I.3 consists of a 8rint8review+ontrol set to fill the client area 3using Aoc$5tyle.#ill4. !otice that it draws what loo$s li$e a piece of paper in miniature' showing the drawing performed by the 8rint8age event handler. The 8rint8review+ontrol class has all $inds of interesting properties and methods for implementing a print preview/ style dialog:
class GrintGre$ie!Control : Control, .. Constr/ctors #/&lic GrintGre$ie!Control(); 6Com#onent, 69is#osa&le, ... "

.. Gro#erties #/&lic &ool )/to\oom " *et; set; #/&lic int Col/mns " *et; set; #/&lic Grint9oc/ment 9oc/ment " *et; set; #/&lic int 2o!s " *et; set; #/&lic int StartGa*e " *et; set; #/&lic &ool <se)nti)lias " *et; set; #/&lic o/&le \oom " *et; set; .. Metho s #/&lic $oi 6n$ali ateGre$ie!(); #/&lic $irt/al $oi 2eset+ac4Color(); #/&lic $irt/al $oi 2esetForeColor(); -

The only re)uirement is that the Aocument property be set to an instance of a 8rintAocument so that the preview control can get the contents of each page to be displayed. Aisplaying multiple pages at once is a matter of setting the @ows and +olumns properties. #igure I.H shows a 8rint8review+ontrol with @ows set to 1 and +olumns set to >. Figure 0.#. Pre&iewing 8ultiple Pages at *nce in PrintPre&iewControl

Aisplaying the ne(t page 3or the ne(t set of pages4 is a matter of setting the 5tart8age property' which dictates the page shown in the upper left of the control. *n addition' 8rint8review+ontrol interprets 8age 1p and 8age Aown to move between pages. The Koom property is a multiplier: 0 Koom of 1.0 is 100Z' a Koom of 0.F is F0Z' and a Koom of >.0 is >00Z. The 0utoKoom property is handy when 8rint8review+ontrol can resi=e. When 0utoKoom is true 3the default4' 8rint8review+ontrol sets the Koom property to scale the page 3or pages4 to a si=e as large as possible inside the control. #inally' the 1se0nti0lias property applies antialiasing to the preview image 3this defaults to false to let the printer%s higher resolution print smoothly without the need to antialias4. 0lthough it%s useful to be able to implement a custom print preview/style dialog with =ooming' page count' and multipage support' often a &standard& print preview dialog is all that%s re)uired. *n those cases' the 8rint8reviewAialog component from the Toolbo( is your friend. #igure I.F shows the 8rint8reviewAialog component in action. Figure 0.'. The PrintPre&iewDialog Component

The 8rint8reviewAialog component uses 8rint8review+ontrol and your 8rintAocument instance to provide a full/featured' preview/style dialog:
PrintPre#iew0ialog printPre#iew0ialogB/

$oi 6nitiali7eCom#onent() " ... ...

this.printPre#iew0ialogB 1 new PrintPre#iew0ialog+./

$oi -

#re$ie!+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

printPre#iew0ialogB.0ocument 1 print0ocumentB/ printPre#iew0ialogB.Show0ialog+./

Basic $rint !vents


0ll print controller implementations rely on the print document%s print events to gather the drawing commands into the graphics ob<ect' either to spool to the printer or to show on the screen:
$oi #rint9oc/ment5%GrintGa*e(o&'ect sen er, GrintGa*e($ent)r*s e) " .. 9ra! to the e.Era#hics o&'ect Era#hics * 1 e.Era#hics;
5 using+ Font font 1 new Font+ Cucida Console - FG. . 4

*.9ra!Strin*("Hello,\nGrinter", ,ont, ...);

!otice that this sample 8rint8age event handler creates a font only for printing. #or a single page' this code is fine' because it creates the font and then reclaims the font resources when the printing is complete. 6owever' if we%re printing more than one page' it%s wasteful to create the font anew on each page. 9n the other hand' creating a font for printing and then caching it in a field seems wasteful if the font is never used again after the print <ob. What we%d really li$e is to be notified when a print <ob is started and ended so that we can have tight control over print/related resources. #or this' we use the print document%s Begin8rint and "nd8rint events:
Font ,ont 1 n/ll;
#oid print0ocumentB8)eginPrint+o!9ect sender- PrintE#ent'rgs e. 4

.. Create ,ont ,or #rintin* ,ont 1 ne! Font("L/ci a Console", K@);

#oid print0ocumentB8EndPrint+o!9ect sender- PrintE#ent'rgs e. 4

.. 2eclaim ,ont ,ont.9is#ose(); ,ont 1 n/ll;

!otice that the Begin8rint and "nd8rint events come with an instance of the 8rint"vent0rgs class. The 8rint"vent0rgs class derives from the +ancel"vent0rgs class and provides no e(tra members. 0s you might guess' the +ancel property of the 8rint"vent0rgs class 3inherited from the +ancel"vent0rgs base class4 is used primarily by a print controller that shows a 1*' such as 8rint+ontrollerWith5tatusAialog' to cancel a print <ob. 1nli$e Begin8rint and "nd8rint' the 8rint8age event comes with an instance of the 8rint8age"vent0rgs class:
class GrintGa*e($ent)r*s : ($ent)r*s " #/&lic &ool Cancel " *et; set; #/&lic Era#hics Era#hics " *et; #/&lic &ool HasMoreGa*es " *et; set; #/&lic 2ectan*le Mar*in+o/n s " *et; #/&lic 2ectan*le Ga*e+o/n s " *et; #/&lic Ga*eSettin*s Ga*eSettin*s " *et; -

0s you%ve seen' the +ancel property is used to cancel a print <ob' and the Graphics property is used for drawing. 6as ore8ages defaults to false. *f there are more pages to print' you set 6as ore8ages to true during the 8rint8age handler for all pages e(cept the last page of a multipage document:
int totalGa*es 1 5N; int #a*e 1 5; $oi #rint9oc/ment5%GrintGa*e(o&'ect sen er, GrintGa*e($ent)r*s e) " Era#hics * 1 e.Era#hics;

*.9ra!Strin*("Hello,\nGrinter\nGa*e: " ; #a*e, ...);


77page/ e.,as$orePages 1 +page < totalPages./

This e(ample has 13 pages' of which as many as S can be shown in the print preview dialog at once 3as shown in #igure I.S4. Figure 0... Printing 8ultiple Pages

%argins
The 8ageBounds rectangle property of the 8rint8age"vent0rgs class represents the entire rectangle of the page' all the way to the edge. The arginBounds rectangle represents the area inside the margins. #igure I.I shows the difference. Figure 0.0. Page"ounds =ersus 8argin"ounds

Both 8ageBounds and arginBounds are always scaled to units of 100 dpi' so a standard 8.F ( 11 inch piece of paper will always have a 8ageBounds rectangle ^0' 0' 8F0' 1100_. With the default margin of 1 inch all the way around' the arginBounds will be at ^100' 100' SF0' 900_. To match the bounds' by default the Graphics1nit for the Graphics ob<ect will be 100 dpi' too' and will be scaled to whatever is appropriate for the printer resolution. #or e(ample' my laser printer is S00 ( S00 dpi. The margin is useful not only because users often want some white space around their pages when they print' but also because many printers can%t print to the edge of the page' so anything printed all the way to the edge is bound to be cut off to some degree. To avoid this' the Graphics ob<ect you get when you%re printing starts at the top/left corner of the printable area of the page. That%s useful for printing outside the margins' such as for headers or footers. 6owever' because printers normally can%t print to the edge of the page' the 8ageBounds rectangle will be too large. To get the actual si=e of the bounding rectangle' you can use the Graphics ob<ect%s -isible+lipBounds rectangle:
.. Eet a #a*e &o/n s !ith an acc/rate si7e 2ectan*leF #a*e+o/n s 1 e.Era#hics.Cisi&leCli#+o/n s; .. 9ra! a hea er *.9ra!Strin*("hea er", ,ont, +r/shes.+lac4, #a*e+o/n s);

1nfortunately' for some reason -isible+lipBounds contains nonsense values when the page is previewed' so in that case' the 8ageBounds rectangle should be used. 0lso' if the

Graphics ob<ect is using a nondefault 8age1nit 3as discussed in +hapter S: 0dvanced Arawing4' -isible+lipBounds will be in different units than 8ageBounds 3which is always in units of 100 dpi4. To handle these variables' it%s useful to have a helper method to return the &real& page bounds in a consistent unit of measure:
// Ket real page !ounds !ased on printa!le area of the page static "ectangle Ket"ealPage)ounds+PrintPageE#ent'rgs e- !ool pre#iew. 4

.. 2et/rn in /nits o, 5.5??th o, an inch i,( #re$ie! ) ret/rn e.Ga*e+o/n s; .. 0ranslate to /nits o, 5.5??th o, an inch 2ectan*leF $#& 1 e.Era#hics.Cisi&leCli#+o/n s; GointFIJ &ottom2i*ht 1 " ne! GointF($#&.Si7e.Wi th, $#&.Si7e.Hei*ht) -; e.Era#hics.0rans,ormGoints( Coor inateS#ace.9e$ice, Coor inateS#ace.Ga*e, &ottom2i*ht); ,loat #iO 1 e.Era#hics.9#iO; ,loat #iM 1 e.Era#hics.9#iM; ret/rn ne! 2ectan*le( ?, ?, (int)(&ottom2i*htI?J.O Q 5?? . #iO), (int)(&ottom2i*htI?J.M Q 5?? . #iM));

Get@eal8ageBounds returns the 8ageBounds rectangle if in preview mode and always scales the returned @ectangle in the same units. This helper allows you to write your printing code to stay within the real bounds of the page:
L1M L1M

Whether the application is printing in preview mode must be managed by the application itself. There is nothing to indicate printing versus print

preview in the printing classes.

// Ket the real page !ounds "ectangle page)ounds 1 Ket"ealPage)ounds+e- this.pre#iew./

.. 9ra! a hea er in the /##er le,t *.9ra!Strin*("hea er", ,ont, +r/shes.+lac4, #a*e+o/n s); .. 9ra! a ,ooter in the lo!er ri*ht Strin*Format ,arFormat 1 ne! Strin*Format(); ,arFormat.)li*nment 1 Strin*)li*nment.Far; ,arFormat.Line)li*nment 1 Strin*)li*nment.Far; *.9ra!Strin*(",ooter", ,ont, +r/shes.+lac4, #a*e+o/n s, ,arFormat);

#or the bul$ of the printed content' however' you should be printing inside the arginBounds rectangle:
$oi #rint9oc/ment5%GrintGa*e(o&'ect sen er, GrintGa*e($ent)r*s e) " Era#hics * 1 e.Era#hics; *.9ra!Strin*(..., e.$argin)ounds ); -

1nfortunately' because arginBounds is offset from 8ageBounds and because 8ageBounds is offset to stay inside the printable region of the page' arginBounds is often lined up at offsets that don%t match the user/specified margins along the edge of the page. #or e(ample' on my 6ewlett/8ac$ard 7aserEet >100' the left edge of the 8ageBounds rectangle is actually b inch in from the left edge of the page' and the top edge is 1C8 inch down from the top. This affects arginBounds' lining up the 1/inch margin * e(pect at 1b inches from the left edge of the page. This poses a problem because neither 8ageBounds nor -isible+lipBounds nor any other information provided by Win#orms actually tells you how much the 8ageBounds is offset from the edge of the paper. To get the physical offsets' you must turn to interoperability with Win3> and the GetAevice+aps function. 1sing that' you can get the printer%s physical T and J offset from the top left and ad<ust the margins appropriately. 6owever' the T and J offset is in printer coordinates' which may not be the same units as arginBounds' so you must convert those units as well. The following helper methods do all that wor$:
ISystem.2/ntime.6ntero#Ser$ices.9ll6m#ort("* iN@. ll")J
static e*tern int Ket0e#iceCaps+;ntPtr hdc- 0e#iceCaps;nde* inde*./

en/m 9e$iceCa#s6n ex " GhysicalD,,setO 1 55@, GhysicalD,,setM 1 55N, // 'd9ust $argin)ounds rectangle when printing !ased // on the physical characteristics of the printer static "ectangle Ket"eal$argin)ounds+PrintPageE#ent'rgs e- !ool pre#iew. 4

i,( #re$ie! ) ret/rn e.Mar*in+o/n s; int cx 1 ?; int cy 1 ?; 6ntGtr h c 1 e.Era#hics.EetH c(); try " .. +oth o, these come &ac4 as e$ice /nits an are not .. scale to 5.5??th o, an inch cx 1 Eet9e$iceCa#s(h c, 9e$iceCa#s6n ex.GhysicalD,,setO); cy 1 Eet9e$iceCa#s(h c, 9e$iceCa#s6n ex.GhysicalD,,setM); ,inally " e.Era#hics.2eleaseH c(h c); .. Create the real mar*in &o/n s &y scalin* the o,,set .. &y the #rinter resol/tion an then rescalin* it .. &ac4 to 5.5??th o, an inch 2ectan*le mar*in+o/n s 1 e.Mar*in+o/n s; int #iO 1 (int)e.Era#hics.9#iO; int #iM 1 (int)e.Era#hics.9#iM; mar*in+o/n s.D,,set(:cx Q 5?? . #iO , :cy Q 5?? . #iM); ret/rn mar*in+o/n s;
5

The Get@eal arginBounds method ta$es preview mode into account and' when you use a real printer' ad<usts arginBounds using the physical offsets' always returning a rectangle in the same units. With this in place' you can safely print inside the margins based on the edges of the paper' as you%d e(pect:
$oi #rint9oc/ment5%GrintGa*e(o&'ect sen er, GrintGa*e($ent)r*s e) " ... *.9ra!Strin*(..., Ket"eal$argin)ounds+e- this.pre#iew.);

0s an alternative to using these helper functions' the .!"T 1.1 #ramewor$ provides a property on 8rintAocument called 9rigin0t argins. This property defaults to false' but setting it to true sets the offset of the 8ageBounds rectangle to be at the margin offset from the physical edge of the page' letting you print at the appropriate margins using the 8ageBounds rectangle. 6owever' this property doesn%t have any effect in preview mode' doesn%t ad<ust the 8ageBounds si=e' and $eeps the arginBounds as offset from the now further offset 8ageBounds. #or these reasons' * don%t find it particularly useful when compared with the Get@eal8ageBounds and Get@eal arginBounds helper methods.

$age Settings
Jou may have noticed that both the arginBounds and the 8age5ettings properties of the 8rint8age"vent0rgs class are read/only. +hanging 8age5ettings on/the/fly 3including the margins4 re)uires handling the print document%s Yuery8age5ettings event:
#oid print0ocumentB8WueryPageSettings+ o!9ect sender- WueryPageSettingsE#ent'rgs e. 4

.. Set mar*ins to ?.A" all the !ay aro/n .. (meas/re in 5??ths o, an inch) e.Ga*eSettin*s.Mar*ins 1 ne! Mar*ins(A?, A?, A?, A?);
5

Yuery8age5ettings"vent0rgs provides only a +ancel property and a 8age5ettings property. The latter is an instance of the 8age5ettings class:
class Ga*eSettin*s : 6Clonea&le " .. Constr/ctors #/&lic Ga*eSettin*s(); #/&lic Ga*eSettin*s(GrinterSettin*s #rinterSettin*s); .. Gro#erties #/&lic 2ectan*le +o/n s " *et; #/&lic &ool Color " *et; set; #/&lic &ool Lan sca#e " *et; set; #/&lic Mar*ins Mar*ins " *et; set; #/&lic Ga#erSi7e Ga#erSi7e " *et; set; #/&lic Ga#erSo/rce Ga#erSo/rce " *et; set; #/&lic Grinter2esol/tion Grinter2esol/tion " *et; set; #/&lic GrinterSettin*s GrinterSettin*s " *et; set; -

*n addition to setting the margins' the 8age5ettings ob<ect can be set to indicate whether color is allowed' the si=e and source of the paper' the printer resolution' and other printer/ specific settings. Jou could ad<ust these properties programmatically during the printing process' but it%s friendlier to let the user do it before the printing begins. #or that' you use the 8age5etupAialog component shown in #igure I.8. Figure 0.2. Page,etupDialog Component with Default Page ,ettings

Before the page setup dialog can be shown' the Aocument property must be set:
Ga*eSet/#9ialo* #a*eSet/#9ialo*5; $oi 6nitiali7eCom#onent() " ... this.#a*eSet/#9ialo*5 1 ne! Ga*eSet/#9ialo*(); ... $oi #a*eSet/#+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " .. Let the /ser select #a*e settin*s #a*eSet/#9ialo*5.9oc/ment 1 #rint9oc/ment5; #a*eSet/#9ialo*5.Sho!9ialo*(); -

When the user presses 9D' the 8age5ettings properties are ad<usted for that instance of the 8rintAocument and are used at the ne(t printing. The 8age5etupAialog itself provides some useful options:
class Ga*eSet/#9ialo* : Common9ialo*, 6Com#onent, 69is#osa&le " .. Constr/ctors #/&lic Ga*eSet/#9ialo*(); .. Gro#erties #/&lic &ool )llo!Mar*ins " *et; set; #/&lic &ool )llo!Drientation " *et; set; #/&lic &ool )llo!Ga#er " *et; set; #/&lic &ool )llo!Grinter " *et; set; #/&lic Grint9oc/ment 9oc/ment " *et; set; #/&lic Mar*ins MinMar*ins " *et; set; #/&lic Ga*eSettin*s Ga*eSettin*s " *et; set; #/&lic GrinterSettin*s GrinterSettin*s " *et; set; #/&lic &ool Sho!Hel# " *et; set; #/&lic &ool Sho!Bet!or4 " *et; set; .. ($ents #/&lic e$ent ($entHan ler Hel#2e8/est; .. Metho s #/&lic $irt/al $oi 2eset();

The 0llowT(( properties dictate whether the dialog allows the user to change things' such as the margins or the orientation 3all these properties default to true4. The in argins property sets the minimum margins that the user can%t go below. The 5how6elp property indicates whether the help button should be shown. By default it isn%t shown' because there%s no built/in help to show 3other than the pop/up help4. *f you set 5how6elp to true' ma$e sure to subscribe to the 6elp@e)uest event so that when the user presses the help button' you can provide help. #inally' the 5how!etwor$ property determines whether the user can navigate the networ$ to find a printer after pressing the 8rinter button 3assuming 0llow8rinter is set to true4.

$rinter Settings
5o far' all the printing in this chapter has been done to the default printer' as defined by Windows itself. The user can change the printer for a document via the 8rinter button on the 8age5etupAialog. *t%s more common' however' to allow the user to choose the printer after choosing the 8rint item from the #ile menu. #or this you use the 8rintAialog component' as shown in #igure I.9. Figure 0.4. The PrintDialog Component

1sing the 8rintAialog component loo$s li$e this:


Grint9ialo* #rint9ialo*5; $oi 6nitiali7eCom#onent() " ... this.#rint9ialo*5 1 ne! Grint9ialo*(); ... .. Can set the Grint9ialo*Fs 9oc/ment #ro#erty .. in the Gro#erty +ro!ser this.#rint9ialo*5.9oc/ment 1 this.#rint9oc/ment5; ... $oi #rint+/tton%Clic4(o&'ect sen er, System.($ent)r*s e) " .. Let the /ser choose the #rinter i,( #rint9ialo*5.Sho!9ialo*() 11 9ialo*2es/lt.DL ) " #rint9oc/ment5.Grint(); -

7i$e 8age5etupAialog' the 8rintAialog component allows you to set a number of options before it is shown:
seale class Grint9ialo* : Common9ialo*, 6Com#onent, 69is#osa&le " .. Constr/ctors #/&lic Grint9ialo*(); .. Gro#erties #/&lic &ool )llo!Grint0oFile " *et; set; #/&lic &ool )llo!Selection " *et; set; #/&lic &ool )llo!SomeGa*es " *et; set; -

#/&lic #/&lic #/&lic #/&lic #/&lic

Grint9oc/ment 9oc/ment " *et; set; GrinterSettin*s GrinterSettin*s " *et; set; &ool Grint0oFile " *et; set; &ool Sho!Hel# " *et; set; &ool Sho!Bet!or4 " *et; set; -

.. ($ents #/&lic e$ent ($entHan ler Hel#2e8/est; .. Metho s #/&lic $irt/al $oi 2eset();

Jou must set the Aocument property before showing a 8rintAialog ob<ect. The other 8rintAialog properties are similar in function to the 8age5etupAialog properties. 0 couple of properties are special' however' because they determine what to print. 7et%s ta$e a loo$.

Print /ange
The 0llow5election property of the 8rintAialog lets the user print only the current selection' and 0llow5ome8ages allows the user to decide on a subset of pages to be printed. Both settings re)uire you to print specially' based on the 8rint@ange property of the 8rinter5ettings class' which is of type 8rint@ange:
L>M L>M

What' if anything' the &current selection& means is application/specific. 6owever' Betsy 6ardinger' the copy editor for this boo$' made an

impassioned plea during her editing of this boo$ that when the print dialog is invo$ed while there is a current selection' the print dialog default to printing only the selection and not all IF pages of the document 3which Betsy often finds herself printing when she doesn%t want to4. Than$ you.

en/m Grint2an*e " )llGa*es, .. Grint all #a*es ( e,a/lt) Selection, .. Grint only the c/rrent selection SomeGa*es, .. Grint #a*es ,rom FromGa*e to 0oGa*e -

Before you can set a print range that%s different from 0ll8ages' you must set 0llow5election or 0llow5ome8ages 3or both4 to true 3they both default to false4. 0llow5ome8ages also re)uires that the 8rinter5ettings #rom8age and To8age be set greater than the default of =ero:
int totalGa*es 1 5N; int #a*e; int maxGa*e; $oi #rint+/tton%Clic4(o&'ect sen er, System.($ent)r*s e) " #rint9oc/ment5.GrinterSettin*s.FromGa*e 1 5; #rint9oc/ment5.GrinterSettin*s.0oGa*e 1 totalGa*es; #rint9oc/ment5.GrinterSettin*s.Minim/mGa*e 1 5; #rint9oc/ment5.GrinterSettin*s.Maxim/mGa*e 1 totalGa*es; #rint9ialo*5.)llo!SomeGa*es 1 tr/e; i,( #rint9ialo*5.Sho!9ialo*() 11 9ialo*2es/lt.DL ) " #rint9oc/ment5.Grint(); -

0lthough it%s not re)uired' it%s a good idea when setting 0llow5ome8ages to true to also set inimum8age and a(imum8age so that users can%t accidentally as$ for a page out of the allowed range. *f 0llow5election or 0llow5ome8ages is set to true' the 8rint8age event will have to chec$ the 8rint@ange and #rom8ageCTo8age properties to see what to print:
int totalGa*es 1 5N; int #a*e; int maxGa*e; $oi #rint+/tton%Clic4(o&'ect sen er, System.($ent)r*s e) " ... i,( #rint9ialo*5.Sho!9ialo*() 11 9ialo*2es/lt.DL ) "
if+ print0ialogB.PrinterSettings.Print"ange 11 Print"ange.SomePages . 4 // Set first page to print to FromPage page 1 print0ocumentB.PrinterSettings.FromPage/ // Set last page to print to ToPage ma*Page 1 print0ocumentB.PrinterSettings.ToPage/ 5 else 4 // Print all pages page 1 B/ ma*Page 1 totalPages/ 5

.. Grint ,rom ,irst #a*e to last #a*e #rint9oc/ment5.Grint();

$oi #rint9oc/ment5%GrintGa*e(o&'ect sen er, GrintGa*e($ent)r*s e) " Era#hics * 1 e.Era#hics; .. #rint c/rrent #a*e...
// Chec6 whether there are more pages to print 77page/ e.,as$orePages 1 +page <1 ma*Page./

*n addition to the 8rint@ange' #rom8age' and To8age properties' the 8rinter5ettings class has many more settings for use in determining e(actly how the user would li$e to print:
class GrinterSettin*s : 6Clonea&le " .. Constr/ctors #/&lic GrinterSettin*s(); .. Gro#erties #/&lic &ool Can9/#lex " *et; #/&lic &ool Collate " *et; set; #/&lic short Co#ies " *et; set; #/&lic Ga*eSettin*s 9e,a/ltGa*eSettin*s " *et; #/&lic 9/#lex 9/#lex " *et; set; pu!lic int FromPage 4 get/ set/ 5

#/&lic static Strin*Collection 6nstalle Grinters " *et; #/&lic &ool 6s9e,a/ltGrinter " *et; -

#/&lic #/&lic #/&lic #/&lic #/&lic #/&lic #/&lic #/&lic #/&lic #/&lic

&ool 6sGlotter " *et; &ool 6sCali " *et; int Lan sca#e)n*le " *et; int Maxim/mCo#ies " *et; int Maxim/mGa*e " *et; set; int Minim/mGa*e " *et; set; Ga#erSi7eCollection Ga#erSi7es " *et; Ga#erSo/rceCollection Ga#erSo/rces " *et; strin* GrinterBame " *et; set; Grinter2esol/tionCollection Grinter2esol/tions " *et; -

pu!lic Print"ange Print"ange 4 get/ set/ 5

#/&lic &ool Grint0oFile " *et; set; #/&lic &ool S/##ortsColor " *et; pu!lic int ToPage 4 get/ set/ 5

.. Metho s #/&lic Era#hics CreateMeas/rementEra#hics();

9ne thing of particular interest is the +reate easurementGraphics method' which returns a Graphics ob<ect based on the printer and its settings. Jou can use this Graphics ob<ect for ma$ing measurement calculations and for enumerating the font families 3using the #ont#amily. Get#amilies method4' all without having to actually start a print operation.

Targeting the Printer


*%d li$e to remind you again that because the drawing happens on a Graphics ob<ect' all the drawing techni)ues from +hapters H' F' and S wor$ <ust as well with printers as they do with screens. 6owever' unli$e the screen' where page units default to 8i(el' the page units for the printer default to Aisplay. #urthermore' whereas Aisplay means 8i(el on the screen' for the printer' Aisplay maps the printer resolution to a logical 100 dpi. Because printers often have different resolutions both vertically and hori=ontally and are almost never 100 dpi anyway' this may seem unintuitive. 6owever' because the default system font setting is 9S dpi on the screen' mapping the printer to a logical 100 dpi means that the default mappings for both screen and printer yield a )uic$ and dirty near/WJ5*WJG' without your having to change a thing. *f you want something even closer' you%re free to use page units such as inches or millimeters' as discussed in +hapter S: 0dvanced Arawing. *f you do change the units' remember to convert 8ageBounds and arginBounds to the new units as well. Jou can use the Graphics method Transform8oints:
static 2ectan*leF 0ranslate+o/n s(Era#hics *, 2ectan*le &o/n s) " .. 0ranslate ,rom /nits o, 5.5??th o, an inch to #a*e /nits ,loat #iO 1 *.9#iO; ,loat #iM 1 *.9#iM; GointFIJ #ts 1 ne! GointFI@J; #tsI?J 1 ne! GointF(&o/n s.O Q #iO . 5??, &o/n s.M Q #iM . 5??); #tsI5J 1 ne! GointF(&o/n s.Wi th Q #iO . 5??, &o/n s.Hei*ht Q #iO . 5??);
g.TransformPoints+

CoordinateSpace.Page- CoordinateSpace.0e#ice- pts./

ret/rn ne! 2ectan*leF( #tsI?J.O, #tsI?J.M, #tsI5J.O, #tsI5J.M); -

The TranslateBounds helper method uses the current Graphics ob<ect to translate a 8ageBounds or arginBounds rectangle from units of 100 dpi to whatever the page unit is set to. This helper is meant to be used from the 8rint8age handler:
$oi #rint9oc/ment5%GrintGa*e(o&'ect sen er, GrintGa*e($ent)r*s e) " Era#hics * 1 e.Era#hics;
g.PageInit 1 KraphicsInit.;nch/

/sin*( Gen thinGen 1 ne! Gen(Color.+lac4, ?) ) " 2ectan*leF #a*e+o/n s 1 Eet2ealGa*e+o/n s(e, #re$ie!); #a*e+o/n s 1 Translate)ounds+g- "ectangle.Truncate+page)ounds../ *.9ra!2ectan*le( thinGen, #a*e+o/n s.O, #a*e+o/n s.M, #a*e+o/n s.Wi th, #a*e+o/n s.Hei*ht); ... ...

!otice that 8age1nit is set on the Graphics ob<ect right away so that any drawing that ta$es place in the 8rint8age handler will be in those units. #inally' notice that the 8rint8age handler sets the 8age1nit during each page being printed. "ach time the 8rint8age handler is called' it gets a fresh Graphics ob<ect' so don%t forget to reset its options every time.

Where Are We?


The nucleus around which the entire .!"T printing architecture revolves is the print document. *t%s the one that initiates the printing process' fires the print events 3through the use of a print controller4' holds the page and printer settings' and gathers the drawing commands for rendering to the printer or to the preview control. Jou implement the actual drawing using the Graphics ob<ect' as discussed in +hapters H' F' and S' and this drawing is governed by the settings held by the print document.

Chapter 2. Controls

The basic unit of the user interface in Win#orms is the control. "verything that interacts directly with the user in a region defined by a container is a control. This includes controls that do everything themselves' as well as standard controls such as the Te(tBo(' user controls 3controls that contain other controls4' and even the #orm class itself. This chapter covers the broad categories of the standard controls provided by Win#orms. *t e(plains how to build custom and user controls and how to provide support for drag and drop' the most popular $ind of intercontrol communication. *f you%d li$e a survey of the standard controls' refer to 0ppendi( A: 5tandard Win#orms +omponents and +ontrols.

Standard Controls
0 control is a class that derives from the 5ystem.Windows.#orms.+ontrol base 3either directly or indirectly4 and is responsible for drawing a chun$ of the container' which is either a form or another control. Win#orms comes with several standard controls available by default on the Toolbo( in -5.!"T. These controls can be bro$en into the following ad hoc categories:

'ction controls.

+ontrols such as Button and Toolbar e(ist to allow the user to clic$ on them to cause something to happen. Lalue controls. +ontrols such as 7abel and 8ictureBo( show the user a value' such as te(t or a picture' but don%t allow the user to change the value. 9ther value controls' such as Te(tBo( or AateTime8ic$er' allow the user to change the value being displayed. Cist controls. +ontrols such as 7istBo( and +omboBo( show the user a list of data. 9ther list controls' such as AataGrid' allow the user to change the data directly. Container controls. GroupBo(' 8anel' and Tab+ontrol e(ist to contain and arrange other controls.

0lthough 0ppendi( A: 5tandard Win#orms +omponents and +ontrols lists and shows each of the standard controls' it%s useful to consider each category for the features that the controls share in common.

1ction Controls
The action controls are Button' ToolBar' enuBar' and +onte(t enu. These controls e(ist to provide something for the user to clic$ on to trigger an action in the application. "ach of the available actions is labeled and' in the case of ToolBar' can have an optional image. The ma<or event the action controls is the +lic$ event:
L1M L1M

Technically'

enuBar and the +onte(t enu classes aren%t controls because they don%t derive from the +ontrol base class' but they fit so nicely

into this category that * didn%t have the heart to remove them. The details of these two components can be found in +hapter >: #orms.

#oid !uttonB8Clic6+o!9ect sender- E#ent'rgs e. 4

Messa*e+ox.Sho!("D/ch3");
5

"(cept for Button' the rest of the action controls are actually containers of multiple subob<ects that the user interacts with. #or e(ample' a ain enu ob<ect contains one or more enu*tem ob<ects' one for each menu item that can fire a +lic$ event:
$oi exitMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " this.Close(); -

The ToolBar control also contains a collection of ob<ects' of type ToolBarButton. 6owever' when the user clic$s' the event is sent for ToolBar itself' so the event handler is responsible for using the Button property of the ToolBarButton+lic$"vent0rgs to figure out which button was pressed:
$oi tool+ar5%+/ttonClic4( o&'ect sen er, 0ool+ar+/ttonClic4($ent)r*s e) "
if+ e.)utton 11 fileE*itTool)ar)utton . 4

this.Close();
5 else if+ e.)utton 11 help'!outTool)ar)utton . 4 5

Messa*e+ox.Sho!("0he stan ar

controls are cool");

Because menu items and toolbar buttons often result in the same action' such as showing the 0bout bo(' it%s good practice to centrali=e the code and call it from both event handlers:
$oi $oi File(xit() "...Hel#)&o/t() "...-

$oi ,ile(xitMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " File(xit(); $oi hel#)&o/tMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " Hel#)&o/t(); $oi tool+ar5%+/ttonClic4( o&'ect sen er, 0ool+ar+/ttonClic4($ent)r*s e) " i,( e.+/tton 11 ,ile(xit0ool+ar+/tton ) " File(xit(); else i,( e.+/tton 11 hel#)&o/t0ool+ar+/tton ) " Hel#)&o/t(); -

*f you centrali=e the handling of an action' you don%t have to worry about which controls trigger itP no matter how many do' all of them will get the same behavior.

While we%re on the topic of ToolBar control' you may be curious as to how images are assigned to each button. 0ssigning an image to a toolbar button involves creating and inde(ing into an *mage7ist' which is a component that holds a list of images for use by controls that display images. *mage lists are discussed later in this chapter.

=alue Controls
The value controls ma$e up the set of controls that show and optionally allow editing of a single value. They can be bro$en down further by the data type of the value:

String #alues:

7abel' 7in$7abel' Te(tBo(' @ichTe(tBo(' 5tatusBar Numeric #alues: !umeric1pAown' 65crollBar' -5crollBar' 8rogressBar' Trac$Bar )oolean #alues: +hec$Bo(' @adio Button 0ate #alues: AateTime8ic$er' onth+alendar Kraphical #alues: 8ictureBo(' 8rint8review+ontrol

The string value controls e(pose a property called Te(t that contains the value of the control in a string format. The 7abel control merely displays the te(t. The 7in$7abel control displays the te(t as if it were an 6T 7 lin$' firing an event when the lin$ is clic$ed. The 5tatusBar control displays the te(t in the same way a 7abel does 3but' by default' doc$ed to the bottom of the container4' but it also allows for multiple chun$s of te(t separated into panels. *n addition to displaying te(t' the Te(tBo( control allows the user to edit the te(t in single or multiline mode 3depending on the value of ultiline property4. The @ichTe(tBo( control allows for editing li$e Te(tBo( but also supports @T# 3@ich Te(t #ormat4 data' which includes font and color information as well as graphics. When the Te(t value of either of these controls changes' the Te(t+hanged event is fired. 0ll the numeric value controls e(pose a numeric -alue property' whose value can range from the inimum to the a(imum property. The difference is only a matter of which 1* you%d li$e to show to the user. When the -alue properties change' the -alue+hanged property is fired. The Boolean value controls.+hec$Bo( and @adioButton.e(pose a +hec$ed property that reflects whether or not they%re chec$ed. Both Boolean value controls can also be set to a third' &indeterminate& state' which is one of the three possible values e(posed from the +hec$5tate property. When the +hec$5tate is changed' the +hec$ed+hanged and +hec$5tate+hanged events are fired. The date value controls allow the user to pic$ one or more instances of the AateTime type. onth+alendar allows the choice of beginning and ending dates as e(posed by the 5election@ange property 3signaled by the 5election@ange+hanged event4. AateTime8ic$er allows the user to enter a single date and time as e(posed by the -alue property 3signaled by the -alue+hanged event4.

The graphical value controls show images' although neither allows the images to be changed. The 8ictureBo( control shows any image as set by the *mage property. 8rint8review+ontrol shows' one page at a time' a preview of print data generated from a 8rintAocument ob<ect 3as described in +hapter I: 8rinting4.

$ist Controls
*f one value at a time is good' then several values at a time must be better. The list controls .+omboBo(' +hec$ed7istBo(' 7istBo(' Aomain1pAown' 7ist-iew' AataGrid' and Tree-iew.can show more than one value at a time. ost of the list controls.+omboBo(' +hec$ed7istBo(' 7istBo(' and Aomain1pAown. show a list of ob<ects e(posed by the *tems collection. To add a new item you use this collection:
$oi Form5%Loa (o&'ect sen er, ($ent)r*s e) "

list)o*B.;tems.'dd+ an item ./

This sample adds a string ob<ect to the list of items' but you can add any ob<ect:
$oi Form5%Loa (o&'ect sen er, ($ent)r*s e) "

0ateTime !day 1 0ateTime.Parse+ BDDH?AY?@A E3AGpm ./ list+ox5.6tems.) (!day);

To come up with a string to display' the list controls that ta$e ob<ects as items call the To5tring method. To show your own custom items in a list control' you simply implement the To5tring method:
class Gerson " strin* name; int a*e; #/&lic Gerson(strin* name, int a*e) " this.name 1 name; this.a*e 1 a*e; #/&lic strin* Bame " *et " ret/rn name; set " name 1 $al/e; #/&lic int )*e " *et " ret/rn a*e; set " a*e 1 $al/e; -

#/&lic o$erri e strin* 0oStrin*() " ret/rn strin*.Format(""?- is "5- years ol ", Bame, )*e); -

$oi Form5%Loa (o&'ect sen er, ($ent)r*s e) " GersonIJ &oys 1 " ne! Gerson("0om", K), ne! Gerson("Wohn", P) -; ,oreach( Gerson &oy in &oys ) " list+ox5.6tems.) (&oy); -

#igure 8.1 shows the instances of the custom type shown in a 7istBo( control. Figure 2.1. Custom T!pe ,hown in a $ist"o3 Control

Because the 7ist-iew control can show items with multiple columns and states' its *tems collection is populated with instances of the 7ist-iew*tem class. "ach item has a Te(t property' which represents the te(t of the first column' and then a collection of subitems that represent the rest of the columns:
$oi Form5%Loa (o&'ect sen er, ($ent)r*s e) " GersonIJ &oys 1 " ne! Gerson("0om", K), ne! Gerson("Wohn", P) -; ,oreach( Gerson &oy in &oys ) " .. BD0(: )ss/mes Col/mns collection alrea y has @ col/mns
CistLiew;tem item 1 new CistLiew;tem+./ item.Te*t 1 !oy.Name/ item.Su!;tems.'dd+!oy.'ge.ToString+../ listLiewB.;tems.'dd+item./

#igure 8.> shows the multicolumn 7ist-iew filled via this code. Figure 2.2. 8ulticolumn $ist=iew

The Tree-iew control shows a hierarchy of items that are instances of the Tree!ode type. "ach Tree!ode ob<ect contains the te(t' some optional images' and a !odes collection

containing subnodes. Which node you add to determines where the newly added node will show up in the hierarchy:
$oi Form5%Loa (o&'ect sen er, ($ent)r*s e) " 0reeBo e #arentBo e 1 ne! 0reeBo e(); #arentBo e.0ext 1 "Chris";
// 'dd a node to the root of the tree treeLiewB.Nodes.'dd+parentNode./

0reeBo e chil Bo e 1 ne! 0reeBo e(); chil Bo e.0ext 1 "Wohn";


// 'dd a node under an e*isting node parentNode.Nodes.'dd+childNode./

#igure 8.3 shows the result of filling a Tree-iew control using this sample code. Figure 2. . 1 Parent (ode and a Child (ode in a Tree=iew Control

The AataGrid control gets its data from a collection set by using the Aata5ource property:
$oi Form5%Loa (o&'ect sen er, ($ent)r*s e) " GersonIJ &oys 1 " ne! Gerson("0om", K), ne! Gerson("Wohn", P) -;
dataKridB.0ataSource 1 !oys/

The AataGrid shows each public property of the ob<ects in the collection as a column' as shown in #igure 8.H Figure 2.#. 1 Data:rid ,howing a Collection of Custom T!pes.

0 AataGrid can also show hierarchical data and do all $inds of other fancy things. Jou%ll find many more details about the AataGrid control in +hapter 13: Aata Binding and Aata Grids. $ist +tem ,election

"ach of the list controls e(poses a property to report the current selection 3or a list of selections' if the list control supports multiple selections4 and fires an event when the selection changes. #or e(ample' the following code handles the 5elected*nde(+hanged event of the 7istBo( control and uses the 5elected*nde( property to pull out the currently selected ob<ect:
$oi list+ox5%Selecte 6n exChan*e (o&'ect sen er, ($ent)r*s e) " .. Eet the selecte o&'ect o&'ect selection 1 list+ox5.6temsIlist+ox5.Selecte 6n exJ; Messa*e+ox.Sho!(selection.0oStrin*()); .. 0he o&'ect is still the same ty#e as !hen !e a Gerson &oy 1 (Gerson)selection; Messa*e+ox.Sho!(&oy.0oStrin*()); e it

!otice that the 5elected*nde( property is an offset into the *tems collection that pulls out the currently selected item. The item comes bac$ as the &ob<ect& type' but a simple cast allows us to treat it as an instance of e(actly the same type as when it was added. This is useful when a custom type shows data using To5tring but has another characteristic' such as a uni)ue identifier' that is needed programmatically. *n fact' for the list controls that don%t ta$e ob<ects' such as Tree-iew and 7ist-iew' each of the items supports a Tag property for stashing away uni)ue *A information:
$oi Form5%Loa (o&'ect sen er, ($ent)r*s e) " 0reeBo e #arentBo e 1 ne! 0reeBo e(); #arentBo e.0ext 1 "Chris";
parentNode.Tag 1 AAA?AA?AAAA / // Put in e*tra info

treeCie!5.Bo es.) -

(#arentBo e);

$oi treeCie!5%),terSelect(o&'ect sen er, 0reeCie!($ent)r*s e) " 0reeBo e selection 1 treeCie!5.Selecte Bo e;


o!9ect tag 1 selection.Tag/ // Pull out e*tra info

Messa*e+ox.Sho!(ta*.0oStrin*()); -

7ist controls support either custom types or the Tag property but not both. The idea is that because the lists contain instances of custom types' any e(tra information can simply be $ept as needed. 1nfortunately' the lac$ of a Tag property ma$es it more difficult to associate *A information with simple types' such as strings. 6owever' a simple wrapper will allow you to add a tag to a list item of any type:
class 0a**e 6tem " #/&lic o&'ect 6tem; #/&lic o&'ect 0a*; #/&lic 0a**e 6tem(o&'ect item, o&'ect ta*) " this.6tem 1 item;

this.0a* 1 ta*;

#/&lic o$erri e strin* 0oStrin*() " ret/rn 6tem.0oStrin*(); $oi Form5%Loa (o&'ect sen er, ($ent)r*s e) " .. ) t!o ta**e strin*s com&o+ox5.6tems.) (ne! 0a**e 6tem("0om", "???:??:????)); com&o+ox5.6tems.) (ne! 0a**e 6tem("Wohn", "???:??:????"));

$oi com&o+ox5%Selecte 6n exChan*e (o&'ect sen er, ($ent)r*s e) " 0a**e 6tem selection 1 (0a**e 6tem)com&o+ox5.6temsIcom&o+ox5.Selecte 6n exJ; o&'ect ta* 1 selection.0a*; Messa*e+ox.Sho!(ta*.0oStrin*()); -

The Tagged*tem wrapper $eeps trac$ of an item and a tag. The To5tring method lets the item decide how it should be displayed' and the *tem and Tag properties e(pose the parts of the Tagged*tem ob<ect for use in processing the current selection.

Container Controls
Whereas the list controls hold multiple ob<ects' the <ob of the container controls 3GroupBo(' 8anel' and Tab+ontrol4 is to hold multiple controls. The 5plitter control is not itself a container' but it can be used with container controls doc$ed to a container%s edge for si=ing. 0ll the anchoring' doc$ing' splitting' and grouping principles covered in +hapter >: #orms also apply to container controls. #igure 8.F shows e(amples of container controls in action. Figure 2.'. Container Controls in 1ction

#igure 8.F shows a GroupBo( on the left' doc$ed to the left edge of the containing form' and a Tab+ontrol with two Tab8age controls on the right' split with a 5plitter control in the middle.

The GroupBo( sets the caption of the group using its Te(t property. The 8anel has no label. The Tab+ontrol is really a container of Tab8age controls. *t%s the Tab8age controls that contain other controls' and the Te(t property shows up as the label of the tab. The only other interesting member of a container control is the +ontrols collection' which holds the list of contained controls. #or e(ample' the list bo( in #igure 8.F is contained by the +ontrols collection of the group bo(:
$oi 6nitiali7eCom#onent() " ...

// group)o*B this.group)o*B.Controls.'dd"ange+ new System.Windows.Forms.Control%( 4 this.list)o*B5./

...
// FormB this.Controls.'dd"ange+ new System.Windows.Forms.Control%( 4 this.ta!ControlBthis.splitterBthis.group)o*B5./

...

!otice in the form%s *nitiali=e+omponent that the group bo(%s +ontrols collection is used to contain the list bo( and that the form%s +ontrols collection is used to contain the tab control' the splitter' and the group bo(. *t%s a child control%s container that determines how a control is arranged. #or e(ample' when the list bo(%s Aoc$ property is set to #ill' the doc$ing is relative to its container 3the group bo(4 and not to the form that actually creates the control. When a control is added to a container%s +ontrols collection' the container control becomes the child control%s parent. 0 child control can discover its container by using its 8arent property.

+mage$ists
*n addition to showing te(t data' several of the controls.including Tab8age' ToolBarButton' 7ist-iew' and Tree-iew.can show optional images. These controls get their images from an instance of the *mage7ist component. The *mage7ist component provides Aesigner support for adding images at design time and then e(poses them by inde( number to controls that use them. "ach image/capable control e(poses one or more properties of type *mage7ist. This property is named &*mage7ist& if the control supports a single set of images. But if the control supports more than one list of images' the property name contains a phrase that includes the term &*mage7ist.& #or e(ample' the Tab+ontrol e(poses the *mage7ist property for use by all the contained Tab8age controls' whereas the 7ist-iew control e(poses the 7arge*mage7ist' 5mall*mage7ist' and 5tate*mage7ist properties for the three $inds of images it can display.

@egardless of the number of *mage7ist properties a control supports' when an item re)uires a certain image from the *mage7ist' the item e(poses an inde( property to offset into the *mage7ist component%s list of images. The following is an e(ample of adding an image to each of the items in a Tree-iew control:
$oi 6nitiali7eCom#onent() " ... ...
// ;mageCist associated with the TreeLiew this.treeLiewB.;mageCist 1 this.imageCistB/

this.treeLiewB 1 new TreeLiew+./ this.imageCistB 1 new ;mageCist+this.components./

... ... -

// ;mages read from Form:s resources this.imageCistB.;mageStream 1 .../

$oi Form5%Loa (o&'ect sen er, ($ent)r*s e) " 0reeBo e #arentBo e 1 ne! 0reeBo e(); #arentBo e.0ext 1 "Chris";
parentNode.;mage;nde* 1 A/ // 0ad image parentNode.Selected;mage;nde* 1 A/

treeCie!5.Bo es.)

(#arentBo e);

0reeBo e chil Bo e 1 ne! 0reeBo e(); chil Bo e.0ext 1 "Wohn";


childNode.;mage;nde* 1 B/ // Son image childNode.Selected;mage;nde* 1 B/

#arentBo e.Bo es.) -

(chil Bo e);

1sing the Aesigner to associate images with the *mage7ist component causes the images themselves to be stored in form/specific resources. *nitiali=e+omponent pulls them in at run time by setting the image list%s *mage5tream propertyP *nitiali=e+omponent also associates the image list with the tree view by setting the tree view%s *mage7ist property. "ach node in a tree view supports two image inde(es: the default image and the selected image. "ach of these properties inde(es into the image list associated with the tree view. #igure 8.S shows the result.
L>M L>M

@esources are covered in detail in +hapter 10: @esources.

Figure 2... 1 Tree=iew 7sing an +mage$ist

When you collect related images in an *mage7ist component' setting images in a control is as simple as associating the appropriate image list 3or image lists4 with the control and then setting each image inde( as appropriate. The control itself handles the wor$ of drawing the image.

*wner)Draw Controls
*mage lists allow you to augment the display of certain controls with an image. *f you%d li$e to ta$e over the drawing of a control' owner/draw controls support this very thing. 0n o ner(dra control provides events that allow a control%s owner 3or the control itself4 to ta$e over the drawing chores from the control in the underlying operating system. +ontrols that allow owner draw.such as menus' some of the list controls' the tab page control' and status bar panel control.e(pose a property that turns owner drawing on and then fires events to let the container $now that it should do the drawing. #or e(ample' the 7istBo( control e(poses the Araw ode property' which can be one of the following values from the Araw ode enumeration:
en/m 9ra!Mo e " Bormal, .. Control ra!s its o!n items ( e,a/lt) D!ner9ra!Fixe , .. Fixe :si7e c/stom ra!in* o, each item D!ner9ra!Caria&le, .. Caria&le:si7e c/stom ra!in* o, each item -

#igure 8.I shows an owner/draw 7istBo( control that changes the style to *talics when it%s drawing the selected item. Figure 2.0. *wner)Drawn $ist "o3

To handle the drawing for a 7istBo(' you first set the Araw ode property to something other than !ormal 3the default4' and then you handle the 7istBo( control%s Araw*tem event:
$oi 6nitiali7eCom#onent() " ...
this.list)o*B.0raw$ode 1 0raw$ode.Jwner0rawFi*ed/

... $oi list+ox5%9ra!6tem(o&'ect sen er, 9ra!6tem($ent)r*s e) "

// 0raw the !ac6ground e.0raw)ac6ground+./ // Ket the default font Font drawFont 1 e.Font/

&ool o/rFont 1 ,alse; .. 9ra! in italics i, selecte

if+ +e.State T 0raw;temState.Selected. 11 0raw;temState.Selected . 4

o/rFont 1 tr/e; ra!Font 1 ne! Font( ra!Font, FontStyle.6talic);

/sin*( +r/sh &r/sh 1 ne! Soli +r/sh(e.ForeColor) ) "


// 0raw the list !o* item e.Kraphics.0rawString+list)o*B.;tems%e.;nde*(.ToString+.drawFontnew Solid)rush+e.ForeColor.e.)ounds./

i,( o/rFont )

ra!Font.9is#ose();

// 0raw the focus rectangle e.0rawFocus"ectangle+./

The Araw*tem method comes with the Araw*tem"vent0rgs ob<ect:


class 9ra!6tem($ent)r*s : ($ent)r*s " .. Gro#erties #/&lic Color +ac4Color " *et; #/&lic 2ectan*le +o/n s " *et; #/&lic Font Font " *et; #/&lic Color ForeColor " *et; #/&lic Era#hics Era#hics " *et; #/&lic int 6n ex " *et; #/&lic 9ra!6temState State " *et; .. Metho s #/&lic $irt/al $oi #/&lic $irt/al $oi 9ra!+ac4*ro/n (); 9ra!Foc/s2ectan*le();

The Araw*tem event is called whenever the item is drawn or when the item%s state changes. The Araw*tem"vent0rgs ob<ect provides all the information you%ll need to draw the item in )uestion' including the inde( of the item being drawn' the bounds of the rectangle to draw in' the preferred font' the preferred color of the foreground and bac$ground' and the Graphics ob<ect to do the drawing on. Araw*tem"vent0rgs also provides the selection state so that you can draw selected items differently 3as our e(ample does4. Araw*tem"vent0rgs also gives you a couple of helper methods for drawing the bac$ground and the focus rectangle if necessary. Jou%ll usually use the latter to brac$et your own custom drawing. When you set Araw ode to 9wnerAraw#i(ed' each item%s si=e is set for you. *f you%d li$e to influence the si=e' too' you can set Araw ode to 9wnerAraw-ariable' and' in addition to doing the drawing in the Araw*tem handler' you can specify the height in the easure*tem handler:
$oi 6nitiali7eCom#onent() " ...

this.list)o*G.0raw$ode 1 Jwner0rawLaria!le/

... $oi list+ox@%Meas/re6tem(o&'ect sen er, Meas/re6tem($ent)r*s e) " .. Ma4e e$ery e$en item t!ice as hi*h i,( e.;nde* ] @ 11 ? ) e.;tem,eight Q1 G; -

The easure*tem event provides an instance of the essage*tem"vent0rgs class' which gives you useful properties for getting and setting each item%s height:
class Meas/re6tem($ent)r*s : ($ent)r*s " .. Gro#erties #/&lic Era#hics Era#hics " *et; pu!lic int ;nde* 4 get/ 5 pu!lic int ;tem,eight 4 get/ set/ 5 pu!lic int ;temWidth 4 get/ set/ 5

#igure 8.8 shows the effects of doubling the heights of the even items 3as well as continuing to show the selection in italics4. Figure 2.2. 1n *wner)Drawn $ist "o3 7sing =aria9le Height

1nli$e the Araw*tem event' the easure*tem event is called only once for every item in the control' so things such as selection state can%t be a factor when you decide how big to ma$e the space for the item. ControlPaint 9ften' owner drawing is used to draw a control that loo$s <ust li$e an e(isting Windows control but has one minor addition' such as an image added to a menu item. *n those cases' you%d li$e to avoid spending any time duplicating the way every version of Windows draws

its controls' and you can use the +ontrol8aint helper class for that purpose. The +ontrol8aint class has static members for drawing common controls' lines' grids' and types of te(t:
seale class ControlGaint " .. Gro#erties #/&lic static Color ContrastControl9ar4 " *et; .. Metho s #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static 6ntGtr CreateH+itma#5X+it(...); 6ntGtr CreateH+itma#ColorMas4(...); 6ntGtr CreateH+itma#0rans#arencyMas4(...); Color 9ar4(...); Color 9ar49ar4(...); $oi 9ra!+or er(...); $oi 9ra!+or erN9(...); $oi 9ra!+/tton(...); $oi 9ra!Ca#tion+/tton(...); $oi 9ra!Chec4+ox(...); $oi 9ra!Com&o+/tton(...); $oi 9ra!ContainerEra&Han le(...); $oi 9ra!Foc/s2ectan*le(...); $oi 9ra!Era&Han le(...); $oi 9ra!Eri (...); $oi 9ra!6ma*e9isa&le (...); $oi 9ra!Loc4e Frame(...); $oi 9ra!Men/Ely#h(...); $oi 9ra!Mixe Chec4+ox(...); $oi 9ra!2a io+/tton(...); $oi 9ra!2e$ersi&leFrame(...); $oi 9ra!2e$ersi&leLine(...); $oi 9ra!Scroll+/tton(...); $oi 9ra!SelectionFrame(...); $oi 9ra!Si7eEri#(...); $oi 9ra!Strin*9isa&le (...); $oi Fill2e$ersi&le2ectan*le(...); Color Li*ht(...; Color Li*htLi*ht(...);

#or e(ample' you can use +ontrol8aint to draw disabled te(t in an owner/draw status bar panel:
$oi stat/s+ar5%9ra!6tem(o&'ect sen er, Stat/s+ar9ra!6tem($ent)r*s e) " .. Ganels onFt ra! !ith their +ac4Color, .. so itFs not set to somethin* reasona&le, an .. there,ore e.9ra!+ac4*ro/n () isnFt hel#,/l. .. 6nstea , /se the +ac4Color o, the Stat/s+ar, !hich is the sen er Stat/s+ar stat/s+ar 1 (Stat/s+ar)sen er; /sin*( +r/sh &r/sh 1 ne! Soli +r/sh(stat/s+ar.+ac4Color) ) " e.Era#hics.Fill2ectan*le(System+r/shes.Control, e.+o/n s); -

.. 9ra! text as isa&le Strin*Format ,ormat 1 ne! Strin*Format(); ,ormat.Line)li*nment 1 Strin*)li*nment.Center; ,ormat.)li*nment 1 Strin*)li*nment.Center;
ControlPaint.0rawString0isa!led+ e.Kraphics- ,i2 - this.Font- this.ForeColor- e.)ounds- format./

What ma$es the +ontrol8aint class handy is that it ta$es into account the conventions between versions of the operating system about the latest way to draw whatever it is you%re trying to draw. 5o' instead of manually trying to duplicate how Windows draws disabled te(t this time' we can let +ontrol8aint do it for us' as shown in #igure 8.9. Figure 2.4. 1n *wner)Drawn ,tatus "ar Panel 7sing ControlPaint

0s nifty as +ontrol8aint is' as of .!"T 1.1 it doesn%t ta$e theming into account. *f you are using a themed operating system 3such as Windows T8 or Windows >003 5erver4' the artifacts drawn by +ontrol8aint will not be themed. But even though +ontrol8aint doesn%t support themed drawing' Win#orms has some support for it in the standard controls' as discussed in +hapter >: #orms.

Custom Controls
9wner/draw controls allow a great deal of control over how a control draws itself' but to ta$e full command of how a control acts you must build a c&stom control. There are three main $inds of custom controls:

+ontrols that derive directly from the +ontrol base class' allowing you to handle your control%s input and output completely +ontrols that derive from 5crolling+ontrol' which are li$e controls that derive from +ontrol but also provide built/in support for scrolling +ontrols that derive from an e(isting control to e(tend their behavior

The $ind of control you choose depends on the $ind of functionality you need. *f you need something that%s fundamentally new' you%ll derive from +ontrol or 5crolling+ontrol' depending on whether you need scrolling. Aeriving from one of the e(isting controls is useful if an e(isting control almost does what you want. The following sections discuss how to build all three $inds of custom controls.

Deri&ing Directl! from the Control Class


*n -5.!"T' if you right/clic$ on your pro<ect in 5olution "(plorer and choose 0dd N 0dd !ew *tem N +ustom +ontrol' you%ll get the following s$eleton:
/sin* /sin* /sin* /sin* /sin* /sin* System; System.Collections; System.Com#onentMo el; System.9ra!in*; System.9ata; System.Win o!s.Forms;

names#ace MyC/stomControls " ... =s/mmary> ... S/mmary escri#tion ,or C/stomControl5. ... =.s/mmary> #/&lic C/stomControl5() " protected o#erride #oid JnPaint+PaintE#ent'rgs pe. 4 // TJ0J3 'dd custom paint code here // Calling the !ase class JnPaint !ase.JnPaint+pe./ 5 5

pu!lic class CustomControlB 3 System.Windows.Forms.Control 4

This s$eleton derives from the +ontrol base class and provides a handler for the 8aint event. *t even provides a helpful comment letting you $now where to add your custom code to render your custom control%s state.

Testing Custom Controls


0fter you%ve wor$ed with your custom control for a while' you%ll want it to show up on the Toolbo( so that you can use it in various places. To do this' right/clic$ on the Toolbo( and choose 0ddC@emove *tems. To choose a .!"T assembly' clic$ on the .!"T #ramewor$ +omponent tab and press the Browse button. When you do that' you will get the +ustomi=e Toolbo( dialog showing the .!"T components that -5.!"T $nows about' as shown in #igure 8.10.
L3M L3M

*n -5.!"T >00>' &0ddC@emove *tems& is called &+ustomi=e Toolbo(.&

Figure 2.16. Customi-ing the Tool9o3

+hoose the assembly that your control lives in and press 9D. *f you are writing a Windows #orms application and writing your custom control in the same assembly' select the application%s ."T" file as the assembly. "ven controls from applications are available for reuse' although A77s are the preferred vehicle for distributing reusable controls. 0fter you%ve chosen the assembly to add' the custom controls will be added to the Toolbo(' as shown in #igure 8.11. Figure 2.11. Custom Controls 1dded to the Tool9o3 in =,.(%T

0lthough it%s possible to customi=e any of the tabs on the Toolbo(' it%s handy to have custom tabs for custom controls so that they don%t get lost among the standard controls and components. #igure 8.11 shows custom controls organi=ed on the y +ustom +ontrols tab. When your control is available on the Toolbo(' you can drop it onto a #orm and use the 8roperty Browser to set all public properties and handle all public events. Because your custom controls inherit from the +ontrol base' all this comes essentially for free. #or the details of how to customi=e your control%s interaction with the Aesigner and the 8roperty Browser' see +hapter 9: Aesign/Time *ntegration.

Control /endering
7oo$ing bac$ at the s$eleton code generated by the Aesigner for a custom control' remember that it handles the 8aint event by deriving from the +ontrol base class and overriding the 9n8aint method. Because we%re deriving from the +ontrol class' we have two options when deciding how to handle a method. The first option is to add a delegate and handle the event. This is the only option available when you%re handling a control%s event from a container. The second option is to override the virtual method that the base class provides that actually fires the methods. By convention' these methods are named 9nU"vent!ameV and ta$e an ob<ect of the "vent0rgs 3or "vent0rgs/derived4 class. When you override an event method' remember to call to the base class%s implementation of the method so that all the event subscribers will be notified. #or e(ample' here%s how to implement 9n8aint for a custom labelXli$e control:
#/&lic class (lli#seLa&el : Control " #/&lic (lli#seLa&el() " .. 2e8/ire ,or 9esi*ner s/##ort 6nitiali7eCom#onent(); -

protected o#erride #oid JnPaint+PaintE#ent'rgs pe. 4

.. C/stom #aint co e Era#hics * 1 #e.Era#hics; /sin*( +r/sh ,ore+r/sh 1 ne! Soli +r/sh(this.ForeColor ) ) /sin*( +r/sh &ac4+r/sh 1 ne! Soli +r/sh(this.)ac6Color) ) " *.Fill(lli#se(,ore+r/sh, this.Client"ectangle); Strin*Format ,mt 1 ne! Strin*Format(); ,mt.)li*nment 1 Strin*)li*nment.Center; ,mt.Line)li*nment 1 Strin*)li*nment.Center; *.9ra!Strin*( this.Te*t- this.Font- &ac4+r/sh, this.Client"ectangle- ,mt);

.. Callin* the &ase class DnGaint &ase.DnGaint(#e);

*n this code' notice how much functionality is available from the base class without the need to add any new properties' methods' or events. *n fact' the sheer amount of functionality in the base +ontrol class is too large to list here. any of the properties have corresponding U8roperty!ameV+hanged events to trac$ when they change. #or e(ample' the state of our custom labelXli$e control depends on the state of the Bac$+olor' #ore+olor' Te(t' #ont' and +lient@ectangle propertiesP so when any of these properties changes' we must apply the principles of drawing and invalidation from +hapter H: Arawing Basics to $eep the control visually up/to/date:
#/&lic (lli#seLa&el() " .. 2e8/ire ,or 9esi*ner s/##ort 6nitiali7eCom#onent();

// 'utomatically redraw when resi&ed // +See Chapter E3 'd#anced 0rawing for ControlStyles details. this.SetStyle+ControlStyles."esi&e"edraw- true./

$oi #oid EllipseCa!el8Te*tChanged+o!9ect sender- E#ent'rgs e. 4 this.;n#alidate+./ 5 this.Te*tChanged 71 new E#ent,andler+this.EllipseCa!el8Te*tChanged./

6nitiali7eCom#onent() "

*n this case' we trac$ when the Te(t property has changed by using the Aesigner to set up an event handler for the Te(t+hanged event 3saving us from typing in the event handler s$eleton or remembering to call the base class4. When the te(t changes' we invalidate our control%s client area. 6owever' we don%t need to trac$ any of the Bac$+olor+hanged' #ont+hanged' or #ore+olor+hanged events because the base class $nows to invalidate the client area of the control in those cases for us. Those properties are special' as e(plained ne(t.
LHM

LHM

Be careful when using the Aesigner with custom controls. *t adds an *nitiali=e+omponent method if there isn%t already one in the class' but it

doesn%t add a call from your control%s constructor to *nitiali=e+omponent' so you must do that manually.

1m9ient Properties
The reason that the base class $nows to treat some properties specially is that they are ambient properties. 0n ambient propert# is one that' if it%s not set in the control' will be &inherited& from the container. 9f all the standard properties provided by the +ontrol base class' only four are ambient: Bac$+olor' #ore+olor' #ont' and +ursor. #or e(ample' imagine an instance of the "llipse7abel control and a button hosted on a form container' as in #igure 8.1>. Figure 2.12. The %llipse$a9el Custom Control Hosted on a Form

0ll the settings for the #orm' the "llipse7abel control' and the Button control are the defaults with respect to the #ont propertyP this means that on my Windows T8 machine running at normal/si=ed fonts' the two controls show with 5 5ans 5erif 8.>F/point font. Because the "llipse7abel control ta$es its own #ont property into account when drawing' changing its #ont property to *mpact 10/point in the 8roperty Browser yields this code:
$oi 6nitiali7eCom#onent() " ... ...

this.ellipseCa!elB.Font 1 new Font+ ;mpact - BAF- ..../

The result loo$s li$e #igure 8.13. Figure 2.1 . ,etting the Font Propert! on the %llipse$a9el Control

This wor$s great if you%re creating a funhouse application in which different controls have different fonts' but more commonly' all the controls in a container will share the same font. 0lthough it%s certainly possible to use the Aesigner to set the fonts for each of the controls individually' it%s even easier to leave the font alone on the controls and set the font on the form:

$oi 6nitiali7eCom#onent() " ... ...

this.Font 1 new Font+ ;mpact - BAF- ..../

Because the #ont property is ambient' setting the font on the container also sets the fonts on the contained controls' as shown in #igure 8.1H. Figure 2.1#. ,etting the Font Propert! on the Hosting Form

When you set the #ont property on the container and leave the #ont property at the default value for the controls' the control &inherits& the #ont property from the container. 5imilarly' a contained control can &override& an ambient property if you set it to something other than the default:
LFM LFM

Jou can return a property to its &default& value in the 8roperty Browser by right/clic$ing on the property name and choosing @eset.

$oi 6nitiali7eCom#onent() " ...


this.ellipseCa!elB.Font 1 new Font+ Times New "oman - BAF- ..../

...
this.Font 1 new Font+ ;mpact - BAF- ..../

... -

!otice that the form%s font is set after the "llipse7abel control%s font. *t doesn%t matter in which order the ambient properties are set. *f a control has its own value for an ambient property' that value will be used instead of the container%s value. The result of the contained "llipse7abel control overriding the ambient #ont property is shown in #igure 8.1F. Figure 2.1'. 1 Contained Control *&erriding the =alue of the 1m9ient Font Propert!

0lso' if you need to reset the ambient properties to a default value' you can do this by using the +ontrol class%s @esetU8roperty!ameV methods:

elli#seLa&el5."esetFont+./

0mbient properties e(ist to allow containers to specify a loo$ and feel that all the contained controls share without any special effort. 6owever' in the event that a particular control needs to override the property inherited from its container' that can happen without incident.

Custom Functionalit!
*n addition to the standard properties that a control gets from the +ontrol base class' the state that a control must render will come from new public methods and properties that are e(posed as they would be e(posed from any .!"T class:
.. <se to #re#en to 0ext #ro#erty at o/t#/t strin* #re,ix 1 ""; #/&lic $oi 2esetGre,ix() " this.Gre,ix 1 ""; .. <ses Gre,ix setter #/&lic strin* Gre,ix " *et " ret/rn this.#re,ix; set " this.#re,ix 1 $al/e;
this.;n#alidate+./

o$erri e $oi DnGaint(Gaint($ent)r*s #e) "

#rotecte ... ...

g.0rawString+this.prefi* 7 this.Te*t- ..../

*n this case' we%ve got some e(tra control state modeled with a string field named &prefi(.& The prefi( is shown <ust before the Te(t property when the control paints itself. The prefi( field itself is private' but you can affect it by calling the public @eset8refi( method or getting or setting the public 8refi( property. !otice that whenever the prefi( field changes' the control invalidates itself so that it can maintain a visual state that%s consistent with its internal state. Because the 8refi( property is public' it shows up directly in the 8roperty Browser when an instance of the "llipse +ontrol is chosen on a design surface' as shown in #igure 8.1S. Figure 2.1.. 1 Custom Propert! in the Propert! "rowser

Custom %&ents The 8roperty Browser will show any public property without your doing anything special to ma$e it wor$. 5imilarly' any public events will show up there' too. #or e(ample' if you want to fire an event when the 8refi( property changes' you can e(pose a public property:
LSM LSM

#or an introduction to delegates and events' see 0ppendi( B: Aelegates and "vents.

.. Let clients 4no! o, chan*es in the Gre,ix #ro#erty #/&lic e$ent ($entHan ler Gre,ixChan*e ; #/&lic strin* Gre,ix " *et " ret/rn this.#re,ix; set " this.#re,ix 1 $al/e;
// Fire Prefi*Changed e#ent if+ this.Prefi*Changed 21 null . 4 Prefi*Changed+this- E#ent'rgs.Empty./ 5

this.6n$ali ate(); -

!otice that this code e(poses a custom event called 8refi(+hanged of type "vent6andler' which is the default delegate type for events that don%t need special data. When the prefi( field is changed' the code loo$s for event subscribers and lets them $now that the prefi( has changed' passing the sender 3the control itself4 and an empty "vent0rgs ob<ect' because we don%t have any additional data to send. When your control has a public event' it will show up as <ust another event in the 8roperty Browser' as shown in #igure 8.1I. Figure 2.10. 1 Custom %&ent ,hown in the Propert! "rowser

Eust li$e handling any other event' handling a custom event yields a code s$eleton for the developer to fill in with functionality.again' without your doing anything e(cept e(posing the event as public. *f' when defining your event' you find that you%d li$e to pass other information' you can create a custom delegate:
pu!lic class Prefi*E#ent'rgs 3 E#ent'rgs 4 pu!lic string Prefi*/ pu!lic Prefi*E#ent'rgs+string prefi*. 4 Prefi* 1 prefi*/ 5 5 pu!lic delegate #oid Prefi*edChangedE#ent,andler+o!9ect sender- Prefi*E#ent'rgs e./ pu!lic e#ent Prefi*edChangedE#ent,andler Prefi*Changed/

#/&lic strin* Gre,ix " *et " ret/rn this.#re,ix; set " this.#re,ix 1 $al/e; .. Fire Gre,ixChan*e e$ent i,( this.Gre,ixChan*e 31 n/ll ) "
Prefi*Changed+this- new Prefi*E#ent'rgs+#alue../

this.6n$ali ate();

!otice that the custom delegate we created uses the same pattern of no return value' an ob<ect sender argument' and an "vent0rgs/derived type as the last argument. This is the pattern that .!"T follows' and it%s a good one for you to emulate with your own custom events. *n our case' we%re deriving from "vent0rgs to pass along a 8refi("vent0rgs class' which derives from "vent0rgs and sends the new prefi( to the event handlers. But you can define new "vent0rgs/derived classes as appropriate for your own custom controls.

Control +nput
*n addition to providing output and e(posing custom methods' properties' and events' custom controls often handle input' whether it%s mouse input' $eyboard input' or both.

8ouse +nput #or e(ample' if we wanted to let users clic$ on "llipse+ontrol and' as they drag' ad<ust the color of the ellipse' we could do so by handling the ouseAown' ouse ove' and ouse1p events:
.. 0rac4 !hether mo/se &/tton is &ool mo/se9o!n 1 ,alse; o!n

$oi SetMo/seForeColor(Mo/se($ent)r*s e) " int re 1 (e.O Q @AA.(this.Client2ectan*le.Wi th : e.O))]@AX; i,( re = ? ) re 1 :re ; int *reen 1 ?; int &l/e 1 (e.M Q @AA.(this.Client2ectan*le.Hei*ht : e.M))]@AX; i,( &l/e = ? ) &l/e 1 :&l/e; this.ForeColor 1 Color.From)r*&(re , *reen, &l/e); $oi (lli#seLa&el%Mo/se9o!n(o&'ect sen er, Mo/se($ent)r*s e) " mo/se9o!n 1 tr/e; SetMo/seForeColor(e); $oi (lli#seLa&el%Mo/seMo$e(o&'ect sen er, Mo/se($ent)r*s e) " i,( mo/se9o!n ) SetMo/seForeColor(e); $oi (lli#seLa&el%Mo/se<#(o&'ect sen er, Mo/se($ent)r*s e) " SetMo/seForeColor(e); mo/se9o!n 1 ,alse; -

The ouseAown event is fired when the mouse is clic$ed inside the client area of the control. The control continues to get ouse ove events until the ouse1p event is fired' even if the mouse moves out of the region of the control%s client area. The code sample watches the mouse movements when the button is down and calculates a new #ore+olor using the T and J coordinates of the mouse as provided by the ouse"vent0rgs argument to the events:
class Mo/se($ent)r*s : ($ent)r*s " #/&lic Mo/se+/ttons +/tton " *et; - .. Which &/ttons are #resse #/&lic int Clic4s " *et; - .. Ho! many clic4s since the last e$ent #/&lic int 9elta " *et; - .. Ho! many mo/se !heel tic4s #/&lic int O " *et; - .. C/rrent O #osition relati$e to the screen #/&lic int M " *et; - .. C/rrent M #osition relati$e to the screen -

ouse"vent0rgs is meant to provide you with the information you need in order to handle mouse events. #or e(ample' to eliminate the need to trac$ the mouse button state manually' we could use the Button property to chec$ for a clic$ of the left mouse button:

$oi (lli#seLa&el%Mo/se9o!n(o&'ect sen er, Mo/se($ent)r*s e) " SetMo/seForeColor(e); $oi


5

(lli#seLa&el%Mo/seMo$e(o&'ect sen er, Mo/se($ent)r*s e) " SetMo/seForeColor(e);

if+ +e.)utton T $ouse)uttons.Ceft. 11 $ouse)uttons.Ceft . 4

$oi (lli#seLa&el%Mo/se<#(o&'ect sen er, Mo/se($ent)r*s e) " SetMo/seForeColor(e); -

0dditional mouse/related input events are ouse"nter' ouse6over' and ouse7eave' which can tell you that the mouse is over the control' that it%s hovered for &a while& 3useful for showing tooltips4' and that it has left the control%s client area. *f you%d li$e to $now the state of the mouse buttons or the mouse position outside a mouse event' you can access this information from the static ouseButtons and ouse8osition properties of the +ontrol class. *n addition to ouseAown' ouse ove' and ouse1p' there are five other mouse/related events. ouse"nter' ouse6over' and ouse7eave allow you to trac$ when a mouse enters' loiters in' and leaves the control%s client area. +lic$ and Aouble+lic$ provide an indication that the user has clic$ed or double/clic$ed the mouse in the control%s client area. <e!9oard +nput *n addition to providing mouse input' forms 3and controls4 can capture $eyboard input via the DeyAown' Dey1p' and Dey8ress events. #or e(ample' to ma$e the $eys i' <' $' and l move our elliptical label around on the container' the "llipse7abel control could handle the Dey8ress event:
$oi (lli#seLa&el%LeyGress(o&'ect sen er, LeyGress($ent)r*s e) " Goint location 1 ne! Goint(this.Le,t, this.0o#); s!itch( e.LeyChar ) " case FiF: ::location.M; &rea4; case F'F: ::location.O; &rea4; case F4F: ;;location.M; &rea4; case FlF:

;;location.O; &rea4; this.Location 1 location;

The Dey8ress event ta$es a Dey8ress"vent0rgs argument:


class LeyGress($ent)r*s : ($ent)r*s " #/&lic &ool Han le " *et; set; - .. Whether this 4ey is han le #/&lic char LeyChar " *et; - .. Character $al/e o, the 4ey #resse -

The Dey8ress"vent0rgs ob<ect has two properties. The 6andled property defaults to false but can be set to true to indicate that no other handlers should handle the event. The Dey+har property is the character value of the $ey after the modifier has been applied. #or e(ample' if the user presses the * $ey' the Dey+har will be i' but if the user presses 5hift and the * $ey' the Dey+har property will be *. 9n the other hand' if the user presses +trl;* or 0lt;*' we won%t get a Dey8ress event at all' because those are special se)uences that aren%t sent via the Dey8ress event. To handle these $inds of se)uences along with other special characters such as #/$eys or arrows' you need the DeyAown event:
$oi 0rans#arentForm%Ley9o!n(o&'ect sen er, Ley($ent)r*s e) " Goint location 1 ne! Goint(this.Le,t, this.0o#);
switch+ e.PeyCode . 4 case Peys.;3 case Peys.Ip3

::location.M; &rea4;
case Peys.U3 case Peys.Ceft3

::location.O; &rea4;
case Peys.P3 case Peys.0own3

;;location.M; &rea4;
case Peys.C3 case Peys."ight3

;;location.O; &rea4;

this.Location 1 location; -

!otice that the DeyAown event ta$es a Dey"vent0rgs argument 3as does the Dey1p event4' which is shown here:
class Ley($ent)r*s : ($ent)r*s " #/&lic &ool )lt " $irt/al *et; - .. Whether )lt is #resse #/&lic &ool Control " *et; - .. Whether Ctrl is #resse #/&lic &ool Han le " *et; set; - .. Whether this 4ey is han le #/&lic Leys LeyCo e " *et; - .. 0he 4ey &ein* #resse , !.o mo i,iers #/&lic Leys Ley9ata " *et; - .. 0he 4ey an the mo i,iers #/&lic int LeyCal/e " *et; - .. Ley9ata as an inte*er #/&lic Leys Mo i,iers " *et; - .. Dnly the mo i,iers #/&lic &ool Shi,t " $irt/al *et; - .. Whether Shi,t is #resse -

0lthough it loo$s as if the Dey"vent0rgs ob<ect contains a lot of data' it really contains only one thing: a private field e(posed via the DeyAata property. DeyAata is a bit field of the combination of the $eys being pressed 3from the Deys enumeration4 and the modifiers being pressed 3also from the Deys enumeration4. #or e(ample' if the * $ey is pressed by itself' DeyAata will be Deys.*' whereas if +trl;5hift;#> is pressed' DeyAata will be a bitwise combination of Deys.#>' Deys.5hift' and Deys.+ontrol. The rest of the properties in the Dey"vent0rgs ob<ect are handy views of the DeyAata property' as shown in Table 8.1. 0lso shown is the Dey+har that would be generated in a corresponding Dey8ress event. "ven though we%re handling the DeyAown event specifically to get special characters' some special characters' such as arrows' aren%t sent to the control by default. To enable them' the custom control overrides the *s*nputDey method from the base class:
#rotecte o$erri e &ool 6s6n#/tLey(Leys 4ey9ata) " .. Ma4e s/re !e *et arro! 4eys s!itch( 4ey9ata ) " case Leys.<#: case Leys.Le,t: case Leys.9o!n: case Leys.2i*ht: ret/rn tr/e; .. 0he rest can &e etermine &y the &ase class ret/rn &ase.6s6n#/tLey(4ey9ata); -

Ta9le 2.1. <e!%&ent1rgs and <e!Press%&ent1rgs %3amples


Peys Pressed Pey0ata PeyCode $odifiers 'lt Ctrl Shift PeyLalue PeyChar

* 5hift;*

Deys.* Deys.5hift ; Deys.*

Deys.* Deys.*

Deys.!one Deys.5hift

false false false I3 false false true I3

i *

Ta9le 2.1. <e!%&ent1rgs and <e!Press%&ent1rgs %3amples


Peys Pressed Pey0ata PeyCode $odifiers 'lt Ctrl Shift PeyLalue PeyChar

+trl;5hift;* Deys.+trl ; Deys.5hift ; Deys.* +trl

Deys.*

Deys.+trl ; Deys.5hift

false true true I3

nCa

Deys.+ontrolDey Deys.+ontrolDey Deys.+ontrol false true false 1I ; Deys.+trl

nCa

The return from *s*nputDey indicates whether or not the $ey data should be sent in events to the control. *n this e(ample' *s*nputDey returns true for all the arrow $eys and lets the base class decide what to do about the other $eys. *f you%d li$e to $now the state of a modifier $ey outside a $ey event' you can access the state in the static odifierDeys property of the +ontrol class. #or e(ample' the following chec$s to see whether the +trl $ey is the only modifier to be pressed during a mouse clic$ event:
$oi
5

(lli#seLa&el%Clic4(o&'ect sen er, ($ent)r*s e) " Messa*e+ox.Sho!("Ctrl;Clic4 etecte ");

if+ Control.$odifierPeys 11 Peys.Control . 4

Windows 8essage Handling


The paint event' the mouse and $eyboard events' and most of the other events handled by a custom control come from the underlying Windows operating system. 0t the Win3> level' the events start out life as Windows messages. 0 Windo s message is most often generated by Windows because of some $ind of hardware event' such as the user pressing a $ey' moving the mouse' or bringing a window from the bac$ground to the foreground. The window that needs to react to the message gets the message )ueued in its message 0&e&e. That%s where Win#orms steps in. The +ontrol base class is roughly e)uivalent to the concept of a window in the operating system. *t%s the <ob of Win#orms to ta$e each message off the Windows message )ueue and route it to the +ontrol responsible for handling the message. The base +ontrol class turns this message into an event' which +ontrol then fires by calling the appropriate method in the base class. #or e(ample' the W Q80*!T Windows message eventually turns into a call on the 9n8aint method' which in turn fires the 8aint event to all interested listeners. 6owever' not all Windows messages are turned into events by Win#orms. #or those cases' you can drop down to a lower level and handle the messages as they come into the +ontrol class. Jou do this by overriding the Wnd8roc method:

#rotecte o$erri e $oi Wn Groc(re, Messa*e m) " .. Grocess an .or /# ate messa*e ... .. Let &ase class han le it i, yo/ &ase.Wn Groc(re, m); onFt

0s a somewhat esoteric e(ample of handling Windows messages directly' the following is a rewrite of the code from +hapter >: #orms to move the nonrectangular form around the screen:
#rotecte o$erri e $oi Wn Groc(re, Messa*e m) " .. Let the &ase class ha$e ,irst crac4 &ase.Wn Groc(re, m); int WM%BCH600(S0 1 ?xPH; .. !in/ser.h i,( m.Ms* 31 WM%BCH600(S0 ) ret/rn; .. 6, the /ser clic4e on the client area, .. as4 the DS to treat it as a clic4 on the ca#tion int H0CL6(B0 1 5; int H0C)G06DB 1 @; i,( m.2es/lt.0o6ntN@() 11 H0CL6(B0 ) m.2es/lt 1 (6ntGtr)H0C)G06DB;

This code handles the W Q!+6*TT"5T message' which is one of the few that Win#orms doesn%t e(pose as an event. *n this case' the code calls to the Windows/provided handler for this message to see whether the user is moving the mouse over the client area of the form. *f that%s the case' the code pretends that the entire client area is the caption so that when the user clic$s and drags on it' Windows will ta$e care of moving the form for us. There aren%t a whole lot of reasons to override the Wnd8roc method and handle the Windows message directly' but it%s nice to $now that the option is there in case you need it.

,crolling Controls
0lthough directly inheriting from +ontrol gives you a bunch of functionality' you may find the need to create a control that scrolls. Jou could use a custom control to handle the logic involved in creating the scrollbar3s4 and handling repainting correctly as the user scrolls across the drawing surface. 7uc$ily' though' the .!"T #ramewor$ provides a class that handles most of these chores for you. To create a scrolling control' you derive from 5crollable+ontrol instead of +ontrol:
class Scrollin*(lli#seLa&el :
Scrolla!leControl

"...-

When you implement a scrolling control' the +lient@ectangle represents the si=e of the control%s visible surface' but there could be more of the control that isn%t currently visible because it%s been scrolled out of range. To get to the entire area of the control' use the Aisplay@ectangle property instead. Aisplay@ectangle is a property of the 5crollable+ontrol class that represents the virtual drawing area. #igure 8.18 shows the difference between the +lient@ectangle and the Aisplay@ectangle. Figure 2.12. Displa!/ectangle =ersus Client/ectangle

0n 9n8aint method for handling scrolling should loo$ something li$e this:
#rotecte o$erri e $oi DnGaint(Gaint($ent)r*s #e) " Era#hics * 1 #e.Era#hics; /sin*( +r/sh ,ore+r/sh 1 ne! Soli +r/sh(this.ForeColor) ) /sin*( +r/sh &ac4+r/sh 1 ne! Soli +r/sh(this.+ac4Color) ) " *.Fill(lli#se(,ore+r/sh, this.0isplay"ectangle); Strin*Format ,ormat 1 ne! Strin*Format(); ,ormat.)li*nment 1 Strin*)li*nment.Center; ,ormat.Line)li*nment 1 Strin*)li*nment.Center; *.9ra!Strin*( this.0ext, this.Font, &ac4+r/sh, this.0isplay"ectangle, ,ormat); &ase.DnGaint(#e);

The only difference between this 9n8aint method and the custom control is that we are painting to the Aisplay@ectangle instead of the +lient@ectangle. ,etting the ,croll Dimension 1nli$e the +lient@ectangle' which is determined by the container of the control' the Aisplay@ectangle is determined by the control itself. The scrollable control gets to decide the minimum when you set the 0uto5croll in5i=e property from the 5crollable+ontrol

base class. #or e(ample' the following code uses the control%s font settings to calculate the si=e needed for the scrollable label based on the si=e of the Te(t property:
$oi Scrollin*(lli#seLa&el%0extChan*e (o&'ect sen er, ($ent)r*s e) " this.6n$ali ate();
// Te*t changed ?? calculate new 0isplay"ectangle SetScroll$inSi&e+./

$oi #oid SetScroll$inSi&e+. 4 // Font changed ?? calculate new 0isplay"ectangle SetScroll$inSi&e+./

Scrollin*(lli#seLa&el%FontChan*e (o&'ect sen er, ($ent)r*s e) "

.. Create a Era#hics D&'ect to meas/re !ith /sin*( Era#hics * 1 this.CreateEra#hics() ) " .. 9etermine the si7e o, the text Si7eF si7eF 1 *.Meas/reStrin*(this.0ext, this.Font); Si7e si7e 1 ne! Si7e( (int)Math.Ceilin*(si7eF.Wi th), (int)Math.Ceilin*(si7eF.Hei*ht));
// Set the minimum si&e to the te*t si&e this.'utoScroll$inSi&e 1 si&e/

The 5et5croll in5i=e helper measures the si=e that the te(t will be in the particular font and then creates a 5i=e structure. The 0uto5croll in5i=e property of the 5i=e structure is used to tell the control when to show the scrollbars. *f the Aisplay@ectangle is larger in either dimension than the +lient@ectangle' scrollbars will appear. The 5crollable+ontrol base class has a few other interesting properties. The 0uto5croll property 3set to true by the Aesigner by default4 enables the Aisplay@ectangle to be a different si=e than the +lient@ectangle. 9therwise' the Aisplay@ectangle is always the same si=e as the +lient@ectangle. The 0uto5croll8osition property lets you programmatically change the position within the scrollable area of the control. The 0uto5croll argin property is used to set a margin around scrollable controls that are also container controls. The Aoc$8adding property is similar but is used for child controls that doc$. +ontainer controls could be controls such as GroupBo( or 8anel' or they could be custom controls' such as user controls' covered later in this chapter. *f you%d li$e to $now when your scrollable control scrolls' you can handle the 65croll and -5croll events. "(cept for the scrolling capability' scrollable controls are <ust li$e controls that derive from the +ontrol base class.

%3tending %3isting Controls


*f you%d li$e a control that%s similar to an e(isting control but not e(actly the same' you don%t want to start by deriving from +ontrol or 5crollable+ontrol and building everything from scratch. *nstead' you should derive from the e(isting control' whether it%s one of the standard controls provided by Win#orms or one of your own controls. #or e(ample' let%s assume that you want to create a #ileTe(tBo( control that%s <ust li$e the Te(tBo( control e(cept that it indicates to the user whether or not the currently entered file e(ists. #igures 8.19 and 8.>0 show the #ileTe(tBo( control in use. Figure 2.14. FileTe3t"o3 with a File That Does Not %3ist

Figure 2.26. FileTe3t"o3 with a File (ame That Does Exist

By putting this functionality into a reusable control' you can drop it on any form without ma$ing the form itself provide this functionality. By deriving the #ileTe(tBo( from the Te(tBo( base control class' you can get most of the behavior you need without any effort' letting you focus on the interesting new functionality:
class File0ext+ox :
Te*t)o* " protected o#erride #oid JnTe*tChanged+E#ent'rgs e. 4

.. 6, the ,ile oes not exist, color the text re i,( 3File.(xists(this.0ext) ) " this.ForeColor 1 Color.2e ; else " .. Ma4e it &lac4 this.ForeColor 1 Color.+lac4; .. Call the &ase class &ase.Dn0extChan*e (e);
5

!otice that implementing #ileTe(tBo( is merely a matter of deriving from the Te(tBo( base class 3which provides all the editing capabilities that the user will e(pect4 and overriding the 9nTe(t+hanged method. 3* also could have handled the Te(t+hanged event.4 When the te(t changes' we use the "(ists method of the 5ystem.*9.#ile class to chec$ whether the currently entered file e(ists in the file system and to set the foreground color of the control accordingly. 9ften' you can easily create new controls that have application/specific functionality using as little code as this because the bul$ of the code is provided by the base control class.

&ser Controls
Aeriving from an e(isting control is one way to reuse it' but the most popular form of reuse for a control is simple containment' as you%re accustomed to doing when building custom forms using e(isting controls. 0 &ser control is a way to contain a set of other controls for reuse as a set' producing a $ind of &subform.& #or e(ample' imagine that we wanted a control that composed our #ileTe(tBo( control with a &W& button for browsing. *n use' it would loo$ li$e the one shown in #igure 8.>1. Figure 2.21. 1 ,ample 7ser Control in 1ction

*t%s hard to tell from the picture' but as far as the form in #igure 8.>1 is concerned' it%s containing only a single control 3named #ileBrowseTe(tBo(4. The control is a user control because it derives from the 1ser+ontrol base class and contains two other controls: a #ileTe(tBo( control and a Button control. To create a custom user control' you right/clic$ on your pro<ect in 5olution "(plorer' choose 0dd N 0dd 1ser +ontrol' and press 9D. When you do' you%ll get the design surface for your user control to arrange with controls' as shown in #igure 8.>>.
LIM LIM

*f you%d li$e to start a new pro<ect to hold user controls' you can do so with the Windows +ontrols 7ibrary pro<ect template in the !ew 8ro<ect

dialog.

Figure 2.22. 1 (ew 7ser Control

Building a user control that brings the #ileTe(tBo( together with a browse button is a matter of dropping each onto the form and arranging to taste. 0lso' to enable browsing' you%ll probably want to use an instance of the 9pen#ileAialog component' capturing all that functionality into a single user control for reuse' as shown in #igure 8.>3. Figure 2.2 . The File"rowseTe3t"o3 7ser Control in the Designer

0ll the control arranging that you%re already accustomed to.such as anchoring and doc$ing.wor$s the same way in a user control as in a custom form. Jou also use the same techni)ues for setting properties or handling events. 0fter arranging the e(isting controls and components on the user control design surface' you simply write a tiny chun$ of code to handle the clic$ on the browse button to ma$e it all wor$:
$oi &ro!se+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " i,( this.o#enFile9ialo*5.Sho!9ialo*() 11 9ialo*2es/lt.DL ) " ,ile0ext+ox5.0ext 1 this.o#enFile.FileBame; -

1ser controls allow you to build reusable controls using the same tools you use when building forms' but with the added advantage of being able to drop a user control onto anything that can contain controls' including container controls' forms' and even other user controls.

#rag and #ro"


!o matter what $ind of controls you%re using or building' often you%d li$e to enable the user to drag data from one to another. This communication protocol' $nown as drag and drop' has long been standardi=ed and is fully supported in Win#orms for both targets and sources.

The Drop Target


0dding drag and drop to your application involves two elements: the drop target and the drop source. #irst' you must have a control that supports having things dragged and

dropped onto it. This $ind of control is $nown as the drop target. Jou designate your target by setting the 0llowArop property of the control to true. !e(t' you ma$e the target control subscribe to one or more of the drag/and/drop events:

is fired when the mouse enters the area of a control containing drag/and/ drop data. *t is used by the target to indicate whether it can accept the data. 0ragJ#er is called as the user hovers the mouse over the target. 0ragCea#e is called when the mouse leaves the area of a control containing the drag/ and/drop data. 0rag0rop is called when the user drops the data onto the target.
0ragEnter

0ll target controls must implement the Arag"nter event' or else they won%t be able to accept any dropped data. The Arag"nter event comes along with an instance of the Arag"vent0rgs class' which gives the target information about the data:
class 9ra*($ent)r*s : ($ent)r*s " .. Gro#erties

pu!lic 0rag0ropEffects 'llowedEffect 4 get/ 5 pu!lic ;0ataJ!9ect 0ata 4 get/ 5 pu!lic 0rag0ropEffects Effect 4 get/ set/ 5

#/&lic int LeyState " *et; #/&lic int O " *et; #/&lic int M " *et; -

0 target control%s Arag"nter event handler chec$s the Aata property to see whether it can be accepted when dropped. The ob<ect returned from the Aata property implements *Aata9b<ect to ma$e that determination possible:
inter,ace 69ataD&'ect " .. Metho s #/&lic $irt/al o&'ect Eet9ata(strin* ,ormat, &ool a/toCon$ert); #/&lic $irt/al o&'ect Eet9ata(strin* ,ormat); #/&lic $irt/al o&'ect Eet9ata(0y#e ,ormat); #/&lic $irt/al $oi Set9ata(strin* ,ormat, &ool a/toCon$ert, o&'ect ata); #/&lic $irt/al $oi Set9ata(strin* ,ormat, o&'ect ata); #/&lic $irt/al $oi Set9ata(0y#e ,ormat, o&'ect ata); #/&lic $irt/al $oi Set9ata(o&'ect ata); #/&lic &ool #/&lic #/&lic $irt/al Eet9ataGresent(strin* ,ormat, &ool a/toCon$ert); $irt/al &ool Eet9ataGresent(strin* ,ormat); $irt/al &ool Eet9ataGresent(0y#e ,ormat);

#/&lic $irt/al strin*IJ EetFormats(&ool a/toCon$ert); #/&lic $irt/al strin*IJ EetFormats();

The *Aata9b<ect interface is actually defined from .!"T%s +omponent 9b<ect odel 3+9 4 cousin' where drag and drop was born. Win#orms continues to wor$ with the +9 /based protocol so that managed and unmanaged applications can participate in drag/ and/drop operations between each other. #urthermore' the +9 /based protocol itself is based on the Windows convention for the way the +lipboard wor$s. 0ll data passed around using drag and drop is represented in +lipboard formats. 5ome +lipboard formats are customi=ed for your own application' and others are well $nown to allow +lipboard and drag/and/drop operations between applications. The format strings used to specify the well/$nown formats are predefined as static fields of the Aata#ormats class:
class 9ataFormats " .. Fiel s #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea #/&lic static rea

only only only only only only only only only only only only only only only only only only only only only

strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin* strin*

+itma#; CommaSe#arate Cal/e; 9i&; 9i,; (nhance Meta,ile; File9ro#; Html; Locale; Meta,ileGict; Dem0ext; Galette; Gen9ata; 2i,,; 2t,; Seriali7a&le; Strin*Format; Sym&olicLin4; 0ext; 0i,,; <nico e0ext; Wa$e)/ io;

.. Metho s #/&lic static 9ataFormats.Format EetFormat(strin* ,ormat); #/&lic static 9ataFormats.Format EetFormat(int i );

*n addition to support for well/$nown data formats' .!"T provides a conversion from some .!"T types' such as 5tring' to a corresponding format string' such as Aata#ormats.Te(t. 1sing a format string and the GetAata8resent method of the *Aata9b<ect' the target can determine whether the type of data being dragged is acceptable for a drop:
$oi text+oxN%9ra*(nter(o&'ect sen er, 9ra*($ent)r*s e) "

// Could chec6 against 0ataFormats.Te*t as well i,( e.0ata.Ket0ataPresent+typeof+string.. ) "

e.Effect 1 0rag0ropEffects.Copy/

else " -

e.Effect 1 0rag0ropEffects.None/

GetAata8resent chec$s the format of the data to see whether it matches the +lipboard format 3or a .!"T type converted to a +lipboard format4. To find out whether the data is in a convertible format' you can call the Get#ormats34 function' which returns an array of formats. +alling any of the *Aata9b<ect methods with the auto+onvert parameter set to false will disable anything e(cept a direct match of data types. *f the data is acceptable' the Arag"nter event handler must set the "ffect property of the Arag"ffect0rgs ob<ect to one or more flags indicating what the control is willing to do with the data if it%s dropped' as determined by the flags in the AragArop"ffects enumeration:
en/m 9ra*9ro#(,,ects " Co#y, .. 0a4e a co#y o, the ata Mo$e, .. 0a4e o!nershi# o, the ata Lin4, .. Lin4 to the ata Scroll, .. Scrollin* is ha##enin* in the tar*et )ll, .. )ll o, the a&o$e Bone, .. 2e'ect the ata -

*f a drop is allowed and it happens while the mouse is over the target' the target control will receive the AragArop event:
$oi text+oxN%9ra*9ro#(o&'ect sen er, 9ra*($ent)r*s e) " text+oxN.0ext 1 (strin*)e.0ata.Ket0ata+typeof+string..; -

When you implement the AragArop handler' the "ffect property of the Arag"vent0rgs will be the effect that the source and target agreed on' should multiple effects be allowed. @etrieving the data is a matter of calling GetAata.using either a Aata#ormat format string or a .!"T Type ob<ect.and casting the result. Drop Targets and C*8 When you enable a control as a target' you open yourself up to the possibility that the user will receive the cryptic message shown in #igure 8.>H. Figure 2.2#. Cr!ptic Drag)and)Drop %rror 8essage

Because drag and drop is a feature provided using +9 ' +9 must be initiali=ed on the 1* thread for drag and drop to wor$. 0lthough .!"T is smart enough to la=ily initiali=e +9 on the running thread as needed' for reasons of efficiency it pic$s the 1*/hostile ulti/Threaded 0partment 3 T04 for the thread to <oin unless told to do otherwise. 1nfortunately' for drag and drop' the 1* thread must <oin the 5ingle/Threaded 0partment 35T04. To ensure that that%s the case' always double/chec$ that the ain function on all your Win#orms applications is mar$ed with 5T0Thread0ttribute:
%ST'Thread(

static $oi Main() " )##lication.2/n(ne! Form5()); -

3!ote that 5T0Thread is a +, shortcut for 5T0Thread0ttribute.4 By default' any -5.!"T/generated code will contain this attribute on the ain function 3even +onsole applications4' but <ust in case it somehow goes missing' this is the first thing to chec$ when you see the message bo( from #igure 8.>H.

The Drop ,ource


With the target implemented' what%s left is initiating a drag/and/drop operation from the drop so&rce using the AoAragArop method of the +ontrol class. AoAragArop is almost always placed in the handler for a ouseAown event:
$oi &/ttonN%Mo/se9o!n(o&'ect sen er, Mo/se($ent)r*s e) "

// Start a drag?and?drop operation 0o0rag0rop+!utton@.Te*t- 0rag0ropEffects.Copy./

The AoAragArop method%s first parameter is the data' which can be any ob<ect. The second parameter is a combination of the drag/and/drop effects that the source supports. #or e(ample' #igure 8.>F shows the button initiating the drag and drop. Figure 2.2'. 1 Drag)and)Drop *peration ,howing the (one %ffect

0s the drag/and/drop operation progresses' the AoAragArop method trac$s the mouse as it moves over controls' loo$ing to see whether they are potential drop targets 3as set with the 0llowArop property4 and firing the Arag"nter event to see whether potential targets can accept the data. Aepending on whether the target can accept the data' AoAragArop sets the cursor based on the current effect indicated by the target to communicate to users what would happen if they were to drop at any point. !otice in #igure 8.>F that the button itself is not a drop target' so the cursor indicates that a drop on the button would have no effect. 9n the other hand' when the data is dragged over a te(t bo( that is enabled to accept string data' the Arag"nter event is fired' and the control indicates the effect that it will support. This causes the cursor to be updated appropriately' as shown in #igure 8.>S. Figure 2.2.. Drop Target +ndicating the Cop! %ffect

When the user releases the mouse button' dropping the data' the AragArop event is fired on the target' and the target accepts the data' as shown in #igure 8.>I. Figure 2.20. Completed Drag)and)Drop Cop! *peration

When the drag and drop is completed' the AoAragArop method returns with the effect that was performed. *f the effect was a ove' the source $nows to remove its copy of the data. ,upporting 8ultiple %ffects *f you want to support more than one effect' such as +opy and ove' you can chec$ the Dey5tate property of the Arag"vent0rgs structure. Dey5tate is a set of flags that determines which $eys are being pressed. By Windows convention' the lac$ of modifier

$eys indicates a ove' the +trl modifier indicates a +opy' and the +trl;5hift modifier indicates a 7in$ 3which your application may or may not support4. 1nfortunately' the Dey5tate property is an integer' and Win#orms provides no data type for chec$ing the flags. 5o you%ll need your own' such as this Dey5tate enumeration:
L8M L8M

The #lags0ttribute ma$es instances of the Dey5tate enumeration show up in a friendlier manner when debugging' such as &7eft ouse'

+trlDey& instead of &9.&

.. LeyState Cal/es (not a$aila&le in WinForms) IFla*s)ttri&/teJ en/m LeyState " Le,tMo/se 1 5, 2i*htMo/se 1 @, Shi,tLey 1 H, CtrlLey 1 P, Mi leMo/se 1 5X, )ltLey 1 N@, -

Because users may change the $eys they%re pressing at any time to get the effect they%re loo$ing for' you will want to notify the drag operation of which operation they are trying to do while the mouse is hovering. To do this you chec$ the Arag"nter and Arag9ver events:
$oi $oi $oi Set9ro#(,,ect(9ra*($ent)r*s e) "
PeyState 6eyState 1 +PeyState.e.PeyState/

text+oxN%9ra*(nter(o&'ect sen er, 9ra*($ent)r*s e) "

Set0ropEffect+e./

text+oxN%9ra*D$er(o&'ect sen er, 9ra*($ent)r*s e) "

Set0ropEffect+e./

.. 6, the ata is a strin*, !e can han le it i,( e.9ata.Eet9ataGresent(ty#eo,(strin*)) ) "


// ;f only Ctrl is pressed- copy it if+ +6eyState T PeyState.CtrlPey. 11 PeyState.CtrlPey . 4 e.Effect 1 0rag0ropEffects.Copy/ 5 else 4 // Else- mo#e it e.Effect 1 0rag0ropEffects.$o#e/ 5

.. We onFt li4e the ata, so o not allo! anythin* else " e.(,,ect 1 9ra*9ro#(,,ects.Bone; -

The 5etArop"ffect method ma$es sure that the data is a string because that is all we are e(pecting. *f it finds a string' it tests to see whether the +trl $ey is pressed. *f so' it specifies

that the operation is a copyP otherwise' it specifies that it will do a move. #igure 8.>8 shows what the drag operation now loo$s li$e over the te(t bo( without the +trl $ey pressed' indicating a move effect. Figure 2.22. Dragging without Ctrl, Causing a 8o&e

#igure 8.>9 shows the same operation with the +trl $ey pressed' indicating a copy effect. Figure 2.24. Dragging with Ctrl, Causing a Cop!

*n our sample' when the user drops the data with no modifiers' indicating a move' the te(t is removed from the button when it drops the te(t to the te(t bo(' as shown in #igure 8.30. Figure 2. 6. 1fter a Drag)and)Drop 8o&e *peration

To handle multiple effects in the drop source' you must specify which effects are allowed and chec$ the resulting effect after the AoAragArop method returns:
$oi &/ttonN%Mo/se9o!n(o&'ect sen er, Mo/se($ent)r*s e) "

0rag0ropEffects effect 1

9o9ra*9ro#( &/ttonN.0ext,
0rag0ropEffects.Copy

0rag0ropEffects.$o#e );

// ;f the effect was mo#e- remo#e the te*t of the !utton // ;f the effect was a copy- we don:t ha#e anything to do if+ effect 11 0rag0ropEffects.$o#e . 4

&/ttonN.0ext 1 "";
5

Arag and drop is a great way to allow your mouse/oriented users to directly manipulate the data that your application presents without an undue development burden on you.

Where Are We?


+ontrols are a way to repac$age hun$s of user interaction and behavior for the user. +ontrols can also provide a great many niceties to ma$e them more approachable for the developer. That%s what +hapter 9 is all about' for non/G1* components as well as G1* controls.

Chapter 4. Design)Time +ntegration


0 component is a nonvisual class designed specifically to integrate with a design/time environment such as -isual 5tudio .!"T. Win#orms provides several standard components' and .!"T lets you build your own' gaining a great deal of design/time integration with very little wor$. 9n the other hand' with a bit more effort' you can integrate nonvisual components and controls very tightly into the design/time environment' providing a rich development e(perience for the programmer using your custom components and controls.

Com"onents
@ecall from +hapter 8: +ontrols that controls gain integration into -5.!"T merely by deriving from the +ontrol base class in the 5ystem.Windows.#orms namespace. That%s not the whole story. What ma$es a control special is that it%s one $ind of component: a .!"T class that integrates with a design/time environment such as -5.!"T. 0 component can show up on the Toolbo( along with controls and can be dropped onto any design surface. Aropping a component onto a design surface ma$es it available to set the property or handle the events in the Aesigner' <ust as a control is. #igure 9.1 shows the difference between a hosted control and a hosted component. Figure 4.1. $ocations of Components and Controls Hosted on a Form

,tandard Components
*t%s so useful to be able to create instances of nonvisual components and use the Aesigner to code against them that Win#orms comes with several components out of the bo(:

Standard dialogs.

The +olorAialog' #olderBrowserAialog' #ontAialog' 9pen#ileAialog' 8age5etupAialog' 8rintAialog' 8rint8reviewAialog' and

5ave#ileAialog classes ma$e up the bul$ of the standard components that Win#orms provides. The printing/related components are covered in detail in +hapter I: 8rinting. $enus . The ain enu and +onte(t enu components provide a form%s menu bar and a control%s conte(t menu. They%re both covered in detail in +hapter >: #orms. Iser information . The "rror8rovider' 6elp8rovider' and ToolTip components provide the user with varying degrees of help in using a form and are covered in +hapter >: #orms. Notify icon. The !otify*con component puts an icon on the shell%s Tas$Bar' giving the user a way to interact with an application without the screen real estate re)uirements of a window. #or an e(ample' see 0ppendi( A: 5tandard Win#orms +omponents and +ontrols. ;mage Cist . The *mage7ist component $eeps trac$ of a developer/provided list of images for use with controls that need images when drawing. +hapter 8: +ontrols shows how to use them. Timer. The Timer component fires an event at a set interval measured in milliseconds.

7sing ,tandard Components What ma$es components useful is that they can be manipulated in the design/time environment. #or e(ample' imagine that you%d li$e a user to be able to set an alarm in an application and to notify the user when the alarm goes off. Jou can implement that using a Timer component. Aropping a Timer component onto a #orm allows you to set the "nabled and *nterval properties as well as handle the Tic$ event in the Aesigner' which generates code such as the following into *nitiali=e+omponent:
$oi
this.components 1 new Container+./ this.timerB 1 new Timer+this.components./

6nitiali7eCom#onent() "

...
// timerB this.timerB.Ena!led 1 true/ this.timerB.Tic6 71 new E#ent,andler+this.timerB8Tic6./

... -

0s you have probably come to e(pect by now' the Aesigner/generated code loo$s very much li$e what you%d write yourself. What%s interesting about this sample *nitiali=e+omponent implementation is that when a new component is created' it%s put on a list with the other components on the form. This is similar to the +ontrols collection that is used by a form to $eep trac$ of the controls on the form. 0fter the Aesigner has generated most of the Timer/related code for us' we can implement the rest of the alarm functionality for our form:
9ate0ime alarm 1 9ate0ime.MaxCal/e; .. Bo alarm $oi set)larm+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

alarm 1

ate0imeGic4er5.Cal/e;

// ,andle the Timer:s Tic6 e#ent #oid timerB8Tic6+o!9ect sender- System.E#ent'rgs e. 4

stat/s+ar5.0ext 1 9ate0ime.Bo!.0imeD,9ay.0oStrin*(); .. Chec4 to see !hether !eFre !ithin 5 secon o, the alarm o/&le secon s 1 (9ate0ime.Bo! : alarm).0otalSecon s; i,( (secon s >1 ?) ZZ (secon s =1 5) ) " alarm 1 9ate0ime.MaxCal/e; .. Sho! alarm only once Messa*e+ox.Sho!("Wa4e <#3"); 5

*n this sample' when the timer goes off every 100 milliseconds 3the default value4' we chec$ to see whether we%re within 1 second of the alarm. *f we are' we shut off the alarm and notify the user' as shown in #igure 9.>. Figure 4.2. The Timer Component Firing %&er! 166 8illiseconds

*f this $ind of single/fire alarm is useful in more than one spot in your application' you might choose to encapsulate this functionality in a custom component for reuse.

Custom Components
0 component is any class that implements the *+omponent interface from the 5ystem.+omponent odel namespace:
inter,ace 6Com#onent : 69is#osa&le " 6Site Site " *et; set; e$ent ($entHan ler 9is#ose ; inter,ace 69is#osa&le " $oi 9is#ose(); -

0 class that implements the *+omponent interface can be added to the Toolbo( in -5.!"T and dropped onto a design surface. When you drop a component onto a form' it shows itself in a tray below the form. 1nli$e controls' components don%t draw themselves in a region on their container. *n fact' you could thin$ of components as nonvisual controls' because' <ust li$e controls' components can be managed in the design/time environment. 6owever' it%s more accurate to thin$ of controls as visual components because controls implement *+omponent' which is where they get their design/time integration.
L1M L1M

The same procedure for adding a custom control to the Toolbo( in +hapter 8: +ontrols can be used to add a custom component to the Toolbo(.

1 ,ample Component 0s an e(ample' to pac$age the alarm functionality we built earlier around the Timer component' let%s build an 0larm+omponent class. To create a new component class' right/ clic$ on the pro<ect and choose 0dd N 0dd +omponent' enter the name of your component class' and press 9D. Jou%ll be greeted with a blan$ design surface' as shown in #igure 9.3. Figure 4. . 1 (ew Component Design ,urface

The design surface for a component is meant to host other components for use in implementing your new component. #or e(ample' we can drop our Timer component from the Toolbo( onto the alarm component design surface. *n this way' we can create and configure a timer component <ust as if we were hosting the timer on a form. #igure 9.H shows the alarm component with a timer component configured for our needs.

Figure 4.#. 1 Component Design ,urface Hosting a Timer Component

5witching to the code view for the component displays the following s$eleton generated by the component pro<ect item template and filled in by the Aesigner for the timer:
L>M L>M

Jou can switch to code view from designer view by choosing -iew N +ode or pressing #I. 5imilarly' you can switch bac$ by choosing -iew N

Aesigner or by pressing 5hift;#I.

/sin* /sin* /sin* /sin*

System; System.Com#onentMo el; System.Collections; System.9ia*nostics;

names#ace Com#onents " ... =s/mmary> ... S/mmary escri#tion ,or )larmCom#onent. ... =.s/mmary> #/&lic class )larmCom#onent : System.Com#onentMo el.Com#onent " #ri$ate 0imer timer5; #ri$ate System.Com#onentMo el.6Container com#onents; #/&lic )larmCom#onent(System.Com#onentMo el.6Container container) " ... =s/mmary> ... 2e8/ire ,or Win o!s.Forms Class Com#osition 9esi*ner s/##ort ... =.s/mmary> container.) (this); 6nitiali7eCom#onent();

.. .. 0D9D: ) .. -

any constr/ctor co e a,ter 6nitiali7eCom#onent call

#/&lic )larmCom#onent() " ... =s/mmary> ... 2e8/ire ,or Win o!s.Forms Class Com#osition 9esi*ner s/##ort ... =.s/mmary> 6nitiali7eCom#onent(); .. .. 0D9D: ) .. any constr/ctor co e a,ter 6nitiali7eCom#onent call

Ure*ion Com#onent 9esi*ner *enerate co e ... =s/mmary> ... 2e8/ire metho ,or 9esi*ner s/##ort : o not mo i,y ... the contents o, this metho !ith the co e e itor. ... =.s/mmary> #ri$ate $oi 6nitiali7eCom#onent() " this.com#onents 1 ne! System.Com#onentMo el.Container();
this.timerB 1 new System.Windows.Forms.Timer+this.components./ // // timerB // this.timerB.Ena!led 1 true/

Uen re*ion -

!otice that the custom component derives from the +omponent base class from the 5ystem.+omponent odel namespace. This class provides an implementation of *+omponent for us. 0fter the timer is in place in the alarm component' it%s a simple matter to move the alarm functionality from the form to the component by handling the timer%s Tic$ event:
#/&lic class )larmCom#onent : Com#onent " ... 9ate0ime alarm 1 9ate0ime.MaxCal/e; .. Bo alarm
pu!lic 0ateTime 'larm 4

*et " ret/rn this.alarm; set " this.alarm 1 $al/e; -

.. Han le the 0imerFs 0ic4 e$ent


pu!lic e#ent E#ent,andler 'larmSounded/ #oid timerB8Tic6+o!9ect sender- System.E#ent'rgs e. 4

.. Chec4 to see !hether !eFre !ithin 5 secon o, the alarm o/&le secon s 1 (9ate0ime.Bo! : this.alarm).0otalSecon s; i,( (secon s >1 ?) ZZ (secon s =1 5) ) "

this.alarm 1 9ate0ime.MaxCal/e; .. Sho! alarm only once i,( this.)larmSo/n e 31 n/ll ) " )larmSo/n e (this, ($ent)r*s.(m#ty); 5

This implementation is <ust li$e what the form was doing before' e(cept that the alarm date and time are set via the public 0larm propertyP when the alarm sounds' an event is fired. !ow we can simplify the form code to contain merely an instance of the 0larm+omponent' setting the 0larm property and handling the 0larm5ounded event:
#/&lic class )larmForm : Form "
'larmComponent alarmComponentB/

... $oi 6nitiali7eCom#onent() " ...


this.alarmComponentB 1 new 'larmComponent+this.components./

...
this.alarmComponentB.'larmSounded 71 new E#ent,andler+this.alarmComponentB8'larmSounded./

$oi -

...

alarmComponentB.'larm 1 dateTimePic6erB.Lalue/

set)larm+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

#oid alarmComponentB8'larmSounded+o!9ect sender- E#ent'rgs e. 4

Messa*e+ox.Sho!("Wa4e <#3");
5

*n this code' the form uses an instance of 0larm+omponent' setting the 0larm property based on user input and handling the 0larm5ounded event when it%s fired. The code does all this without any $nowledge of the actual implementation' which is encapsulated inside the 0larm+omponent itself. Component /esource 8anagement 0lthough components and controls are similar as far as their design/time interaction is concerned' they are not identical. The most obvious difference lies in the way they are drawn on the design surface. 0 less obvious difference is that the Aesigner does not generate the same hosting code for components that it does for controls. 5pecifically' a component gets e(tra code so that it can add itself to the container%s list of components. When the container shuts down' it uses this list of components to notify all the components that they can release any resources that they%re holding. +ontrols don%t need this e(tra code because they already get the +losed event' which is an e)uivalent notification for most purposes. To let the Aesigner $now that it would li$e to be

notified when its container goes away' a component can implement a public constructor that ta$es a single argument of type *+ontainer:
#/&lic )larmCom#onent(6Container container) " .. ) o&'ect to containerFs list so that .. !e *et noti,ie !hen the container *oes a!ay container.) (this); 6nitiali7eCom#onent(); -

!otice that the constructor uses the passed container interface to add itself as a container component. *n the presence of this constructor' the Aesigner generates code that uses this constructor' passing it a container for the component to add itself to. @ecall that the code to create the 0larm+omponent uses this special constructor:
#/&lic class )larmForm : Form "
;Container components/ 'larmComponent alarmComponentB/

... $oi

this.components 1 new Container+./

6nitiali7eCom#onent() "

... ...

this.alarmComponentB 1 new 'larmComponent+this.components./

By default' most of the -5.!"T/generated classes that contain components will notify each component in the container as part of the Aispose method implementation:
#/&lic class )larmForm : Form " ...
;Container components/

...

// J#erridden from the !ase class Component.0ispose method

#rotecte o$erri e $oi i,( is#osin* ) "

9is#ose( &ool

is#osin* ) "

if +components 21 null. 4 // Call each component:s 0ispose method components.0ispose+./ 5

&ase.9is#ose( -

is#osin* );

0s you may recall from +hapter H: Arawing Basics' the client is responsible for calling the Aispose method from the *Aisposable interface. The *+ontainer interface derives from *Aisposable' and the +ontainer implementation of Aispose wal$s the list of components' calling *Aisposable. Aispose on each one. 0 component that has added itself to the container can override the +omponent base class%s Aispose method to catch the notification that is being disposed of:

#/&lic class )larmCom#onent : Com#onent "


Timer timerB/ ;Container components/ ...

$oi

this.components 1 new Container+./ this.timerB 1 new Timer+this.components./

6nitiali7eCom#onent() "

... protected o#erride #oid 0ispose+!ool disposing. 4

i,( is#osin* ) " .. 2elease mana*e ...

reso/rces

// Cet contained components 6now to release their resources if+ components 21 null . 4 components.0ispose+./ 5

.. 2elease /nmana*e ... reso/rces

!otice that' unli$e the method that the client container is calling' the alarm component%s Aispose method ta$es an argument. The +omponent base class routes the implementation of *Aisposable.Aispose34 to call its own Aispose3bool4 method' with the Boolean argument disposing set to true. This is done to provide optimi=ed' centrali=ed resource management. 0 disposing argument of true means that Aispose was called by a client that remembered to properly dispose of the component. *n the case of our alarm component' the only resources we have to reclaim are those of the timer component we%re using to provide our implementation' so we as$ our own container to dispose of the components it%s holding on our behalf. Because the Aesigner/generated code added the timer to our container' that%s all we need to do. 0 disposing argument of false means that the client forgot to properly dispose of the ob<ect and that the .!"T Garbage +ollector 3G+4 is calling our ob<ect%s finali=er. 0 finali$er is a method that the G+ calls when it%s about to reclaim the memory associated with the ob<ect. Because the G+ calls the finali=er at some indeterminate time.potentially long after the component is no longer needed 3perhaps hours or days later4.the finali=er is a bad place to reclaim resources' but it%s better than not reclaiming them at all. The +omponent base class%s finali=er implementation calls the Aispose method' passing a disposing argument of false' which indicates that the component shouldn%t touch any of the managed ob<ects it may contain. The other managed ob<ects should remain untouched because the G+ may have already disposed of them' and their state is undefined.

0ny component that contains other ob<ects that implement *Aisposable' or handles to unmanaged resources' should implement the Aispose3bool4 method to properly release those ob<ects% resources when the component itself is being released by its container.

#esign.Time 'ntegration Basics


Because a component is a class that%s made to be integrated into a design/time host' it has a life separate from the run/time mode that we normally thin$ of for ob<ects. *t%s not enough for a component to do a good <ob when interacting with a user at run time as per developer instructionsP a component also needs to do a good <ob when interacting with the developer at design time.

Hosts, Containers, and ,ites


*n -isual 5tudio .!"T' the Windows #orms Aesigner is responsible for providing design/ time services during Windows #orms development. 0t a high level' these services include a form%s 1* and code views. The responsibility of managing integration between design/ time ob<ects and the designer is handled by the designer%s internal implementation of *Aesigner6ost 3from the 5ystem.+omponent odel.Aesign namespace4. The designer host stores *+omponent references to all design/time ob<ects on the current form and also stores the form itself 3which is also a component4. This collection of components is available from the *Aesigner6ost interface through the +ontainer property of type *+ontainer 3from the 5ystem.+omponent odel namespace4:
inter,ace 6Container : 69is#osa&le " Com#onentCollection Com#onents " *et; $oi ) (6Com#onent com#onent); $oi ) (6Com#onent com#onent, strin* name); $oi 2emo$e(6Com#onent com#onent); -

This implementation of *+ontainer allows the designer host to establish a relationship that helps it manage each of the components placed on the form. +ontained components can access the designer host and each other through their container at design time. #igure 9.F illustrates this two/way relationship. Figure 4.'. Design)Time 1rchitecture

*n #igure 9.F you can see that the fundamental relationship between the designer host and its components is established with an implementation of the *5ite interface 3from the 5ystem.+omponent odel namespace4:
inter,ace 6Site : 6Ser$iceGro$i er " 6Com#onent Com#onent " *et; 6Container Container " *et; &ool 9esi*nMo e " *et; strin* Bame " *et; set; -

*nternally' a container stores an array of sites. When each component is added to the container' the designer host creates a new site' connecting the component to its design/time container and vice versa by passing the *5ite interface in the *+omponent.5ite property implementation:
inter,ace 6Com#onent : 69is#osa&le " 6Site Site " *et; set; e$ent ($entHan ler 9is#ose ; -

The +omponent base class implements *+omponent and caches the site%s interface in a property. *t also provides a helper property to go directly to the component%s container without having to go first through the site:
class Com#onent : Marshal+y2e,D&'ect, 6Com#onent, 69is#osa&le " #/&lic 6Container Container " *et; #/&lic $irt/al 6Site Site " *et; set; #rotecte &ool 9esi*nMo e " *et; #rotecte ($entHan lerList ($ents " *et; -

The +omponent base class gives a component direct access to both the container and the site. 0 component can also access the -isual 5tudio .!"T designer host itself by re)uesting the *Aesigner6ost interface from the container:
69esi*nerHost esi*nerHost 1 this.Container as 69esi*nerHost;

*n -isual 5tudio .!"T' the designer has its own implementation of the *Aesigner6ost interface' but' to fit into other designer hosts' it%s best for a component to rely only on the interface and not on any specific implementation.

De9ugging Design)Time Functionalit!


To demonstrate the .!"T #ramewor$%s various design/time features and services' *%ve built a sample. Because components and controls share the same design/time features and because * li$e things that loo$ sna==y' * built a digitalCanalog cloc$ control with the following public members:
L3M L3M

While * use the term &*& to maintain consistency with the rest of the prose' it was actually

ichael Weinhardt who built this sample as well as ichael2

doing the initial research and even the initial draft of much of the material in this chapter. Than$s'

#/&lic class Cloc4Control : Control " #/&lic Cloc4Control(); #/&lic 9ate0ime )larm " *et; set; #/&lic &ool 6s6t0imeFor)+rea4 " *et; set; #/&lic e$ent )larmHan ler )larmSo/n e ; ... -

#igure 9.S shows the control in action. Figure 4... ,na--! Cloc5 Control

When you build design/time features into your components' you%ll need to test them and' more than li$ely' debug them. To test run/time functionality' you simply set a brea$point in
LHM

your component%s code and run a test application' relying on -isual 5tudio .!"T to brea$ at the right moment.
LHM

@emember that nonvisual components as well as controls are components for the purposes of design/time integration.

What ma$es testing design/time debugging different is that you need a design/time host to debug againstP an ordinary application won%t do. Because the hands/down hosting favorite is -isual 5tudio .!"T itself' this means that you%ll use one instance of -isual 5tudio .!"T to debug another instance of -isual 5tudio .!"T with a running instance of the component loaded. This may sound confusing' but it%s remar$ably easy to set up: 1. 9pen the component solution to debug in one instance of -isual 5tudio .!"T. >. 5et a second instance of -isual 5tudio .!"T as your debug application by going to 8ro<ect N 8roperties N +onfiguration 8roperties N Aebugging and setting the following properties: a. 5et Aebug ode to 8rogram. b. 5et 5tart 0pplication to Uyour devenv.e(e pathVOdevenv.e(e. c. 5et +ommand 7ine 0rguments to Uyour test solution pathVOyourTest5olution.sln. 3. +hoose 5et 0s 5tart1p 8ro<ect on your component pro<ect. H. 5et a brea$point in the component. F. 1se Aebug N 5tart 3#F4 to begin debugging. 0t this point' a second instance of -isual 5tudio.!"T starts up with another solution' allowing you to brea$ and debug at will' as illustrated in #igure 9.I. Figure 4.0. Design)Time Control De9ugging

The $ey to ma$ing this setup wor$ is to have one solution loaded in one instance of -5.!"T that starts another instance of -5.!"T with a completely different solution to test your component in design mode.

The Design8ode Propert!


To change the behavior of your component at design time' often you need to $now that you%re running in a Aesigner. #or e(ample' the cloc$ control uses a timer component to trac$ the time via its Tic$ event handler:
#/&lic class Cloc4Control : Control " ...
Timer timer 1 new Timer+./

... #/&lic Cloc4Control() " ... .. 6nitiali7e timer timer.6nter$al 1 5???;

timer.Tic6 71 new System.E#ent,andler+this.timer8Tic6./

timer.(na&le ...

1 tr/e;

#oid timer8Tic6+o!9ect sender- E#ent'rgs e. 4 // "efresh cloc6 face this.;n#alidate+./ ... 5

*nspection reveals that the control is overly =ealous in $eeping time both at design time and at run time. 5uch code should really be e(ecuted at run time only. *n this situation' a component or control can chec$ the Aesign ode property' which is true only when it is e(ecuting at design time. The timerQTic$ event handler can use Aesign ode to ensure that it is e(ecuted only at run time' returning immediately from the event handler otherwise:
$oi timer%0ic4(o&'ect sen er, ($ent)r*s e) " .. 9onFt exec/te e$ent i, r/nnin* in esi*n time
if+ this.0esign$ode . return/

this.6n$ali ate(); ... -

!ote that the Aesign ode property should not be chec$ed from within the constructor or from any code that the constructor calls. 0 constructor is called before a control is sited' and it%s the site that determines whether or not a control is in design mode. Aesign ode will also be false in the constructor.

1ttri9utes
Aesign/time functionality is available to controls in one of two ways: programmatically and declaratively. +hec$ing the Aesign ode property is an e(ample of the programmatic approach. 9ne side effect of using a programmatic approach is that your implementation ta$es on some of the design/time responsibility' resulting in a blend of design/time and run/time code within the component implementation. The declarative approach' on the other hand' relies on attributes to re)uest design/time functionality implemented somewhere else' such as the designer host. #or e(ample' consider the default Toolbo( icon for a component' as shown in #igure 9.8. Figure 4.2. Default Tool9o3 +con

*f the image is important to your control' you%ll want to change the icon to something more appropriate. The first step is to add a 1S(1S' 1S/color icon or bitmap to your pro<ect and set its Build 0ction to "mbedded @esource 3embedded resources are discussed in +hapter 10:

@esources4. Then add the Toolbo(Bitmap0ttribute to associate the icon with your component:
%Tool!o*)itmap'ttri!ute+ typeof+Cloc6ControlCi!rary.Cloc6Control.images.Cloc6Control.ico .(

#/&lic class Cloc4Control : Control "...-

The parameters to this attribute specify the use of an icon resource located in the &images& pro<ect subfolder. Jou%ll find that the Toolbo( image doesn%t change if you add or change Toolbo(Bitmap0ttribute after the control has been added to the Toolbo(. 6owever' if your implementation is a component' its icon is updated in the component tray. 9ne can only assume that the Toolbo( is not under the direct management of the Windows #orm Aesigner' whereas the component tray is. To refresh the Toolbo(' remove your component and then add it again to the Toolbo(. The result will be something li$e #igure 9.9. Figure 4.4. (ew and +mpro&ed Tool9o3 +con

Jou can achieve the same result without using Toolbo(Bitmap0ttribute: 5imply place a 1S(1S' 1S/color bitmap in the same pro<ect folder as the component' and give it the same name as the component class. This is a special shortcut for the Toolbo(Bitmap0ttribute onlyP don%t e(pect to find similar shortcuts for other design/time attributes.

Propert! "rowser +ntegration


!o matter what the icon is' after a component is dragged from the Toolbo( onto a form' it can be configured through the designer/managed 8roperty Browser. The Aesigner uses reflection to discover which properties the design/time control instance e(poses. #or each property' the Aesigner calls the associated get accessor for its current value and renders both the property name and the value onto the 8roperty Browser. #igure 9.10 shows how the 8roperty Browser loo$s for the basic cloc$ control. Figure 4.16. =isual ,tudio.(%T with a Cloc5 Control Chosen

The 5ystem.+omponent odel namespace provides a comprehensive set of attributes' shown in Table 9.1' to help you modify your component%s behavior and appearance in the 8roperty Browser. By default' public read and readCwrite properties.such as the 0larm property highlighted in #igure 9.10.are displayed in the 8roperty Browser under the & isc& category. *f a property is intended for run time only' you can prevent it from appearing in the 8roperty Browser by adorning the property with Browsable0ttribute:
%)rowsa!le'ttri!ute+false.(

#/&lic &ool 6s6t0imeFor)+rea4 " *et " ... set " ... -

With *s*tTime#or0Brea$ out of the design/time picture' only the custom 0larm property remains. 6owever' it%s currently listed under the 8roperty Browser%s isc category and lac$s a description. Jou can improve the situation by applying both +ategory0ttribute and Aescription0ttribute:
% Category'ttri!ute+ )eha#ior .0escription'ttri!ute+ 'larm for late risers . (

#/&lic 9ate0ime )larm " *et " ... set " ... -

Ta9le 4.1. Design)Time Propert! "rowser 1ttri9utes


'ttri!ute 0escription

0mbient-alue0ttribute

5pecifies the value for this property that causes it to ac)uire its value from another source' usually its container 3see the section titled 0mbient 8roperties in +hapter 8: +ontrols4. Aetermines whether the property is visible in the 8roperty Browser.

Browsable0ttribute

Ta9le 4.1. Design)Time Propert! "rowser 1ttri9utes


'ttri!ute 0escription

+ategory0ttribute Aescription0ttribute Aesign9nly0ttribute

Tells the 8roperty Browser which group to include this property in. 8rovides te(t for the 8roperty Browser to display in its description bar. 5pecifies that the design/time value of this property is seriali=ed to the form%s resource file. This attribute is typically used on properties that do not e(ist at run time. 0llows this property to be combined with properties from other ob<ects when more than one are selected and edited.

ergable8roperty0ttribute

8arenthesi=e8roperty!ame0ttribute 5pecifies whether this property should be surrounded by parentheses in the 8roperty Browser. @ead9nly0ttribute 5pecifies that this property cannot be edited in the 8roperty Browser.

0fter adding these attributes and rebuilding' you will notice that the 0larm property has relocated to the desired category in the 8roperty Browser' and the description appears on the description bar when you select the property 3both shown in #igure 9.114. Jou can actually use +ategory0ttribute to create new categories' but you should do so only if the e(isting categories don%t suitably describe a property%s purpose. 9therwise' you%ll confuse users loo$ing for your properties in the logical category. Figure 4.11. 1larm Propert! with Categor!1ttri9ute and Description1ttri9ute 1pplied

*n #igure 9.11' some property values are shown in boldface and others are not. Boldface values are those that differ from the property%s default value' which is specified by Aefault-alue0ttribute:
I Cate*ory)ttri&/te(")##earance"), 9escri#tion)ttri&/te("Whether i*ital time is sho!n"),
0efaultLalue'ttri!ute+true.

J #/&lic &ool Sho!9i*ital0ime " *et " ... set " ... -

1sing Aefault-alue0ttribute also allows you to reset a property to its default value using the 8roperty Browser' which is available from the property%s conte(t menu' as shown in #igure 9.1>. Figure 4.12. /esetting a Propert! to +ts Default =alue

This option is disabled if the current property is already the default value. Aefault values represent the most common value for a property. 5ome properties' such as 0larm or Te(t' simply don%t have a default that%s possible to define' whereas others' such as "nabled and +ontrolBo(' do. Eust li$e properties' a class can have defaults. Jou can specify a default event by adorning a class with Aefault"vent0ttribute:
%0efaultE#ent'ttri!ute+ 'larmSounded .(

class Cloc4Control : Control " ... -

Aouble/clic$ing the component causes the Aesigner to automatically hoo$ up the default eventP it does this by seriali=ing code to register with the specified event in *nitiali=e+omponent and providing a handler for it:
class Cloc4ControlHostForm : Form " ... $oi 6nitiali7eCom#onent() " ...
this.cloc6ControlB.'larmSounded 71

new 'larm,andler+this.cloc6ControlB8'larmSounded./

... ...
#oid cloc6ControlB8'larmSounded+ o!9ect senderCloc6ControlCi!rary.'larmType type. 4 5

...

Jou can also adorn your component with Aefault8roperty0ttribute:


%0efaultProperty'ttri!ute+ Show0igitalTime .(

#/&lic class Cloc4Control : Win o!s.Forms.Control " ... -

This attribute causes the Aesigner to highlight the default property when the component%s property is first edited' as shown in #igure 9.13. Figure 4.1 . Default Propert! Highlighted in the Propert! "rowser

Aefault properties aren%t terribly useful' but setting the correct default event properly can save a developer%s time when using your component.

Code ,eriali-ation
Whereas Aefault"vent0ttribute and Aefault8roperty0ttribute affect the behavior only of the 8roperty Browser' Aefault-alue0ttribute serves a dual purpose: *t also plays a role in helping the Aesigner determine which code is seriali=ed to *nitiali=e+omponent. 8roperties that don%t have a default value are automatically included in *nitiali=e+omponent. Those that have a default value are included only if the property%s value differs from the default. To avoid unnecessarily changing a property' your initial property values should match the value set by Aefault-alue0ttribute. Aesigner5eriali=ation-isibility0ttribute is another attribute that affects the code seriali=ation process. The Aesigner5eriali=ation-isibility0ttribute constructor ta$es a value from the Aesigner5eriali=ation-isibility enumeration:

en/m 9esi*nerSeriali7ationCisi&ility " Cisi&le, .. initiali7e this #ro#erty i, non e,a/lt $al/e Hi en, .. onFt initiali7e this #ro#erty Content .. initiali7e sets o, #ro#erties on a s/&o&'ect -

The default' -isible' causes a property%s value to be set in *nitiali=e+omponent if the value of the property is not the same as the value of the default. *f you%d prefer that no code be generated to initiali=e a property' use 6idden:
I 9e,a/ltCal/e)ttri&/te(tr/e), J #/&lic &ool Sho!9i*ital0ime " *et " ... set " ... 0esignerSeriali&ationLisi!ility'ttri!ute+ 0esignerSeriali&ationLisi!ility.,idden.

Jou can use 6idden in con<unction with Browsable0ttribute set to false for run/time/only properties. 0lthough Browsable0ttribute determines whether a property is visible in the 8roperty Browser' its value may still be seriali=ed unless you prevent that by using 6idden. By default' properties that maintain a collection of custom types cannot be seriali=ed to code. 5uch a property is implemented by the cloc$ control in the form of a &messages to self& feature' which captures a set of messages and displays them at the appropriate date and time. To enable seriali=ation of a collection' you can apply Aesigner5eriali=ation-isibility. +ontent to instruct the Aesigner to wal$ into the property and seriali=e its internal structure:
I Cate*ory)ttri&/te("+eha$ior"), 9escri#tion)ttri&/te ("St/,, to remem&er ,or later"),
0esignerSeriali&ationLisi!ility'ttri!ute +0esignerSeriali&ationLisi!ility.Content.

J #/&lic Messa*e0oSel,Collection Messa*es0oSel, " *et " ... set " ... -

The generated *nitiali=e+omponent code for a single message loo$s li$e this:
$oi 6nitiali7eCom#onent() " ...
this.cloc6ControlB.$essagesToSelf.'dd"ange+ new Cloc6ControlCi!rary.$essageToSelf%( 4 new Cloc6ControlCi!rary.$essageToSelf+ new System.0ateTime+GAA@- G- GG- GB- HH- A- A.- Wa6e up .5./

...

This code also needs a &translator& class to help the Aesigner seriali=e the code to construct a essageTo5elf type. This is covered in detail in the section titled &Type +onverters& later in this chapter. Host Form +ntegration While we%re tal$ing about affecting code seriali=ation' there%s another tric$ that%s needed for accessing a component%s hosting form. #or e(ample' consider a cloc$ control and a cloc$ component' both of which offer the ability to place the current time in the hosting form%s caption. "ach needs to ac)uire a reference to the host form to set the time in the form%s Te(t property. The control comes with native support for this re)uirement:
Form hostin*Form 1 this.Garent as Form;

1nfortunately' components do not provide a similar mechanism to access their host form. 0t design time' the component can find the form in the designer host%s +ontainer collection. 6owever' this techni)ue will not wor$ at run time because the +ontainer is not available at run time. To get its container at run time' a component must ta$e advantage of the way the Aesigner seriali=es code to the *nitiali=e+omponent method. Jou can write code that ta$es advantage of this infrastructure to seed itself with a reference to the host form at design time and run time. The first step is to grab the host form at design time using a property of type #orm:
Form hostin*Form 1 n/ll; I+ro!sa&le)ttri&/te(,alse)J #/&lic Form Hostin*Form " .. <se to #o#/late 6nitiali7eCom#onent at esi*n time *et " i,( (hostin*Form 11 n/ll) ZZ this.9esi*nMo e ) " .. )ccess esi*ner host an o&tain re,erence to root com#onent 69esi*nerHost esi*ner 1 this.EetSer$ice(ty#eo,(69esi*nerHost)) as 69esi*nerHost; i,( esi*ner 31 n/ll ) " hostin*Form 1 esi*ner.2ootCom#onent as Form; ret/rn hostin*Form; set "...-

The 6osting#orm property is used to populate the code in *nitiali=e+omponent at design time' when the designer host is available. 5tored in the designer host%s @oot+omponent property' the root component represents the primary purpose of the Aesigner. #or e(ample' a #orm component is the root component of the Windows #orms Aesigner. Aesigner6ost.@oot+omponent is a helper function that allows you to access the root component without enumerating the +ontainer collection. 9nly one component is considered the root component by the designer host. Because the 6osting#orm property

should go about its business transparently' you should decorate it with Browsable0ttribute set to false' thereby ensuring that the property is not editable from the 8roperty Browser. Because 6ost#orm is a public property' the Aesigner retrieves 6ost#orm%s value at design time to generate the following code' which is needed to initiali=e the component:
$oi 6nitiali7eCom#onent() " ...
this.myComponentB.,ostingForm 1 this/

... -

0t run time' when *nitiali=e+omponent runs' it will return the hosting form to the component via the 6osting#orm property setter:
Form hostin*Form 1 n/ll; I+ro!sa&le)ttri&/te(,alse)J #/&lic Form Hostin*Form " *et " ... // Set !y ;nitiali&eComponent at run time set 4 if+ 2this.0esign$ode . 4 // 0on:t change hosting form at run time if+ +hostingForm 21 null. TT +hostingForm 21 #alue. . 4 throw new ;n#alidJperationE*ception + Can:t set ,ostingForm at run time. ./ 5 5 else hostingForm 1 #alue/ 5

*n this case' we%re using our $nowledge of how the Aesigner wor$s to tric$ it into handing our component a value at run/time that we pic$ at design/time.

"atch +nitiali-ation
0s you may have noticed' the code that eventually gets seriali=ed to *nitiali=e+omponent is laid out as an alphanumerically ordered se)uence of property sets' grouped by ob<ect. 9rder isn%t important until your component e(poses range/dependent properties' such as inC a( or 5tartC5top pairs. #or e(ample' the cloc$ control also has two dependent properties: 8rimary0larm and Bac$up0larm 3the 0larm property was split into two for e(tra sleepy people4. *nternally' the cloc$ control instance initiali=es the two properties 10 minutes apart' starting from the current date and time:
9ate0ime #rimary)larm 1 9ate0ime.Bo!; 9ate0ime &ac4/#)larm 1 9ate0ime.Bo!.) Min/tes(5?);

Both properties should chec$ to ensure that the values are valid:
#/&lic 9ate0ime Grimary)larm " *et " ret/rn #rimary)larm; set "

if+ #alue =1 !ac6up'larm . throw new 'rgumentJutJf"angeE*ception + Primary alarm must !e !efore )ac6up alarm ./

#rimary)larm 1 $al/e;

#/&lic 9ate0ime +ac4/#)larm " *et " ret/rn &ac4/#)larm; set " if+ #alue < #rimary)larm .
throw new 'rgumentJutJf"angeE*ception + )ac6up alarm must !e after Primary alarm ./

&ac4/#)larm 1 $al/e;

With this dependence chec$ing in place' at design time the 8roperty Browser will show an e(ception in an error dialog if an invalid property is entered' as shown in #igure 9.1H. Figure 4.1#. +n&alid =alue %ntered into the Propert! "rowser

This error dialog is great at design time' because it lets the developer $now the relationship between the two properties. 6owever' there%s a problem when the properties are seriali=ed into *nitiali=e+omponent alphabetically:
$oi 6nitiali7eCom#onent() " ... .. cloc4Control5 ...
this.cloc6ControlB.Primary'larm 1 new System.0ateTime+GAA@- BB- GS- B@- HF- SF- SE./

this.cloc6ControlB.)ac6up'larm 1 new System.0ateTime+GAA@- BB- GS- B@- SG- SF- SE./

...

!otice that even if the developer sets the two alarms properly' as soon as Bac$up0larm is set and is chec$ed against the default value of 8rimary0larm' a run/time e(ception will result. To avoid this' a component must be notified when its properties are being set from *nitiali=e+omponent in &batch mode& so that they can be validated all at once at the end. *mplementing the *5upport*nitiali=e interface 3from the 5ystem.+omponent odel namespace4 provides this capability' with two notification methods to be called before and after initiali=ation:
#/&lic inter,ace 6S/##ort6nitiali7e " #/&lic $oi +e*in6nit(); #/&lic $oi (n 6nit();

When a component implements this interface' calls to Begin*nit and "nd*nit are seriali=ed to *nitiali=e+omponent:
$oi 6nitiali7eCom#onent() " ...

++System.Component$odel.;Support;nitiali&e. +this.cloc6ControlB...)egin;nit+./

... .. cloc4Control5 this.cloc4Control5.+ac4/#)larm 1 ne! System.9ate0ime(@??N, 55, @H, 5N, H@, HK, HX); ... this.cloc4Control5.Grimary)larm 1 ne! System.9ate0ime(@??N, 55, @H, 5N, AK, HK, HX); ...
++System.Component$odel.;Support;nitiali&e. +this.cloc6ControlB...End;nit+./

... -

The call to Begin*nit signals the entry into initiali=ation batch mode' a signal that is useful for turning off value chec$ing:
#/&lic class Cloc4Control : Control, ...
!ool initiali&ing 1 false/ ;Support;nitiali&e

"

...

#oid )egin;nit+. 4 initiali&ing 1 true/ 5

... #/&lic 9ate0ime Grimary)larm " *et " ... set "
if+ 2initiali&ing . 4 /Q chec6 #alue Q/ 5

#rimary)larm 1 $al/e; -

#/&lic 9ate0ime +ac4/#)larm "

*et " ... set "


if+ 2initiali&ing . 4 /Q chec6 #alue Q/ 5

&ac4/#)larm 1 $al/e; -

8lacing the appropriate logic into "nd*nit performs batch validation:


#/&lic class Cloc4Control : Control,
;Support;nitiali&e #oid End;nit+. 4 if+ primary'larm =1 !ac6up'larm . throw new 'rgumentJutJf"angeE*ception + Primary alarm must !e !efore )ac6up alarm ./ 5

"

... -

"nd*nit also turns out to be a better place to avoid the timer%s Tic$ event' which currently fires once every second during design time. 0lthough the code inside the Tic$ event handler doesn%t run at design time 3because it%s protected by a chec$ of the Aesign ode property4' it would be better not to even start the timer at all until run time. 6owever' because Aesign ode can%t be chec$ed in the constructor' a good place to chec$ it is in the "nd*nit call' which is called after all properties have been initiali=ed at run time or at design time:
#/&lic class Cloc4Control : Control, 6S/##ort6nitiali7e " ... $oi (n 6nit() " ...
if+ 2this.0esign$ode . 4 // ;nitiali&e timer timer.;nter#al 1 BAAA/ timer.Tic6 71 new System.E#ent,andler+this.timer8Tic6./ timer.Ena!led 1 true/ 5

The Aesigner and the 8roperty Browser provide all $inds of design/time help to augment the e(perience of developing a component' including establishing how a property is categori=ed and described to the developer and how it%s seriali=ed for the *nitiali=e+omponent method.

!+tender $ro"ert( $roviders


5o far the discussion has focused on the properties implemented by a control for itself. TimeKone odifier' an e(ample of such a property' allows the cloc$ control to be configured to display the time in any time =one. 9ne way to use this feature is to display the time in each time =one where your organi=ation has offices. *f each office were visually represented with a picture bo(' you could drag one cloc$ control for each time =one onto

the form' manually ad<usting the TimeKone odifier property on each cloc$ control. The result might loo$ li$e #igure 9.1F. Figure 4.1'. Form with 8ultiple Time ;ones

This wor$s )uite nicely but could lead to real estate problems' particularly if you have one cloc$ control for each of the >H time =ones globally and' conse)uently' >H implementations of the same logic on the form. *f you are concerned about resources' this also means >H system timers. #igure 9.1S shows what this might loo$ li$e. Figure 4.1.. *ne Pro&ider Control for %ach Client Control

0nother approach is to have a single cloc$ control and update its TimeKone odifier property with the relevant time =one from the +lic$ event of each picture bo(. This is a cumbersome approach because it re)uires developers to write the code associating a time =one offset with each control' a situation controls are meant to help avoid. #igure 9.1I illustrates this approach.

Figure 4.10. *ne Pro&ider Control for 1ll Client Controls, 1ccessed with Code

0 nicer way to handle this situation is to provide access to a single implementation of the cloc$ control without forcing the developer to write additional property update code. .!"T offers e(tender property support to do <ust this' allowing components to e(tend property implementations to other components. 7ogically' an e%tender propert# is a property provided by an e(tender component' li$e the cloc$ control' on other components in the same container' li$e picture bo(es. "(tender properties are useful whenever a component needs data from a set of other components in the same host. #or e(ample' Win#orms itself provides several e(tender components' including "rror8rovider' 6elp8rovider' and ToolTip. *n the case of the ToolTip component' it ma$es a lot more sense to set the ToolTip property on each control on a form than it does to try to set tooltip information for each control using an editor provided by the ToolTip component itself. *n our case' by implementing TimeKone odifier as an e(tender property' we allow each picture bo( control on the form to get its own value' as shown in #igure 9.18. Figure 4.12. *ne Pro&ider Control for 1ll Client Controls, 1ccessed with a Propert! ,et

"(posing an e(tender property from your control re)uires that you first use 8rovide8roperty0ttribute to declare the property to be e(tended:
%Pro#ideProperty'ttri!ute+ Time[one$odifier - typeof+Picture)o*..(

#/&lic class Cloc4Control : Control " ... -

The first parameter to the attribute is the name of the property to be e(tended. The second parameter is the &receiver& type' which specifies the type of ob<ect to e(tend' such as 8ictureBo(. 9nly components of the type specified by the receiver can be e(tended. *f you want to implement a more sophisticated algorithm' such as supporting picture bo(es and panels' you must implement the *"(tender8rovider +an"(tend method:
class Cloc4Control : ...,
;E*tenderPro#ider " !ool ;E*tenderPro#ider.CanE*tend+o!9ect e*tendee. 4 // 0on:t e*tend self if+ e*tendee 11 this . return false/ // E*tend suita!le controls return+ +e*tendee is Picture)o*. XX +e*tendee is Panel. ./ 5

... -

0s you saw in #igure 9.18' the provider supports one or more e(tendee controls. +onse)uently' the provider control must be able to store and distinguish one e(tendee%s property value from that of another. *t does this in the GetU8roperty!ameV and 5etU8roperty!ameV methods' in which 8roperty!ame is the name you provided in 8rovide8roperty0ttribute. Then GetTimeKone odifier simply returns the property value when re)uested by the 8roperty Browser:
#/&lic class Cloc4Control : Control, 6(xten erGro$i er "
// $apping of components to numeric time&one offsets ,ashTa!le time[one$odifiers 1 new ,ashTa!le+./ pu!lic int KetTime[one$odifier+Control e*tendee. 4 // "eturn component:s time&one offset return int.Parse+time[one$odifiers%e*tendee(./ 5

... -

5etTimeKone odifier has a little more wor$ to do. !ot only does it put the property value into a new hash table for the e(tendee when provided' but it also removes the hash table entry when the property is cleared. 0lso' with the sample TimeKone odifier property' you need to hoo$ into each e(tendee control%s +lic$ event' unless the control isn%t using the e(tender property. 5etTimeKone odifier is shown here:
class Cloc4Control : ..., 6(xten erGro$i er "
,ashTa!le time[one$odifiers 1 new ,ashTa!le+./ ... pu!lic #oid SetTime[one$odifier+Control e*tendee- o!9ect #alue. 4 // ;f property isn:t pro#ided if+ #alue 11 null . 4 // "emo#e it time[one$odifiers."emo#e+e*tendee./ if+ 2this.0esign$ode . 4 e*tendee.Clic6 ?1 new E#ent,andler+e*tendee8Clic6./ 5 5 else 4 // 'dd the offset as an integer

time[one$odifiers%e*tendee( 1 int.Parse+#alue./ if+ 2this.0esign$ode . 4 e*tendee.Clic6 71 new E#ent,andler+e*tendee8Clic6./ 5 5 5

0s with other properties' you can affect the appearance of an e(tender property in the 8roperty Browser by adorning the GetU8roperty!ameV method with attributes:
class Cloc4Control : ..., 6(xten erGro$i er "
% Category+ )eha#ior .0escription+ Sets the time&one difference from the current time .0efaultLalue+ .

( pu!lic int KetTime[one$odifier+Control e*tendee. 4 ... 5

...

These attributes are applied to the e(tendee%s 8roperty Browser view. With all this in place' you can compile your e(tender component to see the results. "(tended properties will appear in the e(tendee component%s properties with the following naming format:
=(xten e Gro#ertyBame> on =(xten erGro$i erBame>

#igure 9.19 shows the TimeKone odifier e(tender property behaving li$e any other property on a 8ictureBo( control. Figure 4.14. %3tended Propert! in 1ction

*f a property is set and is not the default value' it is seriali=ed to *nitiali=e+omponent34' as a 5etTimeKone odifier method call' and grouped with the e(tendee component:
$oi 6nitiali7eCom#onent() " ...
this.cloc6ControlB.SetTime[one$odifier+this.picture)o*B- ?BB./

... -

"(tender properties allow a component to add to the properties of other components in the same host. *n this way' the developer can $eep the data with the intuitive component' which is not necessarily the component that provides the service

T("e Converters
When you select a component on a design surface' the entries in the 8roperty Browser are rendered from the design/time control instance. When you edit properties in the 8roperty Browser' the component%s design/time instance is updated with the new property values. This synchronicity isn%t as straightforward as it seems' however' because the 8roperty Browser displays properties only as te(t' even though the source properties can be of any type. 0s values shuttle between the 8roperty Browser and the design/time instance' they must be converted bac$ and forth between the string type and the type of the property. "nter the t#pe converter' the translator droid of .!"T' whose main goal in life is to convert between types. #or string/to/type conversion' a type converter is used for each property displayed in the 8roperty Browser' as shown in #igure 9.>0. Figure 4.26. The Propert! "rowser and Design)Time Con&ersion

.!"T offers the Type+onverter class 3from the 5ystem.+omponent odel namespace4 as the base implementation type converter. .!"T also gives you several derivations. including 5tring+onverter' *nt3>+onverter' and AateTime+onverter.that support conversion between common .!"T types. *f you $now the type that needs conversion at compile time' you can create an appropriate converter directly:
.. 0y#e is 4no!n at com#ile time 0y#eCon$erter con$erter 1 ne! 6ntN@Con$erter();

9r' if you don%t $now the type that needs conversion until run time' let the TypeAescriptor class 3from the 5ystem.+omponent odel namespace4 ma$e the choice for you:
.. 9onFt 4no! the ty#e &e,ore r/n time o&'ect my9ata 1 ?; 0y#eCon$erter con$erter 1 0y#e9escri#tor.EetCon$erter(my9ata.Eet0y#e());

The TypeAescriptor class provides information about a particular type or ob<ect' including methods' properties' events' and attributes. TypeAescriptor.Get+onverter evaluates a type to determine a suitable Type+onverter based on the following: 1. +hec$ing whether a type is adorned with an attribute that specifies a particular type converter. >. +omparing the type against the set of built/in type converters. 3. @eturning the Type+onverter base if no other type converters are found. Because the 8roperty Browser is designed to display the properties of any component' it can%t $now specific property types in advance. +onse)uently' it relies on TypeAescriptor.Get+onverter to dynamically select the most appropriate type converter for each property. 0fter a type converter is chosen' the 8roperty Browser and the design/time instance can perform the re)uired conversions' using the same fundamental steps as those shown in the following code:
.. Create the a##ro#riate ty#e con$erter o&'ect my9ata 1 ?;
TypeCon#erter con#erter 1 Type0escriptor.KetCon#erter+my0ata.KetType+../

.. Can con$erter con$ert int to strin*> i,( con#erter.CanCon#ertTo+typeof+string.. ) " .. Con$ert it -

o!9ect intToString 1 con#erter.Con#ertTo+SG- typeof+string../

.. Can con$erter con$ert strin* to int> i,( con#erter.CanCon#ertFrom+typeof+string.. ) " .. Con$ert it -

o!9ect stringTo;nt 1 con#erter.Con#ertFrom+ SG ./

When the 8roperty Browser renders itself' it uses the type converter to convert each design/ time instance property to a string representation using the following steps: 1. +an+onvertTo: +an you convert from the design/time property type to a string? >. +onvertTo: *f so' please convert property value to string. The string representation of the source value is then displayed at the property%s entry in the 8roperty Browser. *f the property is edited and the value is changed' the 8roperty Browser uses the ne(t steps to convert the string bac$ to the source property value: 1. +an+onvert#rom: +an you convert bac$ to the design/time property type? >. +onvert#rom: *f so' please convert string to property value.

5ome intrinsic type converters can do more than <ust convert between simple types. To demonstrate' let%s e(pose a #ace property of type +loc$#ace' allowing developers to decide how the cloc$ is displayed' including options for 0nalog' Aigital' or Both:
#/&lic en/m Cloc4Face " )nalo* 1 ?, 9i*ital 1 5, +oth 1 @ class Cloc4Control : Control " Cloc4Face ,ace 1 Cloc4Face.+oth; #/&lic Cloc4Face Face " *et " ... set " ... ... -

TypeAescriptor.Get+onverter returns an "num+onverter' which contains the smarts to e(amine the source enumeration and convert it to a drop/down list of descriptive string values' as shown in #igure 9.>1. Figure 4.21. %numeration T!pe Displa!ed in the Propert! "rowser &ia %numCon&erter

Custom T!pe Con&erters


0lthough the built/in type converters are useful' they aren%t enough if your component or control e(poses properties based on custom types' such as the cloc$ control%s 6our6and' inute6and' and 5econd6and properties' shown here:
#/&lic class Han " #/&lic Han (Color color, int !i th) " this.color 1 color; this.!i th 1 !i th; #/&lic Color Color " *et " ret/rn color; set " color 1 $al/e; -

#/&lic int Wi th " *et " ret/rn !i th; set " !i th 1 $al/e; Color color 1 Color.+lac4; int !i th 1 5; #/&lic class Cloc4Control : Control "
pu!lic ,and ,our,and 4 ... 5 pu!lic ,and $inute,and 4 ... 5 pu!lic ,and Second,and 4 ... 5

The idea is to give developers the option to pretty up the cloc$%s hands with color and width values. Without a custom type converter' the unfortunate result is shown in #igure 9.>>.
LFM LFM

Be careful when you use custom types for properties. *f the value of the property is null' you won%t be able to edit it in the 8roperty Browser at

all.

Figure 4.22. Comple3 Properties in the Propert! "rowser

Eust as the 8roperty Browser can%t $now which types it will be displaying' .!"T can%t $now which custom types you%ll be developing. +onse)uently' there aren%t any type of converters capable of handling them. 6owever' you can hoo$ into the type converter infrastructure to provide your own. Building a custom type converter starts by deriving from the Type+onverter base class:
#/&lic class Han Con$erter :
TypeCon#erter

" ... -

To support conversion' 6and+onverter must override +an+onvert#rom' +onvertTo' and +onvert#rom:


#/&lic class Han Con$erter : 0y#eCon$erter "
pu!lic o#erride !ool CanCon#ertFrom+ ;Type0escriptorConte*t conte*t- Type sourceType. 4...5 pu!lic o#erride o!9ect Con#ertFrom+

;Type0escriptorConte*t conte*tCulture;nfo infoo!9ect #alue. 4...5 pu!lic o#erride o!9ect Con#ertTo+ ;Type0escriptorConte*t conte*tCulture;nfo cultureo!9ect #alueType destinationType. 4...5

+an+onvert#rom lets clients $now what it can convert from. *n this case' 6and+onverter reports that it can convert from a string type to a 6and type:
#/&lic o$erri e &ool CanCon$ertFrom( 60y#e9escri#torContext context, 0y#e so/rce0y#e) " .. We can con$ert ,rom a strin* to a Han ty#e i,( so/rce0y#e 11 ty#eo,(strin*) ) " ret/rn tr/e; ret/rn &ase.CanCon$ertFrom(context, so/rce0y#e);

Whether the string type is in the correct format is left up to +onvert#rom' which actually performs the conversion. 6and+onverter e(pects a multivalued string. *t splits this string into its atomic values and then uses it to instantiate a 6and ob<ect:
#/&lic o$erri e o&'ect Con$ertFrom( 60y#e9escri#torContext context, C/lt/re6n,o in,o, o&'ect $al/e) " .. 6, con$ertin* ,rom a strin* i,( $al/e is strin* ) " .. +/il a Han ty#e try " .. Eet Han #ro#erties strin* #ro#ertyList 1 (strin*)$al/e; strin*IJ #ro#erties 1 #ro#ertyList.S#lit(F;F); ret/rn ne! Han (Color.FromBame(#ro#ertiesI?J.0rim()), int.Garse(#ro#ertiesI5J)); catch "thro! ne! )r*/ment(xce#tion("0he ar*/ments !ere not $ali ."); ret/rn &ase.Con$ertFrom(context, in,o, $al/e);

...

+onvertTo converts from a 6and type bac$ to a string:


#/&lic o$erri e o&'ect Con$ert0o( 60y#e9escri#torContext context, C/lt/re6n,o c/lt/re, o&'ect $al/e, 0y#e estination0y#e) "

.. 6, so/rce $al/e is a Han ty#e i,( $al/e is Han ) " .. Con$ert to strin* i,( ( estination0y#e 11 ty#eo,(strin*)) ) " Han han 1 (Han )$al/e; strin* color 1 (han .Color.6sBame Color > han .Color.Bame : han .Color.2 ; ", " ; han .Color.E ; ", " ; han .Color.+); ret/rn strin*.Format(""?-; "5-", color, han .Wi th.0oStrin*()); ret/rn &ase.Con$ert0o(context, c/lt/re, $al/e, estination0y#e);

Jou may have noticed that 6and+onverter doesn%t implement a +an+onvertTo override. The base implementation of Type+onverter.+an+onvertTo returns a Boolean value of true when )ueried for its ability to convert to a string type. Because this is the right behavior for 6and+onverter 3and for most other custom type converters4' there%s no need to override it. When the 8roperty Browser loo$s for a custom type converter' it loo$s at each property for a Type+onverter0ttribute:
#/&lic class Cloc4Control : Control " ...
% TypeCon#erter'ttri!ute +typeof+,andCon#erter.. (

#/&lic Han #/&lic Han #/&lic Han ...

Ho/rHan Min/teHan Secon Han

" ... " ... " ... -

%TypeCon#erter'ttri!ute +typeof+,andCon#erter.. (

%TypeCon#erter'ttri!ute +typeof+,andCon#erter.. (

6owever' this is somewhat cumbersome' so it%s simpler to decorate the type itself with Type+onverter0ttribute:
% TypeCon#erter'ttri!ute+typeof+,andCon#erter.. (

#/&lic class Han " ... #/&lic class Cloc4Control : Control " ... #/&lic Han Ho/rHan " ... #/&lic Han Min/teHan " ... #/&lic Han Secon Han " ... ... -

#igure 9.>3 shows the effect of the custom 6and+onverter type converter.

Figure 4.2 . HandCon&erter in 1ction

%3panda9le *9ject Con&erter


0lthough using the 1* shown in #igure 9.>3 is better than not being able to edit the property at all' there are still ways it can be improved. #or instance' put yourself in a developer%s shoes. 0lthough it might be obvious what the first part of the property is' it%s disappointing not to be able to pic$ the color from one of those pretty drop/down color pic$ers. 0nd what is the second part of the property meant to be? 7ength' width' degrees' something else? 0s an e(ample of what you%d li$e to see' the #ont type supports browsing and editing of its subproperties' as shown in #igure 9.>H. Figure 4.2#. %3panded Propert! =alue

This ability to e(pand a property of a custom type ma$es it a lot easier to understand what the property represents and what sort of values you need to provide. To allow subproperty editing' you simply change the base type from Type+onverter to "(pandable9b<ect+onverter 3from the 5ystem.+omponent odel namespace4:
#/&lic class Han Con$erter :
E*panda!leJ!9ectCon#erter

" ... -

This change gives you multivalue and nested property editing support' as shown in #igure 9.>F. Figure 4.2'. HandCon&erter Deri&ed from %3panda9le*9jectCon&erter

0lthough you don%t have to write any code to ma$e this property e(pandable' you must write a little code to fi( an ir$some problem: a delay in property updating. *n e(panded mode' a change to the root property value is automatically reflected in the nested property value list. This occurs because the root property entry refers to the design/time property instance' whereas its nested property values refer to the design/time instance%s properties directly' as illustrated in #igure 9.>S. Figure 4.2.. /elationship 9etween /oot and (ested Properties and Design) Time Propert! +nstance

When the root property is edited' the 8roperty Browser calls 6and+onverter.+onvert#rom to convert the 8roperty Browser%s string entry to a new 5econd6and instance' and that results in a refresh of the 8roperty Browser. 6owever' changing the nested values only changes the current instance%s property values' rather than creating a new instance' and that doesn%t result in an immediate refresh of the root property.

Type+onverters offer a mechanism you can use to force the creation of a new instance whenever instance property values change' something you achieve by overriding Get+reate*nstance5upported and +reate*nstance. The Get+reate*nstance5upported method returns a Boolean indicating whether this support is available and' if it is' calls +reate*nstance to implement it:
#/&lic class Han Con$erter : (x#an a&leD&'ectCon$erter "
pu!lic o#erride !ool KetCreate;nstanceSupported+ ;Type0escriptorConte*t conte*t. 4 // 'lways force a new instance return true/

pu!lic o#erride o!9ect Create;nstance+ ;Type0escriptorConte*t conte*t- ;0ictionary propertyLalues. 4 // Ise the dictionary to create a new instance return new ,and+ +Color.propertyLalues% Color (+int.propertyLalues% Width (./

...

*f Get+reate*nstance5upported returns true' then +reate*nstance will be used to create a new instance whenever any of the subproperties of an e(pandable ob<ect are changed. The property-alues argument to +reate*nstance provides a set of nameCvalue pairs for the current values of the ob<ect%s subproperties' and you can use them to construct a new instance. Custom T!pe Code ,eriali-ation with T!peCon&erters 0lthough the 6and type now plays nicely with the 8roperty Browser' it doesn%t yet play nicely with code seriali=ation. *n fact' at this point it%s not being seriali=ed to *nitiali=e+omponent at all. To enable seriali=ation of properties e(posing comple( types' you must e(pose a public 5hould5eriali=eU8roperty!ameV method that returns a Boolean:
#/&lic class Cloc4Control : Control " #/&lic Han Secon Han " ... !ool ShouldSeriali&eSecond,and+. 4

.. Dnly seriali7e non e,a/lt $al/es


return+ +second,and.Color 21 Color."ed. XX +second,and.Width 21 B. ./ 5

... -

*nternally' the Aesigner loo$s for a method named 5hould5eriali=e U8roperty!ameV to as$ whether the property should be seriali=ed. #rom the Aesigner%s point of view' it doesn%t matter whether your 5hould5eriali=eU8roperty!ameV is public or private' but choosing private removes it from client visibility.

To programmatically implement the 8roperty Browser reset functionality' you use the @esetU8roperty!ameV method:
#/&lic Han Secon Han
5

Secon Han

" ... -

#oid "esetSecond,and+. 4

1 ne! Han (Color.2e , 5);

*mplementing 5hould5eriali=e lets the design/time environment $now whether the property should be seriali=ed' but you also need to write custom code to help assist in the generation of appropriate *nitiali=e+omponent code. 5pecifically' the Aesigner needs an instance descriptor' which provides the information needed to create an instance of a particular type. The code seriali=er gets an *nstanceAescriptor ob<ect for a 6and by as$ing the 6and type converter:
#/&lic class Han Con$erter : (x#an a&leD&'ectCon$erter " #/&lic o$erri e &ool CanCon$ert0o( 60y#e9escri#torContext context, 0y#e estination0y#e) "

// We can !e con#erted to an ;nstance0escriptor if+ destinationType 11 typeof+;nstance0escriptor. . return true/

ret/rn &ase.CanCon$ert0o(context, -

estination0y#e);

#/&lic o$erri e o&'ect Con$ert0o( 60y#e9escri#torContext context, C/lt/re6n,o c/lt/re, o&'ect $al/e, 0y#e estination0y#e) " i,( $al/e is Han ) "
// Con#ert to ;nstance0escriptor if+ destinationType 11 typeof+;nstance0escriptor. . 4 ,and hand 1 +,and.#alue/ o!9ect%( properties 1 new o!9ect%G(/ Type%( types 1 new Type%G(/ // Color types%A( 1 typeof+Color./ properties%A( 1 hand.Color/ // Width types%B( 1 typeof+int./ properties%B( 1 hand.Width/ // )uild constructor Constructor;nfo ci 1 typeof+,and..KetConstructor+types./ return new ;nstance0escriptor+ci- properties./

...

... ret/rn &ase.Con$ert0o(context, c/lt/re, $al/e,

estination0y#e);

To be useful' an instance descriptor re)uires two pieces of information. #irst' it needs to $now what the constructor loo$s li$e. 5econd' it needs to $now which property values should be used if the ob<ect is instantiated. The former is described by the +onstructor*nfo type' and the latter is simply an array of values' which should be in constructor parameter order. 0fter the control is rebuilt and assuming that 5hould5eriali=eU8roperty!ameV permits' all 6and type properties will be seriali=ed using the information provided by the 6and+onverter/provided *nstanceAescriptor:
#/&lic class Cloc4ControlHostForm : Form " ... $oi 6nitiali7eCom#onent() " ...
this.cloc6ControlB.,our,and 1 new Cloc6ControlCi!rary.,and+System.0rawing.Color.)lac6- G./

...

Type converters provide all $inds of help for the 8roperty Browser and the Aesigner to display' convert' and seriali=e properties of custom types for components that use such properties.

&' T("e !ditors


"(pandable9b<ect+onverters help brea$ down a comple( multivalue property into a nested list of its atomic values. 0lthough this techni)ue simplifies editing of a complicated property' it may not be suitable for other properties that e(hibit the following behavior:

6ard to construct' interpret' or validate' such as a regular e(pression 9ne of a list of values so large it would be difficult to remember all of them 0 visual property' such as a #ore+olor' that is not easily represented as a string

0ctually' the #ore+olor property satisfies all three points. #irst' it would be hard to find the color you wanted by typing comma/separated integers li$e 33' 8S' >H or guessing a named color' li$e 8apayaWhip. 5econd' there are a lot of colors to choose from. #inally' colors are <ust plain visual. *n addition to supporting in/place editing in the 8roperty Browser' properties such as #ore+olor help the developer by providing an alternative 1*/based property/editing mechanism. Jou access this tool' shown in #igure 9.>I' from a drop/down arrow in the 8roperty Browser. Figure 4.20. Color Propert! Drop)Down 7+ %ditor

The result is a prettier' more intuitive way to select a property value. This style of visual editing is supported by the 1, t#pe editor' a design/time feature that you can leverage to similar effect. There are two types of &editor& you can choose from: modal or drop/down. Arop/down editors support single/clic$ property selection from a drop/down 1* attached to the 8roperty Browser. This 1* might be a nice way to enhance the cloc$ control%s #ace property' allowing developers to visuali=e the cloc$ face style as they ma$e their selection' shown in #igure 9.>8. Figure 4.22. Custom =iew Drop)Down 7+ %ditor

Jou begin implementing a custom 1* editor by deriving from the 1*Type"ditor class 3from the 5ystem.Arawing.Aesign namespace4:
#/&lic class Face( itor : <60y#e( itor " ... -

The ne(t step re)uires you to override the Get"dit5tyle and "dit-alue methods from the 1*Type"ditor base class:

#/&lic class Face( itor : <60y#e( itor " #/&lic o$erri e <60y#e( itor( itStyle Eet( itStyle( 60y#e9escri#torContext context) "...#/&lic o$erri e o&'ect ( itCal/e( 60y#e9escri#torContext context, 6Ser$iceGro$i er #ro$i er, o&'ect $al/e) "...-

0s with type converters' the appropriate 1* type editor' provided by the Get"ditor method of the TypeAescription class' is stored with each property. When the 8roperty Browser updates itself to reflect a control selection in the Aesigner' it )ueries Get"dit5tyle to determine whether it should show a drop/down button' an open dialog button' or nothing in the property value bo( when the property is selected. This behavior is determined by a value from the 1*Type"ditor"dit5tyle enumeration:
en/m <60y#e( itor( itStyle " 9ro#9o!n, .. 9is#lay ro#: o!n <6 Mo al, .. 9is#lay mo al ialo* <6 Bone, .. 9onFt is#lay a <6 -

!ot overriding Get"dit5tyle is the same as returning 1*Type"ditor"dit5tyle.!one' which is the default edit style. To show the drop/down 1* editor' the cloc$ control returns 1*Type"ditor"dit5tyle.AropAown:
#/&lic class Face( itor : <60y#e( itor "
pu!lic o#erride I;TypeEditorEditStyle KetEditStyle+ ;Type0escriptorConte*t conte*t. 4 if+ conte*t 21 null . return I;TypeEditorEditStyle.0rop0own/ return !ase.KetEditStyle+conte*t./ 5

... -

*TypeAescriptor+onte(t is passed to Get"dit5tyle to provide conte(tual information regarding the e(ecution of this method' including the following:

The container and' subse)uently' the designer host and its components The component design/time instance being shown in the 8roperty Browser 0 8ropertyAescriptor type describing the property' including the Type+onverter and 1*Type"ditor assigned to the component 0 8ropertyAescriptorGrid"ntry type' which is a composite of the 8ropertyAescriptor and the property%s associated grid entry in the 8roperty Browser

Whereas Get"dit5tyle is used to initiali=e the way the property behaves' "dit-alue actually implements the defined behavior. Whether the 1* editor is drop/down or modal' you follow the same basic steps to edit the value: 1. 0ccess the 8roperty Browser%s 1* display service' *Windows#orms"ditor5ervice. >. +reate an instance of the editor 1* implementation' which is a control that the 8roperty Browser will display. 3. 8ass the current property value to the 1* editor control. H. 0s$ the 8roperty Browser to display the 1* editor control. F. +hoose the value and close the 1* editor control. S. @eturn the new property value from the editor.

Drop)Down 7+ T!pe %ditors


6ere%s how the cloc$ control implements these steps to show a drop/down editor for the #ace property:
#/&lic class Face( itor : <60y#e( itor " ...
pu!lic o#erride o!9ect EditLalue+ ;Type0escriptorConte*t conte*t;Ser#icePro#ider pro#idero!9ect #alue. 4

if+ +conte*t 21 null. TT +pro#ider 21 null. . 4 // 'ccess the Property )rowser:s I; display ser#ice ;WindowsFormsEditorSer#ice editorSer#ice 1 +;WindowsFormsEditorSer#ice. pro#ider.KetSer#ice+typeof+;WindowsFormsEditorSer#ice../ if+ editorSer#ice21 null . 4 // Create an instance of the I; editor control FaceEditorControl drop0ownEditor 1 new FaceEditorControl+editorSer#ice./ // Pass the I; editor control the current property #alue drop0ownEditor.Face 1 +Cloc6Face.#alue/ // 0isplay the I; editor control editorSer#ice.0rop0ownControl+drop0ownEditor./ // "eturn the new property #alue from the I; editor control return drop0ownEditor.Face/

5 5 return !ase.EditLalue+conte*t- pro#ider- #alue./ 5

When it comes to displaying the 1* editor control' you must play nicely in the design/time environment' particularly regarding 1* positioning in relation to the 8roperty Browser. 5pecifically' drop/down 1* editors must appear flush against the bottom of the property entry and must be si=ed to the width of the property entry.

To facilitate this' the 8roperty Browser e(poses a service' an implementation of the *Windows#orms"ditor5ervice interface' to manage the loading and unloading of 1* editor controls as well as their positioning inside the development environment. The #ace"ditor type references this service and calls its AropAown+ontrol method to display the #ace"ditor+ontrol' relative to 8roperty%s Browser edit bo(. When displayed' #ace"ditor+ontrol has the responsibility of capturing the user selection and returning control to "dit-alue with the new value. This re)uires a call to *Windows#orms"ditor5ervice.+loseAropAown from #ace"ditor+ontrol' something you do by passing to #ace"ditor+ontrol a reference to the *Windows#orms"ditor5ervice interface:
#/&lic class Face( itorControl : <serControl " Cloc4Face ,ace 1 Cloc4Face.+oth;
;WindowsFormsEditorSer#ice editorSer#ice 1 null/

...
pu!lic FaceEditorControl+;WindowsFormsEditorSer#ice editorSer#ice. 4 ... this.editorSer#ice 1 editorSer#ice/ 5

#/&lic Cloc4Face Face " *et " ... set " ... $oi #ic+oth%Clic4(o&'ect sen er, System.($ent)r*s e) " ,ace 1 Cloc4Face.+oth;
// Close the I; editor control upon #alue selection editorSer#ice.Close0rop0own+./

$oi #ic)nalo*%Clic4(o&'ect sen er, System.($ent)r*s e) " ,ace 1 Cloc4Face.)nalo*;


// Close the I; editor control upon #alue selection editorSer#ice.Close0rop0own+./

$oi #ic9i*ital%Clic4(o&'ect sen er, System.($ent)r*s e) " ,ace 1 Cloc4Face.9i*ital;


// Close the I; editor control upon #alue selection editorSer#ice.Close0rop0own+./

...

The final step is to associate #ace"ditor with the #ace property by adorning the property with "ditor0ttribute:
I Cate*ory)ttri&/te(")##earance"), 9escri#tion)ttri&/te("Which style o, cloc4 ,ace to 9e,a/ltCal/e)ttri&/te(Cloc4Face.+oth),

is#lay"),

Editor'ttri!ute+typeof+FaceEditor.- typeof+I;TypeEditor..

J #/&lic Cloc4Face Face " ... -

!ow #ace"ditor is in place for the #ace property. When a developer edits that property in 8ropery Browser' it will show a drop/down arrow and the #ace"ditor+ontrol as the 1* for the developer to use to choose a value of the +loc$#ace enumeration.

8odal 7+ T!pe %ditors


0lthough drop/down editors are suitable for single/clic$ selection' there are times when unrestricted editing is re)uired. *n such situations' you would use a modal 1*Type"ditor implemented as a modal form. #or e(ample' the cloc$ control has a digital time format sufficiently comple( to edit with a separate dialog outside the 8roperty Browser:
#/&lic class Cloc4Control : Control " ...
string digitalTimeFormat 1 dd/$$/yyyy hh3mm3ss tt /

... I Cate*ory)ttri&/te(")##earance"), 9escri#tion)ttri&/te("0he i*ital time ,ormat, ..."),


0efaultLalue'ttri!ute+ dd/$$/yyyy hh3mm3ss tt .-

J #/&lic strin* 9i*ital0imeFormat " *et " ret/rn i*ital0imeFormat; set " i*ital0imeFormat 1 $al/e; this.6n$ali ate(); -

Aate and Time format strings are composed of a comple( array of format specifiers that are not easy to remember and certainly aren%t intuitive in a property browser' as shown in #igure 9.>9. Figure 4.24. The DigitalTimeFormat Propert!

odal 1*Type"ditors are an ideal way to provide a more intuitive way to construct hard/ to/format property values. By providing a custom form' you give developers whatever editing e(perience is the most conducive for that property type. #igure 9.30 illustrates how

the Aigital Time #ormat "ditor dialog ma$es it easier to edit the cloc$ control%s AigitTime#ormat property. Figure 4. 6. Custom DigitalTimeFormat 8odal 7+ %ditor

0 modal 1*Type"ditor actually re)uires slightly different code from that of its drop/down counterpart. Jou follow the same logical steps as with a drop/down editor' with three minor implementation differences:

@eturning 1*Type"ditor"dit5tyle. odal from 1*Type"ditor.Get"dit5tyle +alling *Windows#orms"ditor5ervice.5howAialog from "dit-alue to open the 1* editor dialog !ot re)uiring an editor service reference to be passed to the dialog' because a Windows #orm can close itself

The cloc$ control%s modal 1* type editor is shown here:


#/&lic class 9i*ital0imeFormat( itor : <60y#e( itor " #/&lic o$erri e <60y#e( itor( itStyle Eet( itStyle( 60y#e9escri#torContext context) " i,( context 31 n/ll ) "
return I;TypeEditorEditStyle.$odal/

ret/rn &ase.Eet( itStyle(context); #/&lic o$erri e o&'ect ( itCal/e( 60y#e9escri#torContext context, 6Ser$iceGro$i er #ro$i er, o&'ect $al/e) " i,( (context 31 n/ll) ZZ (#ro$i er 31 n/ll) ) "

// 'ccess the Property )rowser:s I; display ser#ice ;WindowsFormsEditorSer#ice editorSer#ice 1 +;WindowsFormsEditorSer#ice. pro#ider.KetSer#ice+typeof+;WindowsFormsEditorSer#ice../ if+ editorSer#ice 21 null . 4 // Create an instance of the I; editor form 0igitalTimeFormatEditorForm modalEditor 1 new 0igitalTimeFormatEditorForm+./

// Pass the I; editor dialog the current property #alue modalEditor.0igitalTimeFormat 1 +string.#alue/ // 0isplay the I; editor dialog if+ editorSer#ice.Show0ialog+modalEditor. 11 0ialog"esult.JP . 4 // "eturn the new property #alue from the I; editor form return modalEditor.0igitalTimeFormat/ 5

ret/rn &ase.( itCal/e(context, #ro$i er, $al/e); -

0t this point' normal dialog activities 3as covered in +hapter 3: Aialogs4 apply for the 1* editor%s modal form:
#/&lic class 9i*ital0imeFormat( itorForm : Form " ... strin* i*ital0imeFormat 1 " .MM.yyyy hh:mm:ss tt"; #/&lic strin* 9i*ital0imeFormat " *et " ret/rn i*ital0imeFormat; set " i*ital0imeFormat 1 $al/e; ... $oi &tnDL%Clic4(o&'ect sen er, System.($ent)r*s e) " 9ialo*2es/lt 1 9ialo*2es/lt.DL; i*ital0imeFormat 1 txtFormat.0ext; ... -

0gain' to associate the new 1* type editor with the property re)uires applying the "ditor0ttribute:
I Cate*ory)ttri&/te(")##earance"), 9escri#tion)ttri&/te("0he i*ital time ,ormat, ..."), 9e,a/ltCal/e)ttri&/te(" .MM.yyyy hh:mm:ss tt"), Editor )ttri&/te+typeof+0igitalTimeFormatEditor.- typeof+I;TypeEditor.. J #/&lic strin* 9i*ital0imeFormat " ... -

0fter "ditor0ttribute is applied' the modal 1*Type"ditor is accessed via an ellipsis/style button displayed in the 8roperty Browser' as shown in #igure 9.31. Figure 4. 1. 1ccessing a 8odal 7+T!pe%ditor

1* type editors allow you to provide a customi=ed editing environment for the developer on a per/property basis' whether it%s a drop/down 1* to select from a list of possible values or a modal dialog to provide an entire editing environment outside the 8roperty Browser.

Custom #esigners
5o far' you have seen how properties are e(posed to the developer at design time' and you%ve seen some of the $ey infrastructure provided by .!"T to improve the property/ editing e(perience' culminating in 1*Type"ditor. 0lthough the focus has been on properties' they aren%t the only aspect of a control that operates differently in design/time mode compared with run/time mode. *n some situations' a control%s 1* might render differently between these modes. #or e(ample' the 5plitter control displays a dashed border when its Border5tyle is set to Border5tyle.!one. This design ma$es it easier for developers to find this control on the form%s design surface in the absence of a visible border' as illustrated in #igure 9.3>. Figure 4. 2. ,plitter Dashed "order When "order,t!le +s (one

Because Border5tyle.!one means &don%t render a border at run time'& the dashed border is drawn only at design time for the developer%s benefit. 9f course' if Border5tyle is set to Border5tyle.#i(ed5ingle or Border5tyle.#i(ed3A' the dashed border is not necessary' as illustrated by #igure 9.33. Figure 4. . ,plitter with "order,t!le.Fi3ed D

What%s interesting about the splitter control is that the dashed border is not actually rendered from the control implementation. *nstead' this wor$ is conducted on behalf of them by a c&stom designer' another .!"T design/time feature that follows the tradition' honored by type converters and 1* type editors' of separating design/time logic from the control. +ustom designers are not the same as designer hosts or the Windows #orms Aesigner' although a strong relationship e(ists between designers and designer hosts. 0s every component is sited' the designer host creates at least one matching designer for it. 0s with type converters and 1* type editors' the TypeAescriptor class does the wor$ of creating a designer in the +reateAesigner method. 0dorning a type with Aesigner0ttribute ties it to the specified designer. #or components and controls that don%t possess their own custom designers' .!"T provides +omponentAesigner and +ontrolAesigner' respectively' both of which are base implementations of *Aesigner:
#/&lic inter,ace 69esi*ner : 69is#osa&le " #/&lic $oi 9o9e,a/lt)ction(); #/&lic $oi 6nitiali7e(6Com#onent com#onent); #/&lic 6Com#onent Com#onent " *et; #/&lic 9esi*nerCer&Collection Cer&s " *et; -

#or e(ample' the cloc$ face is round at design time when the cloc$ control either is 0nalog or is 0nalog and Aigital. This ma$es it difficult to determine where the edges and corners of the control are' particularly when the cloc$ is being positioned against other controls. The dashed border techni)ue used by the splitter would certainly help' loo$ing something li$e #igure 9.3H. Figure 4. #. "order Displa!ed from Cloc5ControlDesigner

Because the cloc$ is a custom control' its custom designer will derive from the +ontrolAesigner base class 3from the 5ystem.Windows.#orms. Aesign namespace4:
#/&lic class Cloc4Control9esi*ner : Control9esi*ner " ... -

To paint the dashed border' +loc$+ontrolAesigner overrides the *nitiali=e and 9n8aint0dornments methods:
#/&lic class Cloc4Control9esi*ner : Control9esi*ner " ...
pu!lic o#erride #oid ;nitiali&e+;Component component. 4 ... 5 protected o#erride #oid JnPaint'dornments+PaintE#ent'rgs e. 4 ... 5

...

*nitiali=e is overridden to deploy initiali=ation logic that%s e(ecuted as the control is being sited. *t%s also a good location to cache a reference to the control being designed:
#/&lic class Cloc4Control9esi*ner : Control9esi*ner "
Cloc6Control cloc6Control 1 null/ pu!lic o#erride #oid ;nitiali&e+;Component component. 4 !ase.;nitiali&e+component./ // Ket cloc6 control shortcut reference cloc6Control 1 +Cloc6Control.component/

...

Jou could manually register with +ontrol.9n8aint to add your design/time 1*' but you%ll find that overriding 9n8aint0dornments is a better option because it is called only after the control%s design/time or run/time 1* is painted' letting you put the icing on the ca$e:
#/&lic class Cloc4Control9esi*ner : Control9esi*ner " ...
protected o#erride #oid JnPaint'dornments+PaintE#ent'rgs e. 4

.. Let the &ase class ha$e a crac4

!ase.JnPaint'dornments+e./

.. 9onFt sho! &or er i, it oes not ha$e an )nalo* ,ace i,( cloc4Control.Face 11 Cloc4Face.9i*ital ) ret/rn; .. 9ra! &or er Era#hics * 1 e.Era#hics; /sin*( Gen #en 1 ne! Gen(Color.Eray, 5) ) " #en.9ashStyle 1 9ashStyle.9ash; *.9ra!2ectan*le( #en, ?, ?, cloc4Control.Wi th : 5, cloc4Control.Hei*ht : 5); 5

... -

0dding Aesigner0ttribute to the +loc$+ontrol class completes the association:


I 9esi*ner)ttri&/te(ty#eo,(Cloc4Control9esi*ner)) J #/&lic class Cloc4Control : Control " ... -

Design)Time)*nl! Properties
The cloc$ control is now wor$ing as shown in #igure 9.3H. 9ne way to improve on this is to ma$e it an option to show the border' because it%s a feature that not all developers will li$e. 0dding a design/time/only 5howBorder property will do the tric$' because this is not a feature that should be accessible at run time. *mplementing a design/time/only property on the control itself is not ideal because the control operates in both design/time and run/ time modes. Aesigners are e(actly the right location for design/time properties. To add a design/time/only property' start by adding the basic property implementation to the custom designer:
#/&lic class Cloc4Control9esi*ner : Control9esi*ner " ...
!ool show)order 1 true/

... #rotecte ...

o$erri e $oi

DnGaint) ornments(Gaint($ent)r*s e) "

// 0on:t show !order if hidden or // does not ha#e an 'nalog face if+ +2show)order. XX +cloc6Control.Face 11 Cloc6Face.0igital. . return/

...

// Pro#ide implementation of Show)order to pro#ide // storage for created Show)order property !ool Show)order 4 get 4 return show)order/ 5 set 4 show)order 1 #alue/ cloc6Control."efresh+./ 5 5

This isn%t enough on its own' however' because the 8roperty Browser won%t e(amine a custom designer for properties when the associated component is selected. The 8roperty Browser gets its list of properties from TypeAescriptor%s Get8roperties method 3which' in turn' gets the list of properties using .!"T reflection4. To augment the properties returned by the TypeAescriptor class' a custom designer can override the 8re#ilter8roperties method:
#/&lic class Cloc4Control9esi*ner : Control9esi*ner " ...
protected o#erride #oid PreFilterProperties+ ;0ictionary properties. 4 // Cet the !ase ha#e a chance !ase.PreFilterProperties+properties./ // Create design?time?only property entry and add it to // the Property )rowser:s 0esign category properties% Show)order ( 1 Type0escriptor.CreateProperty+ typeof+Cloc6Control0esigner.Show)order typeof+!ool.Category'ttri!ute.0esign0esignJnly'ttri!ute.Res./

...

The properties argument to 8re#ilter8roperties allows you to populate new properties by creating 8ropertyAescriptor ob<ects using the TypeAescriptor%s +reate8roperty method' passing the appropriate arguments to describe the new property. 9ne of the parameters to TypeAescriptor. +reate8roperty is Aesign9nly0ttribute.Jes' which specifies design/time/ only usage. *t also physically causes the value of 5howBorder to be persisted to the form%s resource file rather than to *nitiali=e+omponent' as shown in #igure 9.3F. Figure 4. '. ,how"order Propert! =alue ,eriali-ed to the Host Form?s /esource File

*f you need to alter or remove e(isting properties' you can override 8ost#ilter8roperties and act on the list of properties after TypeAescriptor has filled it using reflection. 8reC8ost filter

pairs can also be overridden for methods and events if necessary. #igure 9.3S shows the result of adding the 5howBorder design/time property. Figure 4. .. ,how"order *ption in the Propert! "rowser

Design)Time Conte3t 8enu =er9s


To ta$e the design/time/only property even further' it%s possible to add items to a component%s design/time conte(t menu. These items are called verbs' and 5howBorder would ma$e a fine addition to our cloc$ control%s verb menu. 0dding to the verb menu re)uires that we further augment the custom designer class:
#/&lic class Cloc4Control9esi*ner : Control9esi*ner " ...

pu!lic o#erride 0esignerLer!Collection Ler!s 4 get 4 // "eturn new list of conte*t menu items 0esignerLer!Collection #er!s 1 new 0esignerLer!Collection+./ show)orderLer! 1 new 0esignerLer!+ KetLer!Te*t+.new E#ent,andler+Show)orderClic6ed../ #er!s.'dd+show)orderLer!./ return #er!s/ 5 5

... -

The -erbs override is )ueried by the Aesigner shell for a list of Aesigner-erbs to insert into the component%s conte(t menu. "ach Aesigner-erb in the Aesigner-erb+ollection ta$es a string name value plus the event handler that responds to verb selection. *n our case' this is 5howBorder+lic$ed:
#/&lic class Cloc4Control9esi*ner : Control9esi*ner "

...

#oid Show)orderClic6ed+o!9ect sender- E#ent'rgs e. 4 // Toggle property #alue Show)order 1 2Show)order/ 5

... -

This handler simply toggles the 5howBorder property. 6owever' because the verb menu for each component is cached' it ta$es e(tra code to show the current state of the 5howBorder property in the verb menu:
#/&lic class Cloc4Control9esi*ner : Control9esi*ner " ...
!ool Show)order 4 get 4 return show)order/ 5

set 4 // Change property #alue Property0escriptor property 1 Type0escriptor.KetProperties+typeof+Cloc6Control..% Show)order (/ this."aiseComponentChanging+property./ show)order 1 #alue/ this."aiseComponentChanged+property- 2show)order- show)order./ // Toggle Show/,ide )order #er! entry in conte*t menu ;$enuCommandSer#ice menuSer#ice 1 +;$enuCommandSer#ice.this.KetSer#ice +typeof+;$enuCommandSer#ice../ if+ menuSer#ice 21 null . 4 // "e?create Show/,ide )order #er! if+ menuSer#ice.Ler!s.;nde*Jf+show)orderLer!. =1 A . 4 menuSer#ice.Ler!s."emo#e+show)orderLer!./ show)orderLer! 1 new 0esignerLer!+ KetLer!Te*t+.new E#ent,andler+Show)orderClic6ed../ menuSer#ice.Ler!s.'dd+show)orderLer!./ 5

5 5

// Ipdate cloc6 I; cloc6Control.;n#alidate +./

... -

5howBorder now performs two distinct operations. #irst' the property value is updated between calls to @aise+omponent+hanging and @aise+omponent+hanged' helper functions that wrap calls to the designer host%s *+omponent+hange5ervice. The second part of 5howBorder re/creates the 5howC6ide Border verb to reflect the new property value. This manual intervention is re)uired because the -erbs property is called only when a component is selected on the form. *n our case' &5howC6ide Border& could be toggled any number of times after the control has been selected.

#ortunately' after the -erbs property has delivered its Aesigner-erb+ollection payload to the Aesigner' it%s possible to update it via the designer host%s * enu+ommand5ervice. 1nfortunately' because the Te(t property is read/only' you can%t implement a simple property change. *nstead' the verb must be re/created and re/associated with 5howBorder+lic$ed every time the 5howBorder property is updated. 9n top of adding 5howC6ide Border to the conte(t menu' .!"T throws in a clic$able lin$ for each verb' located on the 8roperty Browser above the property description bar. #igure 9.3I illustrates all three options' including the original editable property. Figure 4. 0. ,how"order *ption in the Propert! "rowser and the Conte3t 8enu

+ustom designers allow you to augment an application developer%s design/time e(perience even further than simply adding the effects to the 8roperty Browser. Aevelopers can change how a control renders itself' controlling the properties' methods' and events that are available at design time and augmenting a component%s verbs.

Where Are We?


0lthough components 3and' by association' controls4 gain all $inds of integration into a .!"T design/time environment with very little wor$' .!"T also provides a rich infrastructure to augment the design/time e(perience for your custom components.

Chapter 16. /esources


0 resource is a named piece of data that is bound into an assembly at build time. @esources are an enormously useful way to bundle arbitrary data into your applications and components for use at run time for tas$s as diverse as setting the bac$ground image on a form and setting the label of a button. 0nd because applications and components can find themselves being used in countries other than those in which they were written' the .!"T resource architecture supports no/compile deployment of locali=ed resources.
L1M L1M

@ecall from +hapter 1: 6ello' Windows #orms' that an assembly is a .!"T e(ecutable or library 3A774.

Resource Basics
*magine setting the bac$ground image of a form in your application by loading a bitmap from a file:
#/&lic Form5() " ...

// Coad a file from the file system this.)ac6ground;mage 1 new )itmap+M C3NW;N0JWSNWe!NWallpaperN'&ul.9pg ./

The problem with this code is that not all installations of Windows will have 0=ul.<pg' and even those that do have it may not have it in the same place. "ven if you shipped this picture with your application' a space/conscious user may decide to remove it' causing your application to fault. The only safe way to ma$e sure that the picture' or any file' stays with code is to embed it and load it as a reso&rce' a named piece of data embedded in the assembly itself.

8anifest /esources
@esources are added to an assembly at compile time. To embed a file into an assembly using -5.!"T re)uires that you add the file to your -5.!"T pro<ect. To add a file to a pro<ect' right/clic$ on your pro<ect in 5olution "(plorer' choose 0dd "(isting *tem' and choose the file you%d li$e to add. *f it%s not already there' it will be copied into your pro<ect%s directory' but it is not yet embedded as a resource. To embed the file as a resource' right/ clic$ on the file and choose 8roperties' changing Build 0ction from +ontent 3the default4 to "mbedded @esource' as shown in #igure 10.1.
L>M L>M

The .!"T #ramewor$ 5AD command line compilers' such as csc.e(e and vbc.e(e' provide options for bundling files into assemblies as

resources 3for csc.e(e and vbc.e(e' the switch is Cresource4. *n addition' the Cembedresource switch for al.e(e will create a new assembly from an e(isting assembly and a set of files to embed as resources.

Figure 16.1. ,etting a File?s "uild 1ction to %m9edded /esource

When it%s mar$ed as an "mbedded @esource' a file gets embedded in the assembly%s set of manifest resources. The manifest of an assembly is composed of a set of metadata that%s part of the assembly. 8art of that metadata is the name and data associated with each embedded resource. (aming 8anifest /esources To chec$ that a file has been embedded properly into your pro<ect%s output assembly' you can use the .!"T #ramewor$ 5AD tool ildasm.e(e. This tool shows all embedded resources in the anifest view of your assembly' as shown in #igure 10.>. Figure 16.2. ildasm ,howing an %m9edded 8anifest /esource

0s shown in ildasm with the .mresource entry' embedding a file as a resource will cause -5.!"T to name the resource using the pro<ect%s default namespace' an optional subfolder name' and the resource file name itself in the following format:
= e,a/ltBames#ace>.=,ol erBame>.=,ileBame>

The default namespace portion of the resource name is the default namespace of the pro<ect itself' as set via 5olution "(plorer N Upro<ect!ameV3right/clic$4 N 8roperties N +ommon 8roperties N General N Aefault !amespace' as shown in #igure 10.3. Figure 16. . 1 =,.(%T Project?s Default (amespace

*f the file happens to be in a subfolder of your pro<ect' the folder name of the resource will include a version of that folder name' replacing the bac$ slashes with dots. #or e(ample' #igure 10.H shows the 0=ul.<pg file in the fooObar pro<ect subfolder' and #igure 10.F shows the resulting name of the resource in ildasm. Figure 16.#. The 1-ul.jpg /esource File in the fooD9ar Project ,u9folder

Figure 16.'. How =,.(%T Composes the (ame of a /esource in a Project ,u9folder

$oading 8anifest /esources To discover the resources embedded in an assembly' you can enumerate the list of manifest resources' as ildasm is doing' by using the Get anifest@esource!ames method of the 5ystem.@eflection.0ssembly class:
L3M L3M

0 type%s assembly can be retrieved from the associated Type ob<ect%s 0ssembly property. 5imilarly' the 0ssembly class itself provides several

methods for retrieving assemblies of interest: Get0ssembly' Get+alling0ssembly' Get"ntry0ssembly' and Get"(ecuting0ssembly.

/sin* System.2e,lection; ... .. Eet this ty#eFs assem&ly )ssem&ly assem 1 this.Eet0y#e().)ssem&ly;

// Enumerate the assem!ly:s manifest resources foreach+ string resourceName in assem.Ket$anifest"esourceNames+. . 4

Messa*e+ox.Sho!(reso/rceBame);
5

When you $now the name of a manifest resource' either by enumerating it or by hard/ coding the one you want' you can load it as a raw stream of bytes via the 0ssembly class%s Get anifest@esource5tream method:
/sin* System.6D; names#ace 2eso/rces)## " #/&lic Form5() " ... .. Eet this ty#eFs assem&ly )ssem&ly assem 1 this.Eet0y#e().)ssem&ly;
// Ket the stream that holds the resource // from the "esources'pp.'&ul.9pg resource // NJTEB3 $a6e sure not to close this stream// or the )itmap o!9ect will lose access to it // NJTEG3 'lso !e #ery careful to match the case // on the resource name itself Stream stream 1 assem.Ket$anifest"esourceStream+ "esources'pp.'&ul.9pg ./

.. Loa the &itma# ,rom the stream this.+ac4*ro/n 6ma*e 1 ne! +itma#(stream); -

!otice that the resource name passed to Get anifest@esource5tream is the full' case/ sensitive name of the resource' including the namespace and the file name. *f the resource is embedded from a subfolder of the pro<ect' remember to include the &dottified& version of the folder name as well:
Stream stream 1 assem.EetMani,est2eso/rceStream("2eso/rces)##.foo.!ar.)7/l.'#*");

8anifest /esource (amespaces *f you pass a 5ystem.Type ob<ect to the Get anifest@esource5tream method' it will use the type%s namespace as the namespace prefi( portion of the embedded resource. This is especially useful because' by default' a newly generated class in -5.!"T is contained in the pro<ect%s default namespace' allowing for an easy match between a type%s namespace and the pro<ect%s default namespace:
names#ace 2eso/rces)## " #/&lic class Form5 : Form "

...

// Coad the stream for resource "esources'pp.'&ul.9pg Stream stream 1 assem.Ket$anifest"esourceStream+this.KetType+.- '&ul.9pg ./

...

This namespace/specification shortcut also wor$s for some types that can directly load files that are embedded as resources. #or e(ample' the Bitmap class can load an image from a resource' eliminating the need to get the manifest stream manually:
names#ace 2eso/rces)## " #/&lic class Form5 : Form " ... #/&lic Form5() " ... .. Eet this ,ormFs assem&ly )ssem&ly assem 1 this.Eet0y#e().)ssem&ly;
// Coad image from "esources'pp.'&ul.9pg this.)ac6ground;mage 1 new )itmap+this.KetType+.- '&ul.9pg ./

To help you $eep trac$ of where all the parts of a manifest resource come from and how they%re specified' #igure 10.S shows a summary. Figure 16... 1 ,ummar! of 8anifest /esource (aming and (ame /esolution

0lthough manifest resources are useful' their degree of integration with -5.!"T and the type system is limited. 6owever' manifest resources serve as the needed foundation for typed resources' which address both of these issues.

T!ped /esources
Aespite the file%s e(tension' manifest resources are embedded with no type information. #or e(ample' if the name of the 0=ul.<pg file were 0=ul.)uu(' that would ma$e no difference to the Bitmap class' which is loo$ing at the data itself for the type.for e(ample' E8"G' 8!G' G*#' and so on. *t%s up to you to properly map the type of each resource to the type of the ob<ect needed to load it. 6owever' you can tag your resources with a type if you%re willing to ta$e an e(tra step. .!"T supports an e(tended set of metadata for a resource that includes ultipurpose

*nternet ail "(tensions 3 * "4 type information in two formats: one te(t and one binary. Both formats have readers so that you can pull out the properly typed resources at run time. Te3t)"ased T!ped /esources The te(t/based format is a .!"T/specific T 7 format called @esT 3.res( files4. *n spite of its te(t basis' this format is not meant to be read by humans 3as few T 7 formats are4. 6owever' -5.!"T provides a rudimentary editor for .res( files. To add a new .res( file to your -5.!"T pro<ect' choose 0dd !ew *tem from the 8ro<ect menu and pic$ the 0ssembly @esource #ile template' as shown in #igure 10.I. Figure 16.0. 1dding a .res3 File to a Project

0s of this writing' even an empty .res( file is H> lines of T 7' most of which is the schema information. The schema allows for any number of entries in the .res( file' each of which has a name' value' comment' type' and * " type. #igure 10.8 shows a .res( file with two entries: a string named y5tring and an image named y*mage. Figure 16.2. 1 ,imple .res3 File in the Data =iew of the Designer

The corresponding T 7 data for the entries is shown here:

=>xml $ersion1"5.?" enco in*1"/t,:P" >> =root> ...


<data name1 $yString = <#alue=a string #alue</#alue= <comment=a comment</comment= </data= <data name1 $y;mage type1 System.0rawing.)itmap- ... mimetype1 application/*?microsoft.net.o!9ect.!ytearray.!aseES = <#alue= ... !aseES?encoded image data ... </#alue= </data=

=.root>

9f the two entries' only the string entry can actually be edited in the Aata view of the .res( editor. The image entry was added by hand in the T 7 view 3the BaseSH/encoded data being particularly challenging4. #or this reason' the direct use of .res( files in -5.!"T is useful only for string resources 3although indirect usage ma$es .res( files very useful for any $ind of data' as you%ll see later in this chapter4. 0fter you%ve got a .res( file' you can load it and enumerate it using the @esT@esource@eader class from the 5ystem.@esources namespace:
/sin* System.Collections;
using System."esources/

...
using+ "esZ"esource"eader reader 1 new "esZ"esource"eader+M c3N"esourceB.res* . . 4 foreach+ 0ictionaryEntry entry in reader . 4

strin* s 1 strin*.Format(""?- ("5-)1 F"@-F", entry.Pey- entry.Lalue.KetType+.- entry.Lalue); Messa*e+ox.Sho!(s);


5 5

The @esT@esource@eader class parses the T 7 file and e(poses a set of named' typed values' but it provides no random access to them. 8ulling out a specific entry re)uires first finding it:
#/&lic Form5() " ... /sin*( 2esO2eso/rce2ea er rea er 1 ne! 2esO2eso/rce2ea er(V"2eso/rce5.resx") ) " ,oreach( 9ictionary(ntry entry in rea er ) "
if+ entry.Pey.ToString+. 11 $yString . 4 // Set form caption from string resource this.Te*t 1 +string.entry.Lalue/ 5

The benefit of the .res( file is that type information is embedded along with the data itself' re)uiring a simple cast to get to a typed version of the data. "inar! T!ped /esources To add a .res( file to your pro<ect as an embedded resource' you use the 0dd !ew *tem dialog. Building the pro<ect causes the .res( data to be embedded as nested reso&rces' which are resources grouped into a named container. When a .res( file is embedded as a resource in a -5.!"T pro<ect' it becomes the container for the nested resources it holds. 0s part of that process' the .res( file is compiled from the te(t format to the .resources binary format. 0ssuming a pro<ect%s default namespace of @esources0pp and a .res( file named @esources1.res(' the container of nested resources is named @esources0pp.@esources1.resources' as shown in ildasm in #igure 10.9. Figure 16.4. 1n %m9edded .resources File

The .resources e(tension comes from the resgen.e(e tool' which -5.!"T uses on the .res( file before embedding it as a resource. Jou can compile a .res( file into a .resources file yourself by using the following command line 3which produces @esource1.resources in this case4:
C:\> res*en.exe 2eso/rce5.resx

0fter you%ve compiled a .res( file into a .resources file in the file system' you can load it and enumerate it using @esource@eader from the 5ystem.@esources namespace. 9ther than the name of the class and the input format' usage of the @esource@eader class is identical to that of @esT@esource@eader' including the lac$ of random access for named entries:
/sin*( 2eso/rce2ea er rea er 1 ne! 2eso/rce2ea er(V"c:\2eso/rce5.reso/rces") ) " ,oreach( 9ictionary(ntry entry in rea er ) " strin* s 1 strin*.Format(""?- ("5-)1 F"@-F", entry.Ley, entry.Cal/e.Eet0y#e(), entry.Cal/e); Messa*e+ox.Sho!(s); -

Jou can read a .resources file from the file system' but because -5.!"T compiles a .res( file and embeds the resulting .resources file for you' it%s easier to access a .resources file directly from its manifest resource stream:
.. Loa em&e e .reso/rces ,ile /sin*( Stream stream 1 assem.EetMani,est2eso/rceStream( this.Eet0y#e(), "2eso/rce5.reso/rces") ) " .. Fin reso/rce in .reso/rces ,ile /sin*( 2eso/rce2ea er rea er 1 ne! 2eso/rce2ea er(stream) ) " ,oreach( 9ictionary(ntry entry in rea er ) " i,( entry.Ley.0oStrin*() 11 "MyStrin*" ) " .. Set ,orm ca#tion ,rom strin* reso/rce this.0ext 1 entry.Cal/e.0oStrin*(); -

This two/step process.loading the .res( file or the .resources nested resource and then enumerating all values loo$ing for the one you want.is an inconvenience' so .!"T provides the @esource anager class' which provides random access to resources.

/esource 8anager
The @esource anager class' also from the 5ystem.@esources namespace' is initiali=ed with an embedded .resources file:
#/&lic Form5() " ... .. Eet this ty#eFs assem&ly )ssem&ly assem 1 this.Eet0y#e().)ssem&ly;
// Coad the .resources file into the "esource$anager // 'ssumes a file named "esourceB.res* as part of the pro9ect "esource$anager resman 1 new "esource$anager+ "esources'pp."esourceB - assem./ ...

!otice the use of the pro<ect%s default namespace appended to the @esource1.resources file. Jou name your .resources files in e(actly the same way that you name any other $ind of resource' e(cept that the .resources e(tension is assumed and cannot be included in the name. 0s a further convenience' if you name a .res( file with the name of a type' such as y+ustomType.res(' the name of the .resources file and the assembly can be determined from the type:

names#ace 2eso/rces)## " class MyC/stom0y#e " #/&lic MyC/stom0y#e() "


// Coad "esources'pp.$yCustomType.resources // from the $yCustomType class:s assem!ly "esource$anager resman 1 new "esource$anager+this.KetType+../

... -

1sing a type%s namespace and name to name a .res( file is a useful way to $eep per/type resources. This is how Win#orms Aesigner associates resources with custom forms and other types with design surfaces' as you%ll see soon. 1ccessing /esources from a /esource 8anager 0fter you%ve created a resource manager' you can pull out nested resources by name using the Get9b<ect method' casting to the appropriate type. But if you%re using the .res( file for string resources' you%ll want to use the Get5tring method instead. This method performs the cast to the 5ystem.5tring type for you:
names#ace 2eso/rces)## " class MyC/stom0y#e " #/&lic MyC/stom0y#e() "

// Coad "esources'pp.$yCustomType.resources "esource$anager resman 1 new "esource$anager+this.KetType+../ // 'ccess the $yString string from the "esource$anager // +!oth of these techni>ues are e>ui#alent for strings. string sB 1 +string.resman.KetJ!9ect+ $yString ./ string sG 1 resman.KetString+ $yString ./

The resource manager acts as a logical wrapper around a resource reader' e(posing the nested resources by name' as shown in #igure 10.10. Figure 16.16. $ogical =iew of the Wa! /esource8anager 7ses /esource/eader

0gain' because the naming scheme for embedded resources is somewhat obscured' #igure 10.11 shows a summary of how -5.!"T settings influence the names used with the @esource anager. Figure 16.11. /esource (aming and /esource8anager

1sing a resource manager directly' especially one associated with a specific type' is a useful thing to do' but somewhat labor/intensive. Win#orms Aesigner often ma$es such manual coding unnecessary.

Designer /esources
0lthough the resource manager code is lots friendlier to write than resource reader code' the lac$ of a decent editor for .res( files ma$es it difficult to use them for anything but string resources. !ot only do you have to write the code manually to bring the data in at run time' but also you don%t get to see what your resources will loo$ li$e at design time. That%s a problem for resources such as a form%s bac$ground image.

*n addition to all its other duties' Win#orms Aesigner provides support for associating resource data with ob<ects hosted on your custom forms and other component types. *f you open the -5.!"T 5olution "(plorer and press the 5how 0ll #iles button' you%ll see that each component type' whether it%s a form' a control' or a simple component' has a corresponding .res( file. This $eeps resources associated with properties of the component' as set in the 8roperty Browser. #or e(ample' if you set the Bac$ground*mage property of a form' not only will the form show the bac$ground image in the Aesigner' but also the form%s .res( file will contain an entry for that image. 5imilarly' if you set the *mage property of a 8ictureBo( control on the same form' the .res( file will have grown to include that resource as well. #igure 10.1> shows both of these entries.
LHM LHM

What ma$es a component type is discussed in detail in +hapter 9: Aesign/Time *ntegration.

Figure 16.12. 1 Component?s .res3 File

"ach component%s .res( file is compiled and embedded as a .resources file' <ust as if you%d added your own .res( file to your pro<ect' thereby ma$ing the resources available to the component at run time. *n addition to the entries in the component%s .res( file' the Aesigner adds the code to *nitiali=e+omponent to load a resource manager for the component. The Aesigner/added code populates the component%s properties using the ob<ects pulled from the resources:
names#ace 2eso/rces)## " #/&lic class Form5 : Form " ... $oi 6nitiali7eCom#onent() " ... ...
this.)ac6ground;mage 1 +System.0rawing.)itmap.resources.KetJ!9ect+ Othis.)ac6ground;mage ./

"esource$anager resources 1 new "esource$anager+typeof+FormB../ this.picture)o*B.;mage 1 +System.0rawing.)itmap.resources.KetJ!9ect+ picture)o*B.;mage ./

...

!otice that the @esource anager ob<ect is constructed using the type of the component' which is used to construct the .resources resource name for the component. !otice also the

naming convention used by the Aesigner to name resources. #or properties on fields of the component' the format of the name is as follows:
=,iel Bame>.=#ro#ertyBame>

#or properties on the component itself' the format of the name loo$s li$e this:
Sthis.=#ro#ertyBame>

*f you%d li$e to add custom string properties for use in an e(isting Aesigner/managed .res( file' you can' but ma$e sure to stay away from the format of the Aesigner/generated names:
Smine.=reso/rceBame>

By using a leading dollar sign or another character that%s illegal for use as a field name' and by avoiding the &`this& prefi( 3and the &VV& prefi(' which is used by locali=ed resources4' you%re li$ely to stay out of the way of the current Aesigner implementation while still allowing yourself per/component typed resources. 6owever' because the implementation of the Aesigner could change' adding your own .res( file to the pro<ect is the surest way of maintaining custom resources outside the influence of the Aesigner. Designer /esource +ssues 0s of this writing there are two main problems with resources as supported in -5.!"T. The first is that when you set a property from a file' the data from the file is copied directly into the .res( file' so changes to the original file are not automatically replicated in the resource. This is e(acerbated by the fact that after data from a file has been added to a .res( file' there is no way in -5.!"T to edit it. *nstead' to get updated data into a .res( file' you must use -5.!"T to remove the entry from -5.!"T' after which you must add it again manually. The second problem is that' e(cept for a single icon' +, pro<ects don%t support the addition of unmanaged resources. 0ll files mar$ed as "mbedded @esource or pulled into a .res( file are managed resources. This ma$es them unavailable for use by unmanaged components' such as those used in the +9 /based AirectT 08*. To support some interoperability scenarios' you%ll need to use the Cwin3>resource compiler command line switch' which can%t be used from within -5.!"T builds' or you must use a third/party tool that will add unmanaged resources after a build. icrosoft provides no tools' as of this writing' that provide this functionality.
LFM LFM

This chapter%s source code includes a tool from 8eter +hiu called ntcopyres.e(e that adds unmanaged resources to a managed assembly. *t was

obtained from http:CCwww.codeguru.comCcppQmfcCrsrc/simple.html.

Resource )ocali*ation

*n addition to simplified access to typed resources' the @esource anager class provides one other important feature: the ability to locali$e resources for your components without recompiling them. The act of locali$ation 3l10n 4 is a process of providing culture/specific information to display to a user in that culture. #or e(ample' a form has been locali=ed when it shows &9D& in "nglish but &7e 9D& in #rench 3or what ever the #rench actually say when they mean 9D4. The act of internationali$ation 3i18n4' on the other hand' is ta$ing advantage of locali=ed information. This could mean using locali=ed resources in the 1* or using code that formats currency or dates according to the current locale' as shown in #igure 10.13.
LSM LSM

The l10n abbreviation came from the need to spell out &locali=ation& so often that the middle 10 letters were replaced with the number 10.

5imilarly' &internationali=ation& is abbreviated i18n. *n this same spirit' * plan to switch from &abbreviation& to &a10n& any day now.

Figure 16.1 . $ocali-ed Currencies and Dates

Culture +nformation
* generated the currencies and dates in #igure 10.13 by enumerating all the cultures that .!"T $nows about 3centrali=ed in the 5ystem.Globali=ation namespace4 and using the information about each culture to provide formatting information:
using System.Klo!ali&ation/

... $oi

Form5%Loa (o&'ect sen er, ($ent)r*s e) " o/&le amo/nt 1 H.A@; 9ate0ime ate 1 9ate0ime.Bo!;
foreach+ Culture;nfo info in Culture;nfo.KetCultures+CultureTypes.'llCultures. . 4 ListCie!6tem item 1 listCie!5.6tems.) (info.EnglishName ); item.S/&6tems.) (info.Name );

if+ 2info.;sNeutralCulture . 4

5 5

item.S/&6tems.) item.S/&6tems.)

(amo/nt.0oStrin*("C", info.Num!erFormat )); ( ate.0oStrin*(" ", info.0ateTimeFormat ));

This code enumerates all $nown cultures' pulling out the name' the number formatting information' and the date formatting informationP the latter two are passed to the To5tring function to govern formatting. The intrinsic To5tring implementations format strings by using the culture stored in the +urrent+ulture property of the current thread 3available via 5ystem.Threading.Thread.+urrentThread4. The +urrent+ulture property on the 5ystem.Windows.#orms.0pplication class is <ust a wrapper around the +urrent+ulture property of the current thread' so either can be used to test your programs in alternative cultures:
static $oi Main() " o/&le amo/nt 1 H.A@;
// Show currency using default culture

Messa*e+ox.Sho!(amo/nt.0oStrin*("C"), 'pplication.CurrentCulture.EnglishName);
// Change current culture +one way. 'pplication.CurrentCulture 1 new Culture;nfo+ fr?C' ./ // Change current culture +another way. System.Threading.Thread.CurrentThread.CurrentCulture 1 new Culture;nfo+ fr?C' ./ // Show currency in current culture +Canadian French.

Messa*e+ox.Sho!(amo/nt.0oStrin*("C"), 'pplication.CurrentCulture.EnglishName); -

By default' the current culture is whatever users have set in their machines. +hanging it re)uires an instance of the +ulture*nfo ob<ect' which is most easily constructed with a culture name. 0 c&lt&re name is composed of uni)ue identifiers of a language and a country and is formatted this way:
=t!oLetterLan*/a*e6 >:=t!oLetterCo/ntry6 >

#or e(ample' 1.5. "nglish is &en/15'& and 0ustralian "nglish is &en/01.&

/esource Pro9ing
The @esource anager class was written with internationali=ation and locali=ation in mind. "ach new @esource anager uses the +urrent1*+ulture property of the current thread to determine which culture%s resources to load. When it%s created' the resource manager probes the file system for an assembly that contains the appropriate culture/specific
LIM

resources. Based on the namespace of the type it%s loaded with' the resource manager loo$s in 1S places for an assembly' either A77 or "T". #irst it loo$s for country/ and language/ specific resources' and then it falls bac$ on country/neutral' language/specific resources. 0ssuming a namespace of 7ocali=ed0pp' Table 10.1 shows the relative paths that the resource manager probes loo$ing for locali=ed resources.
LIM

The +urrent1*+ulture property is not to be confused with the +urrent+ulture property' which is used for a different purpose.

The assemblies that the resource manager is loo$ing for are $nown as satellite assemblies in that they%re separate assemblies that can be found near the location of the main assembl#' which is the assembly containing the code for the locali=ed form3s4. Ta9le 16.1. /esource 8anager Pro9ing for $ocali-ed /esources
"elati#e Pro!ed 'ssem!ly Name Country? and Canguage?Specific Pro!ing

1. en/15CLocalizedApp.resources.A77 >. en/15CLocalizedApp.resourcesCLocalizedApp.resources.A77 3. binCen/15CLocalizedApp.resources.A77 H. binCen/15CLocalizedApp.resourcesCLocalizedApp.resources.A77 F. en/15CLocalizedApp.resources."T" S. en/15CLocalizedApp.resourcesCLocalizedApp.resources."T" I. binCen/15CLocalizedApp.resources."T" 8. binCen/15CLocalizedApp.resourcesCLocalizedApp.resources."T"


Country?Neutral and Canguage?Specific Pro!ing

9. enCLocalizedApp.resources.A77 10. enCLocalizedApp.resourcesCLocalizedApp.resources.A77 11. binCenCLocalizedApp.resources.A77 1>. binCenCLocalizedApp.resourcesCLocalizedApp.resources.A77 13. enCLocalizedApp.resources."T" 1H. enCLocalizedApp.resourcesCLocalizedApp.resources."T" 1F. binCenCLocalizedApp.resources."T" 1S. binCenCLocalizedApp.resourcesC7ocali=ed0pp.resources."T"

The resources embedded in the main assembly get loaded only if no culture/specific resources are found. By default' these resources are c&lt&re(ne&tral in that they aren%t speciali=ed for any culture. To mar$ resources embedded with code as c&lt&re(specific' you can apply the !eutral@esources7anguage0ttribute attribute 3from the 5ystem.@esources namespace4 to the assembly as a whole. The following is an e(ample of mar$ing an assembly%s resources as country/ and language/specific:
L8M L8M

The wi=ard/generated 0ssembly*nfo source file is a handy place to put assembly/level attributes.

/sin* System.2eso/rces;
// $ar6 all resources in this assem!ly as I.S. English. // No pro!ing will !e done in the en?IS culture. %assem!ly3 Neutral"esourcesCanguage'ttri!ute+ en?IS .(

The following is an e(ample of mar$ing an assembly%s resources as country/neutral and language/specific:


/sin* System.2eso/rces;
// $ar6 all resources in this assem!ly as country?neutral English. // Pro!ing will !e done for country?specific resources !ut // will stop when country?neutral resources are needed. %assem!ly3 Neutral"esourcesCanguage'ttri!ute+ en .(

The reason to mar$ an assembly%s resources as culture/specific is to avoid the resource probing process for satellite assemblies when the main assembly code also contains the culture/specific resources.

/esource $ocali-ation
Whereas culture/specific resource assemblies are loaded at the namespace level' resources themselves are locali=ed at the form level. 0 form is locali=ed if the 7ocali=able property is set in the 8roperty Browser to true 3the default is false4. When the 7ocali=able property is false' a new form has no entries in the .res( file. 6owever' when the 7ocali=able property is set to true' the .res( file e(pands to hold >S entries' each corresponding to a property to be read from a locali=ed resource during e(ecution of the *nitiali=e+omponent method:
$oi 6nitiali7eCom#onent() "

"esource$anager resources 1 new "esource$anager+typeof+FormB../

.. Form5

this.'ccessi!le0escription 1 ++string.+resources.KetJ!9ect+ Othis.'ccessi!le0escription .../ this.'ccessi!leName 1 ++string.+resources.KetJ!9ect+ Othis.'ccessi!leName .../ this.'nchor 1 ++'nchorStyles.+resources.KetJ!9ect+ Othis.'nchor .../ this.'utoScale)aseSi&e 1 ++Si&e.+resources.KetJ!9ect+ Othis.'utoScale)aseSi&e .../

this.'utoScroll 1 ++!ool.+resources.KetJ!9ect+ Othis.'utoScroll .../ this.'utoScroll$argin 1 ++Si&e.+resources.KetJ!9ect+ Othis.'utoScroll$argin .../ this.'utoScroll$inSi&e 1 ++Si&e.+resources.KetJ!9ect+ Othis.'utoScroll$inSi&e .../ this.)ac6ground;mage 1 ++;mage.+resources.KetJ!9ect+ Othis.)ac6ground;mage .../ this.ClientSi&e 1 ++Si&e.+resources.KetJ!9ect+ Othis.ClientSi&e .../ this.0oc6 1 ++0oc6Style.+resources.KetJ!9ect+ Othis.0oc6 .../ this.Ena!led 1 ++!ool.+resources.KetJ!9ect+ Othis.Ena!led .../ this.Font 1 ++Font.+resources.KetJ!9ect+ Othis.Font .../ this.;con 1 ++;con.+resources.KetJ!9ect+ Othis.;con .../ this.;me$ode 1 ++;me$ode.+resources.KetJ!9ect+ Othis.;me$ode .../ this.Cocation 1 ++Point.+resources.KetJ!9ect+ Othis.Cocation .../ this.$a*imumSi&e 1 ++Si&e.+resources.KetJ!9ect+ Othis.$a*imumSi&e .../ this.$inimumSi&e 1 ++Si&e.+resources.KetJ!9ect+ Othis.$inimumSi&e .../

this.Bame 1 "Form5";

this."ightToCeft 1 ++"ightToCeft.+resources.KetJ!9ect+ Othis."ightToCeft .../ this.StartPosition 1 ++FormStartPosition.+resources.KetJ!9ect+ Othis.StartPosition .../ this.Te*t 1 resources.KetString+ Othis.Te*t ./ this.Lisi!le 1 ++!ool.+resources.KetJ!9ect+ Othis.Lisi!le .../

Uen re*ion

#or a locali=ed form' the *nitiali=e+omponent method chec$s satellite resources for any property that could be culture/specific. When a form has been set to locali=able' you can choose a culture from the 7anguage property in the 8roperty Browser' as shown in #igure 10.1H. Figure 16.1#. Choosing a Culture in the Propert! "rowser

#or each culture you choose' a corresponding .res( file containing culture/specific data will be associated with the form. #igure 10.1F shows a form in the 8roperty Browser after the developer has chosen to support several languages.some country/specific and others country/neutral. Figure 16.1'. *ne Form with $ocali-ation +nformation for ,e&eral Cultures

When the pro<ect is built' all of the form%s culture/specific resources are bundled together into a satellite assembly' one per culture' and placed into the appropriately named folder. The folders and satellite assemblies are named so that the resource manager' loo$ing for the culture/specific resources' can find the ones it%s loo$ing for:
Cocali&ed'pp.e*e enNLocali7e )##.reso/rces. ll en?C'NLocali7e )##.reso/rces. ll en?ISNLocali7e )##.reso/rces. ll frNLocali7e )##.reso/rces. ll fr?C'NLocali7e )##.reso/rces. ll

!otice that the main application is at the top level' containing the culture/neutral resources' and the culture/specific resource assemblies are in subfolders named after the culture. !otice also that -5.!"T has chosen the names of the subfolders and satellite assemblies that the resource manager will loo$ for first 3as shown in Table 10.14' saving probing time. The presence of a new satellite assembly in the file system in a place that the resource manager can find it is all that%s re)uired to locali=e an assembly%s form for a new culture. When a locali=ed form is loaded' the resource manager will find the new satellite assembly and will load the resources from it as appropriate' without the need to recompile the main assembly itself. This provides no(compile deplo#ment for locali=ed resources.

/esource $ocali-ation for (onde&elopers


-5.!"T is a handy tool for resource locali=ation' but it%s not something you want to force nondevelopers to use. 7uc$ily' after you set the 7ocali=able property to true for each locali=able form and rebuild your component' your user can locali=e a set of forms in an assembly without further use of -5.!"T.

To allow nondevelopers to locali=e resources' the .!"T #ramewor$ 5AD ships with a tool called Windows @esource 7ocali=ation "ditor 3winres.e(e4. To use it' you open a culture/ neutral .res( file for a locali=able form.that is' a form with the 7anguage property set to 3Aefault4. 0fter you%ve loaded the .res( file' you%re presented with a miniature version of the -5.!"T forms Aesigner' which you can use to set culture/specific resource information as shown in #igure 10.1S.
L9M L9M

Win@es and -5.!"T can share culture/neutral .res( files but not culture/specific .res( files' so it%s best to pic$ only one resource locali=ation

tool and stic$ with it.

Figure 16.1.. $ocali-ing a Form 7sing winres.e3e

Before you ma$e any changes' * recommend choosing #ile N 5ave 0s to choose a culture. The culture will be used to format a culture/specific name for the .res( file. #or e(ample' 7ocali=ed#orm1.res( will be saved as 7ocali=ed#orm1.en/15.res( for the 1.5. "nglish culture' <ust as -5.!"T does it. 0fter you save the culture/specific .res( file' ma$e the culture/specific changes and save again. !e(t' you create the set of culture/specific .res( files for an assembly' one per form' to use in creating a satellite assembly. To do that' you start by bundling them into a set of .resources files. Jou can do that using the resgen.e(e tool shown earlier. To e(ecute resgen.e(e on more than one .res( file at a time' use the Ccompile switch:
C:.> res*en.exe .com#ile Form5.en:<S.resx Form@.en:<S.resx ...

@unning resgen.e(e in this manner will produce multiple .resources files' one per .res( file. 0fter you%ve got the .resources files for all the locali=ed forms for a particular culture' you

can bundle them into a single resource assembly by using the assembly lin$er command line tool' al.e(e:
?4ie/ full /idth@

C:.> al.exe .o/t:en:<S\)##.reso/rces. ll .c/lt/re:en:<S .em&e reso/rce:Form5.en:<S .reso/rces,)##.Form5.en:<S.reso/rces .em&e reso/rce:Form@.en: <S.reso/rces,)##.Form@.en:<S .reso/rces ...

The assembly lin$er is a tool with all $inds of uses in .!"T. *n this case' we%re using it to bundle a number of .resources files into a single satellite assembly. The Cout argument determines the name of the produced assembly. a$e sure to pic$ one of the file names that the @esource anager will probe for 3as shown in Table 10.14. The Cculture argument determines the culture of the resource assembly and must match the culture name for the resources you%re building. The Cembedresource arguments provide the .resources files along with the alternative names to match the names that the resource manager will loo$ for. By default' al.e(e bundles each resource into a named container based on the file name. 6owever' to match what the resource manager is loo$ing for' you must use the alternative name synta( to prepend the resource namespace. 0gain' ildasm is a useful tool to ma$e sure that you have things right when it comes to building satellite resources. #igure 10.1I shows the result of running ildasm on the 0pp.resources.dll produced by the earlier call to al.e(e. Figure 16.10. ildasm ,howing a Culture),pecific /esource ,atellite 1ssem9l!

#igure 10.1I shows two locali=ed forms' one for each of the .resources files passed to the al.e(e file. *n addition' notice that the locale has been set to en/15 in the .assembly bloc$. This locale setting is reserved for resource/only satellite assemblies and is used by the resource manager to confirm that the loaded resources match the folder and assembly name used to find the satellite assembly.

/esource /esolution
When there are multiple resources that match the current culture' the resource manager must choose among them. #or e(ample' if an application is running under the en/15 culture' a resource with the same name can be present in an en/15 satellite assembly' in an en satellite assembly' and in the main assembly itself. When multiple assemblies can contain a resource' the resource manager loo$s first in the most specific assembly' that is' the culture/specific assembly. *f that%s not present' the language/specific assembly is chec$ed' and finally the culture/neutral resources. #or e(ample' imagine a form that has three resource/specific Te(t properties: one for a 7abel control' one for a Button control' and one for the #orm itself. *magine further that there are two satellite assemblies.one for en/15 and one for en.along with the neutral resources bundled into the form%s assembly itself. #igure 10.18 shows how the resource manager resolves the resources while running in the en/15 culture. Figure 16.12. The /esource 8anager?s /esource /esolution 1lgorithm

@emember that the resource manager always loo$s for the most specific resource it can find. 5o even though there are three instances of the button%s Te(t property' the most culture/specific resource in the en/15 assembly &overrides& the other two. 5imilarly' the language/specific resource for the label is pulled from the en assembly only because it%s not present in the en/15 assembly. #inally' the culture/neutral resource is pulled from the main assembly for the form%s Te(t property when it%s not found in the satellite assemblies. This resolution algorithm enables resources that are shared between all cultures to be set in the culture/neutral resources' leaving the culture/specific resources for overriding only the things that are culture/specific. 6owever' resolving resources in less culture/specific assemblies wor$s only when a resource is missing from the more culture/specific assembly. -5.!"T is smart about putting only properties that have changed into a more culture/specific assembly' but that is not the case with Win@es. Because of the way it wor$s' Win@es duplicates all the culture/ neutral resource information to the culture/specific resource files. This means that when using Win@es' all the resources will need to be locali=ed to a more specific culture' even if they aren%t changed from a less specific culture. Testing /esource /esolution To test that resource resolution is wor$ing the way you thin$ it should' you can manually set the +urrent1*+ulture property of the current thread:
static $oi Main() "

// Test locali&ed resources under fr?C' culture System.Threading.Thread.CurrentThread.CurrentI;Culture 1 new Culture;nfo+ fr?C' ./

)##lication.2/n(ne! MainForm()); -

0lthough the +urrent1*+ulture property defaults to the current culture setting of Windows itself' it can be changed. Whatever the value is when a resource manager is created will be the culture that the resource manager uses to resolve resources.

+nput $anguage
+losely related to a thread%s current culture is the inp&t lang&age to which the $eyboard is currently mapped' which determines which $eys map to which characters. 0s a further means of testing your application in alternative cultures' the Win#orms 0pplication ob<ect supports switchable input languages. The list of installed layouts is available from the *nput7anguage class%s *nstalled*nput7anguages property:
,oreach( 6n#/tLan*/a*e l in 6n#/tLan*/a*e.6nstalle 6n#/tLan*/a*es ) " Messa*e+ox.Sho!(l.Layo/tBame); -

Jou can change the current input language by setting one of the installed input languages to the *nput7anguage class%s property 3which is also wrapped by the 0pplication.+urrent*nput7anguage property4:
6n#/tLan*/a*e lan* 1 ...; .. Select an in#/t lan*/a*e
'pplication.Current;nputCanguage 1 lang/ // one way ;nputCanguage.Current;nputCanguage 1 lang/ // another way

The default system input language is available via the Aefault*nput7anguage property of the *nput7anguage class' should you need to reinstate it.

Where Are We?


@esources are a great way to bundle arbitrary data' both untyped 3in manifest resources4 and typed 3in .resources resources4. -5.!"T provides e(tensive support for embedding both $inds of resources' even providing them on a per/component basis. 0nd for forms that need to be locali=ed' -5.!"T handles that' too' although Win@es is a better choice when nondevelopers are doing the locali=ation.

Chapter 11. 1pplications and ,ettings


0pplications have special significance and support in Win#orms. #or e(ample' you can manage and tailor your application%s lifetime' even applications such as single/instance applications and multi/5A* applications. 0pplications also have an environment that can be tailored by the user and $ept between sessions in any of several mechanisms for storing settings' including environment variables' command line arguments' .config files' the @egistry' special folders' and isolated storage.

A""lications
0n application is anything with an "T" e(tension that can be started from the shell. 6owever' applications are also provided for directly in Win#orms by the 0pplication class from the 5ystem.Windows.#orms namespace:
seale class )##lication " .. Gro#erties #/&lic static &ool )llo![/it " *et; #/&lic static strin* Common)##9ataGath " *et; #/&lic static 2e*istryLey Common)##9ata2e*istry " *et; #/&lic static strin* Com#anyBame " *et; #/&lic static C/lt/re6n,o C/rrentC/lt/re " *et; set; #/&lic static 6n#/tLan*/a*e C/rrent6n#/tLan*/a*e " *et; set; #/&lic static strin* (xec/ta&leGath " *et; #/&lic static strin* Local<ser)##9ataGath " *et; #/&lic static &ool Messa*eLoo# " *et; #/&lic static strin* Gro /ctBame " *et; #/&lic #/&lic #/&lic #/&lic #/&lic static static static static static strin* Gro /ctCersion " *et; strin* Sa,e0o#Le$elCa#tionFormat " *et; set; strin* Start/#Gath " *et; strin* <ser)##9ataGath " *et; 2e*istryLey <ser)##9ata2e*istry " *et; e$ent e$ent e$ent e$ent ($entHan ler )##lication(xit; ($entHan ler 6 le; 0hrea (xce#tion($entHan ler 0hrea (xce#tion; ($entHan ler 0hrea (xit;

.. ($ents #/&lic static #/&lic static #/&lic static #/&lic static .. Metho s #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static #/&lic static

$oi ) Messa*eFilter(6Messa*eFilter $al/e); $oi 9o($ents(); $oi (xit(); $oi (xit0hrea (); )#artmentState Dle2e8/ire (); $oi Dn0hrea (xce#tion((xce#tion t); $oi 2emo$eMessa*eFilter(6Messa*eFilter $al/e); $oi 2/n(); $oi 2/n()##licationContext context);

#/&lic static $oi

2/n(Form mainForm);

!otice that all the members of the 0pplication class are static. 0lthough there is per/ application state in Win#orms' there is no instance of an 0pplication class. *nstead' the 0pplication class is a scoping mechanism for the various services that the class provides' including lifetime control' message handling' and settings.

1pplication $ifetime
0 Win#orms application starts when the ain method is called. 6owever' to initiali=e a Win#orms application fully and start it routing Win#orms events' you need a call to 0pplication.@un. There are three ways to call the 0pplication class%s @un method. The first is to simply call @un with no arguments at all. This is useful only if other means have already been used to show an initial 1*:
IS0)0hrea )ttri&/teJ static $oi Main() " .. Create an sho! the main ,orm mo elessly Form ,orm 1 ne! MainForm(); ,orm.Sho!();
// "un the application 'pplication."un+./

When you call @un with no arguments' the application runs until e(plicitly told to stop' even when all its forms are closed. This puts the burden on some part of the application to call the 0pplication class%s "(it method:
$oi
// Close the application when the main form goes away // Jnly for use when 'pplication."un is called without // any arguments 'pplication.E*it+./

MainForm%Close (o&'ect sen er, ($ent)r*s e) "

Typically' you call 0pplication.@un without any arguments only when the application needs a secondary 1* thread. 0 1, t"read is one that calls 0pplication.@un and can process the events that drive a Windows application. Because the vast ma<ority of applications contain a single 1* thread and because most of those have a main form that' when closed' causes the application to e(it' another overload of the @un method is used far more often. This overload of @un ta$es as an argument a reference to the form designated as the main form. When @un is called this way' it shows the main form and doesn%t return until the main form closes:
IS0)0hrea )ttri&/teJ static $oi Main() "
// Create the main form

Form form 1 new $ainForm+./ // "un the application until the main form is closed 'pplication."un+form./

*n this case' there is no need for e(plicit code to e(it the application. *nstead' the 0pplication watches for the main form to close and then e(its itself.

1pplication Conte3t
*nternally' the @un method creates an instance of the 0pplication+onte(t class from the 5ystem.Windows.#orms namespace. *t%s this class that subscribes to the main form%s +losed event and e(its the application as appropriate:
class )##licationContext " .. Constr/ctors #/&lic )##licationContext(); #/&lic )##licationContext(Form mainForm); .. Gro#erties #/&lic Form MainForm " *et; set; .. ($ents #/&lic e$ent ($entHan ler 0hrea (xit; .. Metho s #/&lic $oi (xit0hrea (); #rotecte $irt/al $oi DnMainFormClose (o&'ect sen er, ($ent)r*s e); -

*n fact' the @un method allows you to pass an 0pplication+onte(t yourself:


IS0)0hrea )ttri&/teJ static $oi Main() "

// "un the application with a conte*t 'pplicationConte*t ct* 1 new 'pplicationConte*t+new $ainForm+../ 'pplication."un+ct*./

This is useful if you%d li$e to derive from the 0pplication+onte(t class and provide your own custom conte(t:
class My0ime Context : 'pplicationConte*t " 0imer timer 1 ne! 0imer(); #/&lic My0ime Context(Form ,orm) : &ase(,orm) " timer.0ic4 ;1 ne! ($entHan ler(0imes<#); timer.6nter$al 1 A???; .. A min/tes timer.(na&le 1 tr/e; $oi 0imes<#(o&'ect sen er, ($ent)r*s e) " timer.(na&le 1 ,alse;

timer.9is#ose(); 9ialo*2es/lt res 1 Messa*e+ox.Sho!( "DL to char*e yo/r cre it car >", "0imeFs <#3", Messa*e+ox+/ttons.MesBo); i,( res 11 9ialo*2es/lt.Bo ) "
// See ya... !ase.$ainForm.Close+./

... -

IS0)0hrea )ttri&/teJ static $oi Main() "

// "un the application with a custom conte*t 'pplicationConte*t ct* 1 new $yTimedConte*t+new $ainForm+../ 'pplication."un+ct*./

This custom conte(t class waits for five minutes after an application has started and then as$s to charge the user%s credit card. *f the answer is no' the main form of the application will be closed 3available from the ain#orm property of the base 0pplication+onte(t class4' causing the application to e(it. +onversely' if you%d li$e to stop the application from e(iting when the main form goes away' you can override the 9n ain#orm+losed method from the 0pplication+onte(t base class:
class 2emotin*Ser$erContext : 'pplicationConte*t " #/&lic 2emotin*Ser$erContext(Form ,orm) : &ase(,orm) " protected o#erride #oid Jn$ainFormClosed+o!9ect sender- E#ent'rgs e. 4

.. 9onFt let &ase class exit a##lication .. BD0(: 2emem&er to call )##lication.(xit .. later !hen the remotin* ser$ice .. is ,inishe ser$icin* its clients i,( Ser$icin*2emotin*Client() ) ret/rn;
// Cet !ase class e*it application !ase.Jn$ainFormClosed+sender- e./ 5

#rotecte -

&ool Ser$icin*2emotin*Client() "...-

This e(ample assumes an application that is serving .!"T @emoting clients and so needs to stic$ around even if the user has closed the main form.
L1M

L1M

!"T @emoting is a technology that allows ob<ects to tal$ to each other across application and machine boundaries. @emoting is beyond the

scope of this boo$ but is covered very nicely in *ngo @ammer%s boo$

Advanced .NET Remoting 308ress >00>4.

1pplication %&ents
Auring the lifetime of an application' several application events will be fired: idle' thread e(it' application e(it' and sometimes a thread e(ception. Jou can subscribe to application events at any time' but it%s most common to do it in the ain function:
static $oi static $oi static $oi )##%(xit(o&'ect sen er, ($ent)r*s e) "...)##%6 le(o&'ect sen er, ($ent)r*s e) "...)##%0hrea (xit(o&'ect sen er, ($ent)r*s e) "...-

IS0)0hrea )ttri&/teJ static $oi Main() "

'pplication.;dle 71 new E#ent,andler+'pp8;dle./ 'pplication.ThreadE*it 71 new E#ent,andler+'pp8ThreadE*it./ 'pplication.'pplicationE*it 71 new E#ent,andler+'pp8E*it./

...

The idle event happens when all events in a series of events have been dispatched to event handlers and no more events are waiting to be processed. The idle event can sometimes be used to perform concurrent processing in tiny chun$s' but it%s much more convenient and robust to use wor$er threads for those $inds of activities. This techni)ue is covered in +hapter 1H: ultithreaded 1ser *nterfaces. When a 1* thread is about to e(it' it receives a notification via the thread e(it event. When the last 1* thread goes away' the application%s e(it event is fired.

7+ Thread %3ceptions
9ne other application/level event that can be handled is a thread e(ception event. This event is fired when a 1* thread causes an e(ception to be thrown. This one is so important that Win#orms provides a default handler if you don%t. The typical .!"T unhandled e(ception on a user%s machine behavior yields a dialog bo( as shown in #igure 11.1. Figure 11.1. Default .(%T 7nhandled)%3ception Dialog "o3

This $ind of e(ception handling tends to ma$e the user unhappy. This dialog is confusing' and worse' there is no way to continue the application to attempt to save the data being wor$ed on at the moment. 9n the other hand' by default' a Win#orms application that e(periences an e(ception during the processing of an event shows a dialog li$e that in #igure 11.>. Figure 11.2. Default WinForms 7nhandled)%3ception Dialog "o3

0lthough this dialog may loo$ functionally the same as the one in #igure 11.1' there is one ma<or difference: The Win#orms version has a +ontinue button. What%s happening is that Win#orms itself catches e(ceptions thrown by event handlersP in this way' even if that event handler caused an e(ception.for e(ample' if a file couldn%t be opened or there was a security violation.the user is allowed to continue running the application with the hope that saving will wor$' even if nothing else does. This is a safety net that ma$es Win#orms applications more robust in the face of even unhandled e(ceptions than Windows applications of old. 6owever' if a user has triggered an e(ception and it%s caught' the application could be in an inconsistent state' so it%s best to encourage your users to save their files and restart the application. *f you%d li$e to replace the Win#orms unhandled/e(ception dialog with something application/specific' you can do so by handling the application%s thread e(ception event:
/sin* System.0hrea in*;
static #oid 'pp8ThreadE*ception+o!9ect sender- ThreadE*ceptionE#ent'rgs e. 4

strin* ms* 1 ") #ro&lem has occ/rre

in this a##lication:\r\n" ;

"\t" ; e.E*ception.$essage ; "\r\n\r\n" ; "Wo/l yo/ li4e to contin/e the a##lication so that\r\n" ; "yo/ can sa$e yo/r !or4>"; 9ialo*2es/lt res 1 Messa*e+ox.Sho!( ms*, "<nex#ecte (rror", Messa*e+ox+/ttons.MesBo); ...
5

IS0)0hrea )ttri&/teJ static $oi Main() "


// ,andle unhandled thread e*ceptions 'pplication.ThreadE*ception 71 new ThreadE*ceptionE#ent,andler+'pp8ThreadE*ception./

.. 2/n the a##lication )##lication.2/n(ne! MainForm()); -

!otice that the thread e(ception handler ta$es a Thread"(ception"vent ob<ect' which includes the e(ception that was thrown. This is handy if you want to tell the user what happened' as shown in #igure 11.3. Figure 11. . Custom 7nhandled)%3ception Dialog

*f you provide a thread e(ception handler' the default e(ception handler will not be used' so it%s up to you to let the user $now that something bad has happened. *f the user decides not to continue with the application' calling 0pplication."(it will shut down the application:
static $oi )##%0hrea (xce#tion( o&'ect sen er, 0hrea (xce#tion($ent)r*s e) " strin* ms* 1 ... 9ialo*2es/lt res 1 Messa*e+ox.Sho!(ms*, "<nex#ecte
// "eturning continues the application if+ res 11 0ialog"esult.Res . return/ // Shut :er down- Clancy- she:s a:pumpin: mud2 'pplication.E*it+./

(rror", Messa*e+ox+/ttons.MesBo);

,ingle)+nstance 1pplications
By default' each "T" is an application and has an independent lifetime' even if multiple instances of the same application are running at the same time. 6owever' it%s common to want to limit an "T" to a single instance' whether it%s a 5ingle Aocument *nterface 35A*4 application with a single top/level window' a ulti/Aocument *nterface 3 A*4 application' or an 5A* application with multiple top/level windows. 0ll these $inds of applications re)uire that another instance detect the initial instance and then cut its own lifetime short. Jou can do this using an instance of the ute( class from the 5ystem.Threading namespace:
L>M L>M

This method of detecting single instances will stop multiple instances across des$tops and sessions. #or a detailed e(planation of other

definitions of &single instance'& read Eoseph !ewcomer%s treatment of this topic at http:CCwww.pgh.netC\newcomerCnomultiples.htm.

/sin* System.0hrea in*; ... static $oi Main() " .. Chec4 ,or existin* instance

!ool first;nstance 1 false/ string safeName 1 'pplication.Iser'pp0ataPath."eplace+M N - 8 ./ $ute* mute* 1 new $ute*+true- safeName- out first;nstance./ if+ 2first;nstance . return/

)##lication.2/n(ne! MainForm());

This code relies on a named !ernel ob2ect' that is' an ob<ect that is managed by the Windows $ernel. The fact that it%s a mute( doesn%t really matter. What matters is that we create a $ernel ob<ect with a systemwide uni)ue name and that we can tell whether an ob<ect with that same name already e(ists. When the first instance is e(ecuted' that $ernel ob<ect won%t e(ist' and we won%t return from ain until 0pplication.@un returns. When another instance is e(ecuted' the $ernel ob<ect will already e(ist' so ain will e(it before the application is run. 9ne interesting note on the name of the mute( is worth mentioning. To ma$e sure we have a uni)ue name for the mute(' we need something specific to the version of the application but also specific to the user. *t%s important to pic$ a string that%s uni)ue per application so that multiple applications don%t prevent each other from starting. *f there are multiple users' it%s e)ually important that each user get his or her own instance' especially in the face of Windows T8' fast user switching' and terminal services. Toward that end' we use the 1ser0ppAata8ath property of the 0pplication ob<ect. *t%s a path in the file system where per/user settings for an application are meant to be stored' and it ta$es the following form:
L3M L3M

6owever' this code doesn%t ta$e into account the possibility that the same user may be logged in to the same machine in two different sessions.

C:\9oc/ments an Settin*s\csells\)##lication 9ata\Single;nstanceNSingle;nstanceNB.A.BBGB.@YYBB

What ma$es this string useful is that it contains the application name' the version number' and the user name.all the things we need to ma$e the mute( uni)ue per version of an application and per user running the application. 0lso' because the bac$ slashes are illegal in mute( names' those must be replaced with something else 3such as underscores4.

Passing Command $ine 1rguments


This single/instance scheme wor$s fine until the first instance of the application needs to get the command line arguments from any subse)uent instance. #or e(ample' if the first instance of an A* application needs to open the file passed to the other instance of the A* application' the other instance needs to be able to communicate with the initial instance. The easiest solution to this problem is to use .!"T @emoting and threading:
/sin* System.0hrea in*;
using System."untime."emoting/ using System."untime."emoting.Channels/ using System."untime."emoting.Channels.Tcp/ ... // $a6e main form accessi!le from other instance e#ent handler static $ainForm mainForm 1 new $ainForm+./ // Signature of method to call when other instance is detected delegate #oid Jther;nstanceCall!ac6+string%( args./

IS0)0hrea )ttri&/teJ static $oi Main(strin*IJ ar*s) " .. Chec4 ,or existin* instance &ool ,irst6nstance 1 ,alse; strin* sa,eBame 1 )##lication.<ser)##9ataGath.2e#lace(V"\", "%"); M/tex m/tex 1 ne! M/tex(tr/e, sa,eBame, o/t ,irst6nstance);
if+ 2first;nstance . 4 // Jpen remoting channel e*posed from initial instance // NJTE3 port +B@B@. and channel name +mainForm. must match !elow string formIrl 1 tcp3//localhost3B@B@/mainForm / $ainForm other$ainForm 1 +$ainForm."emotingSer#ices.Connect+typeof+$ainForm.- formIrl./ // Send arguments to initial instance and e*it this one other$ainForm.JnJther;nstance+args./ return/ 5 // E*pose remoting channel to accept arguments from other instances // NJTE3 port +B@B@. and channel name +mainForm. must match a!o#e ChannelSer#ices."egisterChannel+new TcpChannel+B@B@../ "emotingSer#ices.$arshal+mainForm- mainForm ./

.. D#en ,ile ,rom comman line i,( ar*s.Len*th 11 5 ) mainForm.D#enFile(ar*sI?J); .. Sho! main ,orm )##lication.2/n(mainForm);

pu!lic #oid JnJther;nstance+string%( args. 4...5

The details of .!"T @emoting are beyond the scope of this boo$' and threading isn%t covered until +hapter 1H: ultithreaded 1ser *nterfaces' but the details are less important than the concepts. Basically what%s happening is that the first instance of the application opens a named communication channel 3main#orm4 on a well/$nown' uni)ue port 313134 in case another instance of the application ever comes along. *f one does' the other instance opens the same channel and retrieves the ain#orm type from it so that it can call the 9n9ther*nstance method. #or this to wor$' the .!"T @emoting assembly' 5ystem.@untime.@emoting' must be referenced in the pro<ect. The @emoting infrastructure then re)uires that the type being retrieved by the other instance from the first instance be mars"al(b#(reference. This means that it can be called from another application domain' which is basically the .!"T e)uivalent of a process. Because the ain#orm class' along with all 1* classes in .!"T' derives from the arshalBy@ef base class' this condition is met. 0nother .!"T @emoting re)uirement is that the methods called on the marshal/by/ref type called from another app domain be instance and public' and that%s why the 9n9ther*nstance method is defined as it is:
// Called #ia remoting channel from other instances // NJTE3 This is a mem!er of the $ainForm class pu!lic #oid JnJther;nstance+string%( args. 4

.. 0ransition to the <6 threa i,( mainForm.6n$o4e2e8/ire ) " Dther6nstanceCall&ac4 call&ac4 1 ne! Dther6nstanceCall&ac4(DnDther6nstance); mainForm.6n$o4e(call&ac4, ne! o&'ectIJ " ar*s -); ret/rn; .. D#en ,ile ,rom comman line i,( ar*s.Len*th 11 5 ) this.D#enFile(ar*sI?J); .. +rin* !in o! to the ,ront mainForm.)cti$ate();

When 9n9ther*nstance is called' it will be called on a non/1* thread. This re)uires a transition to the 1* thread before any methods on the ain#orm are called that access the underlying window 3perhaps to create an A* child to show the file being opened or to activate the window4. That%s why we include the chec$ on the *nvo$e@e)uired re)uired property and the call to Begin*nvo$e.
LHM LHM

0ll these threading details are discussed in +hapter 1H:

ultithreaded 1ser *nterfaces.

#inally' 9n9ther*nstance does what it li$es with the command line arguments and then activates the main form to bring it to the foreground. The details of this are somewhat complicated for the service that they provide' and this ma$es it a good candidate for encapsulation. The only real variables that can%t be handled automatically are which method to call and what the main form is. This means that we can boil down the code to use the *nitial*nstance0ctivator class provided in the sample code included with this boo$:

.. Ma4e main ,orm accessi&le ,rom other instance e$ent han ler static MainForm mainForm 1 ne! MainForm(); IS0)0hrea )ttri&/teJ static $oi Main(strin*IJ ar*s) "

// Chec6 for initial instance Jther;nstanceCall!ac6 call!ac6 1 new Jther;nstanceCall!ac6+JnJther;nstance./ if+ ;nitial;nstance'cti#ator.'cti#ate+mainForm- call!ac6- args. . 4 return/ 5

.. D#en ,ile ,rom comman line i,( ar*s.Len*th 11 5 ) mainForm.D#enFile(ar*sI?J); .. Sho! main ,orm )##lication.2/n(mainForm);

// Called from other instances static #oid JnJther;nstance+string%( args. 4

.. D#en ,ile ,rom comman line i,( ar*s.Len*th 11 5 ) mainForm.D#enFile(ar*sI?J); .. )cti$ate the main !in o! mainForm.)cti$ate();

*nitial*nstance0ctivator ta$es three things: a reference to the main form' a delegate indicating which method to call when another instance is detected' and the arguments from ain in case this is another instance. *f the 0ctivate method returns true' it means that this is another instance and the arguments have been passed to the initial instance. The application bails' safe in the $nowledge that the initial instance has things well in hand. When another instance activates the initial instance' the delegate is invo$ed' letting the initial instance handle the command line arguments and activate itself as appropriate. The underlying communication and threading re)uirements are handled by *nitial*nstance0ctivator using other parts of .!"T that have nothing whatever to do with Win#orms. This is one of the strengths of Win#orms. 1nli$e forms pac$ages of old' Win#orms is only one part of a much larger' integrated whole. When its windowing classes don%t meet your needs' you%ve still got all the rest of the .!"T #ramewor$ +lass 7ibrary to fall bac$ on.

8ulti),D+ 1pplications
0 m&lti(SD, application is li$e an A* application in that it has multiple windows for content' but' unli$e an A* application' each window in a multi/5A* app is a top/level window. The *nternet "(plorer and 9ffice T8 applications are popular e(amples of multi/ 5A* applications. #igure 11.H shows a multi/5A* sample.
LFM LFM

*nternet "(plorer can be configured to show each top/level window in its own process' ma$ing it an 5A* application' or to share all windows in

a single process' ma$ing it a multi/5A* application.

Figure 11.#. 1 ,ample 8ulti),D+ 1pplication

0 multi/5A* application typically has the following features:


0 single instance of the application is running. ultiple top/level windows are running independently of each other. When the last window goes away' so does the application. 0 Window menu allows a user to see and select from the currently available windows.

The single/instance stuff we%ve already got lic$ed with the *nitial*nstance0ctivator class. 6aving multiple top/level windows running independently of each other is a matter of using the modeless #orm.5how method:
class 0o#Le$elForm : Form " ...
#oid fileNewWindow$enu;tem8Clic6+o!9ect sender- E#ent'rgs e. 4 NewWindow+null./ 5

static $oi

// Create another top?le#el form TopCe#elForm form 1 new TopCe#elForm+./

Be!Win o!(strin* ,ileBame) "

i,( ,ileBame 31 n/ll ) ,orm.D#enFile(,ileBame);


form.Show+./

Because the default application conte(t depends on there being only one main window' managing the lifetime of a multi/5A* application re)uires a custom application conte(t. 9ne simple way to leverage the 0pplication+onte(t base class is to derive from it' swapping the &main& form in our multi/5A* application until there are no more top/level windows left in the application' thereby causing the application to shut down:
#/&lic class M/ltiS i)##licationContext : )##licationContext " #/&lic $oi ) 0o#Le$elForm(Form ,orm) " .. 6nitial main ,orm may a itsel, t!ice, &/t thatFs DL i,( to#Le$elForms.Contains(,orm) ) ret/rn; .. ) ,orm to collection o, ,orms an .. !atch ,or it to acti$ate an close to#Le$elForms.) (,orm); ,orm.)cti$ate ;1 ne! ($entHan ler(Form%)cti$ate ); ,orm.Close ;1 ne! ($entHan ler(Form%Close ); .. Set initial main ,orm to acti$ate i,( to#Le$elForms.Co/nt 11 5 ) &ase.MainForm 1 ,orm; $oi Form%)cti$ate (o&'ect sen er, ($ent)r*s e) " .. Whiche$er ,orm acti$ate last is the "main" ,orm &ase.MainForm 1 (Form)sen er; $oi Form%Close (o&'ect sen er, ($ent)r*s e) " .. 2emo$e ,orm ,rom the list to#Le$elForms.2emo$e(sen er); .. Set a ne! "main" i, necessary i,( ((Form)sen er 11 &ase.MainForm) ZZ (this.to#Le$elForms.Co/nt > ?) ) " this.MainForm 1 (Form)to#Le$elFormsI?J; ... #/&lic FormIJ 0o#Le$elForms " .. (x#ose list o, to#:le$el ,orms ,or &/il in* Win o! men/ *et " ret/rn (FormIJ)to#Le$elForms.0o)rray(ty#eo,(Form)); )rrayList to#Le$elForms 1 ne! )rrayList();

The ulti5di0pplication+onte(t class uses the 0ddTop7evel#orm method to $eep trac$ of a list of top/level forms as they are added. "ach new form is $ept in a collection and is watched for 0ctivated and +losed events. When a top/level form is activated' it becomes the new &main& form' which is the one that the base 0pplication+onte(t class will watch for the +losed event. When a top/level form closes' it%s removed from the list. *f the closed form was the main form' another form is promoted to main. When the last form goes away' the base 0pplication+onte(t class notices and e(its the application.

With this basic functionality in place' we can use the application conte(t in argument to 0pplication.@un:
// Need application conte*t to manage top?le#el forms static $ultiSdi'pplicationConte*t conte*t 1 new $ultiSdi'pplicationConte*t+./

ain as the

IS0)0hrea )ttri&/teJ static $oi Main(strin*IJ ar*s) " .. ) initial ,orm 0o#Le$elForm initialForm 1 ne! 0o#Le$elForm(); context.) 0o#Le$elForm(initialForm); .. Let initial instance sho! another to#:le$el ,orm (i, necessary) Dther6nstanceCall&ac4 call&ac4 1 ne! Dther6nstanceCall&ac4(DnDther6nstance); i,( 6nitial6nstance)cti$ator.)cti$ate(context, call&ac4, ar*s) ) " ret/rn; .. D#en ,ile ,rom comman line i,( ar*s.Len*th 11 5 ) initialForm.D#enFile(ar*sI?J);
// "un application 'pplication."un+conte*t./

Because we%re using the application conte(t instead of the initial form as the argument to 0pplication.@un' it will be used to control the lifetime of the application' even as the &main& form cycles. 5imilarly' we%re using a conte(t to the 0ctivate method of the *nitial*nstance0ctivator helper class' and this means that if another instance of the application starts' the activator can as$ the conte(t for the &current& main form to use in transitioning to the 1* thread' even if the initial form has been closed. To $eep the conte(t up/to/date with the current list of top/level forms' the custom conte(t watches for the +losed event on all forms. *n addition' the custom conte(t needs to be notified when a new top/level form has come into e(istence' a tas$ that is best handled by the new form itself:
#/&lic 0o#Le$elForm() " .. 2e8/ire ,or Win o!s Form 9esi*ner s/##ort 6nitiali7eCom#onent();
// 'dd new top?le#el form to the application conte*t conte*t.'ddTopCe#elForm+this./

The only thing left to do is to designate and populate the Window menu with the current list of top/level forms. The forms themselves can do this by handling the pop/up event on the Window enu*tem ob<ect' using that opportunity to build the list of submenu items based on the names of all the forms 3as e(posed via the Top7evel#orms property of the ulti5di0pplication+onte(t helper ob<ect4. 6owever' this code is pretty boilerplate' so it%s

a good candidate to be handled by the custom application conte(t in the 0ddWindow enu method:
#/&lic class M/ltiM i)##licationContext : )##licationContext " ...
pu!lic #oid 'ddWindow$enu+$enu;tem menu. 4

.. ) at least one /mmy men/ item to *et #o#:/# e$ent i,( men/.Men/6tems.Co/nt 11 ? ) men/.Men/6tems.) (" /mmy");
// Su!scri!e to pop?up e#ent menu.Popup 71 new E#ent,andler+Window$enu8Popup./ 5

... -

"ach top/level form with a Window menu can add it to the conte(t' along with itself' when it%s created:
#/&lic 0o#Le$elForm() " .. 2e8/ire ,or Win o!s Form 9esi*ner s/##ort 6nitiali7eCom#onent(); .. ) ne! to#:le$el ,orm to the a##lication context context.) 0o#Le$elForm(this);
// 'dd Window $enu;tem to the application conte*t conte*t.'ddWindow$enu+this.window$enu./

!ow' when the Window menu is shown on any top/level window' the pop/up event fires and a new menu is built on/the/fly to show the current list of top/level menus:
.. C/rrent Win o! men/ items an the ma# to the a##ro#riate ,orm Hashta&le !in o!Men/Ma# 1 ne! Hashta&le(); $oi Win o!Men/%Go#/#(o&'ect sen er, ($ent)r*s e) " .. +/il men/ ,rom list o, to#:le$el !in o!s Men/6tem men/ 1 (Men/6tem)sen er; men/.Men/6tems.Clear(); !in o!Men/Ma#.Clear(); ,oreach( Form ,orm in this.to#Le$elForms ) " Men/6tem item 1 men/.Men/6tems.) (,orm.0ext); item.Clic4 ;1 ne! ($entHan ler(Win o!Men/6tem%Clic4); .. Chec4 c/rrently acti$e !in o! i,( ,orm 11 Form.)cti$eForm ) item.Chec4e 1 tr/e;

.. )ssociate each men/ item &ac4 to the ,orm !in o!Men/Ma#.) (item, ,orm);

0s each menu item is added to the Window menu' a handler is added to the +lic$ event so that the appropriate form can be activated when it%s selected. Because a enu*tem doesn%t have a Tag property' we%re using a 6ashtable collection to map menu items to each form. The hash table is used in the +lic$ handler to find the form that corresponds to the selected menu item:
$oi Win o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " .. )cti$ate to#:le$el ,orm &ase on selection ((Form)!in o!Men/Ma#Isen erJ).)cti$ate(); -

That%s it. The e(tensible lifetime management of Win#orms applications via a custom application conte(t' along with a helper to find and activate application instances already running' provides all the help we need to build a multi/5A* application in only a few lines of code.

!nvironment
Auring its lifetime' an application runs in a certain environment. The environment is provided by a combination of compile/time and run/time settings supplied by .!"T and Windows.

Compile)Time ,ettings
The 0pplication class e(poses several properties that provide the company name' product name' and product version of the currently running application:
$oi )&o/t+ox%Loa (o&'ect sen er, ($ent)r*s e) " this.com#anyBame0ext+ox.0ext 1 'pplication.CompanyName/ this.#ro /ctBame0ext+ox.0ext 1 'pplication.ProductName/ this.#ro /ctCersion0ext+ox.0ext 1 'pplication.ProductLersion/ -

By default' these three values come from assemblywide 0ssembly+ompany0ttribute' 0ssembly8roduct0ttribute' and 0ssembly-ersion0ttribute 3provided in a wi=ard/ generated file called 0ssembly*nfo.cs4:
Iassem&ly: )ssem&lyCom#any)ttri&/te("Sells +rothers, 6nc.")J Iassem&ly: )ssem&lyGro /ct)ttri&/te("(n$ironment 0est )##lication")J .. Cersion in,o ,or an assem&ly consists o, the ,ollo!in* $al/es: .. ma'or.minor.&/il .re$ision .. Mo/ can s#eci,y all the $al/es, or yo/ can e,a/lt the 2e$ision an .. +/il B/m&ers &y /sin* the FQF as sho!n &elo!: Iassem&ly: )ssem&lyCersion)ttri&/te("5.?.Q")J

!ot only will the values you put into these attributes be available using the 0pplication properties' but they%ll also be bundled into the Win3> version information for the assembly' as shown by the -ersion property page in "(plorer in #igure 11.F.

Figure 11.'. 1ssem9l! Properties ,hown in the ,hell

The rest of the version information is set via other assembly/level attributes:
Iassem&ly: Iassem&ly: Iassem&ly: Iassem&ly: Iassem&ly: )ssem&ly0itle("(n$ironment 0est 0itle")J )ssem&ly9escri#tion("(n$ironment 0est 9escri#tion")J )ssem&lyCom#any("Sells +rothers, 6nc.")J )ssem&lyGro /ct("(n$ironment 0est )##lication")J )ssem&lyCo#yri*ht("Co#yri*ht (c) @??N, Chris Sells")J

Iassem&ly: )ssem&ly0ra emar4("0ra emar4 no&o y")J Iassem&ly: )ssem&lyCersion("5.?.Q")J

%n&ironment ,ettings
*f you%d li$e to $now where your application was started from in the file system or which folder it was run from' you can get that information from the "(ecutable8ath and 5tartup8ath properties of the 0pplication ob<ect. Table 11.1 shows e(amples of each.

*f you want more environment settings' such as the environment variables or the command line string' you can get them from the "nvironment ob<ect in the 5ystem namespace:
seale class (n$ironment " .. Gro#erties
pu!lic static string CommandCine 4 get/ 5 pu!lic static string Current0irectory 4 get/ set/ 5

#/&lic static int (xitCo e " *et; set; #/&lic &ool HasSh/t o!nStarte " *et; pu!lic static string $achineName 4 get/ 5

#/&lic static strin* Be!Line " *et; pu!lic static JperatingSystem JSLersion 4 get/ 5

#/&lic #/&lic #/&lic #/&lic #/&lic

static static static static static

strin* Stac40race " *et; strin* System9irectory " *et; int 0ic4Co/nt " *et; strin* <ser9omainBame " *et; &ool <ser6nteracti$e " *et; -

pu!lic static string IserName 4 get/ 5

#/&lic static Cersion Cersion " *et; #/&lic static lon* Wor4in*Set " *et; .. Metho s #/&lic static $oi (xit(int exitCo e); #/&lic static strin* (x#an (n$ironmentCaria&les(strin* name);
pu!lic static string%( KetCommandCine'rgs+./ pu!lic static string KetEn#ironmentLaria!le+string #aria!le./ pu!lic static string KetFolderPath+SpecialFolder folder./

#/&lic static 69ictionary Eet(n$ironmentCaria&les(); #/&lic static strin*IJ EetLo*ical9ri$es(); -

Ta9le 11.1. The 1pplication %3ecuta9lePath and ,tartupPath Properties


'pplication Class Static Property Sample Property Lalue

"(ecutable8ath 5tartup8ath

A:OdataOWin#orms Boo$OsrcOch11O5ettingsObinOAebugO5ettingsTest.e(e A:OdataOWin#orms Boo$OsrcOch11O5ettingsObinOAebug

0s shown earlier' the command line arguments are also available as the array of strings argument passed to ain:
IS0)0hrea )ttri&/teJ static $oi Main(string%( args) " &ool ,la* 1 ,alse; strin* name 1 ""; int n/m&er 1 ?;
// QLeryQ simple command line parsing for+ int i 1 A/ i 21 args.Cength/ 77i . 4 switch+ args%i( . 4

case ".,la*": ,la* 1 tr/e; &rea4; case ".name": name 1 ar*sI;;iJ; &rea4; case ".n/m&er": n/m&er 1 int.Garse(ar*sI;;iJ); &rea4;

5 5

e,a/lt: Messa*e+ox.Sho!("in$ali

ar*s3"); ret/rn;

Messa*e+ox.Sho!(,la*.0oStrin*(), ",la*"); Messa*e+ox.Sho!(name, "name"); Messa*e+ox.Sho!(n/m&er.0oStrin*(), "n/m&er"); ...

*f you want to see more robust command line parsing support' see the Genghis class library available at http:CCwww.genghisgroup.com.

Settings
"nvironment variables and command line arguments are both ways for the user to specify run/time settings to a particular application. .!"T provides several more ways' including 95 favorites li$e the @egistry and special folders' as well as new ways li$e .config files and isolated storage.

T!pes of ,ettings
When saving settings' you should consider several different localities of settings:

'pplication .

These settings are shared among all users of an application on the machine. #or e(ample' the list of directories in which to search for the assemblies to show in the 0dd @eference dialog is a per/application setting.
LSM LSM

This setting is stored at 6D7 O59#TW0@"O icrosoftO.!"T#ramewor$O0ssembly#olders in the @egistry.

Iser.

These settings are specific to an application and a user. #or e(ample' inesweeper high scores are $ept per user. "oaming Iser. 0pplication settings as well as user settings are specific to a machine' but roaming user settings are machine/independent. #or e(ample' if inesweeper high scores were roaming' they%d be available to a specific user no matter what computer the user had logged in to. @oaming user settings are good for things that don%t depend on a machine' li$e a list of color preferences' but not for things that are related to current machine settings' li$e a window location. The use of roaming user settings presupposes that the machine is properly configured to support roaming. 9therwise' roaming user settings are e)uivalent to nonroaming user settings.
LIM LIM

@oaming user settings do depend on very specific Windows domain networ$ settings% being enabled.

$achine .

These are application/independent settings' such as the current screen resolution or the 80T6 environment variable 3although the 80T6 has a user portion4.

'ssem!ly.

These settings are available on a per/assembly basis so that components can have their own versioned settings. 0omain . These settings are available on an application domain basis' which is the .!"T e)uivalent of a process. 058.!"T hosts Web applications in their own domain and provides application domain settings.

$ocalities and Permissions Aifferent storage mechanisms support different localities 3as well as other characteristics' such as whether they can be written to as well as read from4. *n addition' different localities re)uire different user permissions. #or e(ample' writing application settings to the @egistry re)uires 0dministrator group permissions' something you cannot assume that your user has. Before shipping an application that needs to read or write settings' you should test it under the most restricted set of permissions that your users could have.

.config Files
.!"T provides .config files to serve as a read/only location for te(t/based application settings. 0 .config file is a file placed in the same folder as the application and having the same name as the application e(cept for a .config e(tension. #or e(ample' the .config file associated with foo.e(e would be named foo.e(e.config. .!"T itself uses .config files for all $inds of things' such as resolving assemblies and assembly versions.
L8M L8M

Essential .NET* +ol&me 3- T"e )ommon .ang&age R&ntime 30ddison/Wesley' >0034' by Aon Bo(' with

+hris 5ells' covers assembly loading and versioning in detail.

Jou can add a new .config file to your -5.!"T pro<ect by right/clic$ing on the pro<ect in 5olution "(plorer and choosing 0dd N 0dd !ew *tem N Te(t #ile and naming the file &app.config& 3without the )uotation mar$s4. This action will add an empty .config file to your pro<ect and' when your pro<ect is built' will copy and rename the app.config file to the output folder alongside your application. 0 minimal .config file loo$s li$e this:
=con,i*/ration> =.con,i*/ration>

*n addition to the .!"T/specific settings' .config files can be e(tended with custom T 7 sections as designated with uni)uely named elements. 9ne general/purpose custom section built into .!"T is designated with the element named app5ettings. #or e(ample' the following .config file contains a custom value for pi 3in case the >0 digits provided by 5ystem. ath.8i <ust aren%t enough4:
=con,i*/ration>
<appSettings= <add 6ey1 pi #alue1 @.BSBHDGEH@HYDFD@G@YSEG /= </appSettings=

=.con,i*/ration>

"ach .config section has a specific section reader that $nows how to read values from that section. These section readers can be defined in an application%s .config file or in the systemwide machine.config' as shown here for the app5ettings section reader:
=con,i*/ration> =con,i*Sections> ... =section name1"a##Settin*s" ty#e1"System.Con,i*/ration.BameCal/eFileSectionHan ler, ..." .> ... =.con,i*Sections> ... =.con,i*/ration>

0 section reader is a class that implements *+onfiguration5ection6andler and is registered with an entry in the config5ections section of a .config file. #or e(ample' the !ame-alue#ile5ection6andler class $nows how to read a section in the app5ettings format and return a !ame-alue+ollection from the 5ystem.+ollections.5peciali=ed namespace. 6owever' instead of creating an instance of !ame-alue#ile5ection6andler yourself' it%s more robust to use the +onfigurations5ettings class 3from the 5ystem. +onfiguration namespace4 to map the name of the section to a section reader for you:
using System.Configuration/ using System.Collections.Speciali&ed/

... static $oi ...

Main() "

NameLalueCollection settings 1 +NameLalueCollection.ConfigurationSettings.KetConfig+ appSettings ./

The +onfiguration5ettings class finds the appropriate section handler. The section handler then loo$s in the current app configuration data for the app5ettings section 3parts of which can be inherited from machine.config4' parses the contents' builds the !ame-alue+ollection' and returns it. Because different section handlers can return different data types based on the data provided in their sections' the Get+onfig method returns an ob<ect that must be cast to the appropriate type. 0s a shortcut that doesn%t re)uire the cast' the +onfiguration5ettings class provides built/in support for the app5ettings section via the 0pp5ettings property:
static $oi NameLalueCollection settings 1 ConfigurationSettings.'ppSettings/ Messa*e+ox.Sho!(settings% pi ( );

Main() "

When you%ve got the settings collection' you can access the string values using the $ey as an inde(er $ey. *f you%d li$e typed data 3pi is not much good as a string4' you can manually parse the string using the type in )uestion. 0lternatively' you can use the

0pp5ettings@eader class 3also from the 5ystem.+onfiguration namespace4 to provide typed access to the app5ettings values:
static $oi Main() " .. Garse the $al/e man/ally BameCal/eCollection settin*s 1 Con,i*/rationSettin*s.)##Settin*s; 9ecimal #i5 1 0ecimal.Parse+settings% pi (.;
// Cet 'ppSettings"eader parse the #alue 'ppSettings"eader reader 1 new 'ppSettings"eader+./ 9ecimal #i@ 1 +0ecimal.reader.KetLalue+ pi - typeof+0ecimal../

... -

The 0pp5ettings@eader class%s Get-alue method uses .!"T type conversion classes to do its wor$' ma$ing things a bit easier for you if your application%s .config file uses different types.

D!namic Properties
*f you%d li$e even easier access to values from the app5ettings section of the .config file' you can bind a property of a form or control to a value by using the 8roperty Browser and dynamic properties. 0 d#namic propert# is a property value that%s pulled from the .config file' with the e(tra benefit that Win#orms Aesigner writes the reader code for you in *nitiali=e+omponent. #or e(ample' to bind the 9pacity property of a form to a value in the .config file' you bring up the properties for the form and press the &W& button under the 0dvanced property of the Aynamic8roperties item' as shown in #igure 11.S. Figure 11... D!namic Properties in the Propert! "rowser

*n the Aynamic 8roperties dialog' chec$ the bo( ne(t to 9pacity and notice the Dey mapping' as shown in #igure 11.I. Figure 11.0. D!namic Properties Dialog

The Dey mapping defaults to Uob<ect!ameV.Uproperty!ameV' but you can name it whatever you li$e. When you press the 9D button and open the pro<ect%s app.config' you%ll notice a new $ey in the app5ettings section:
=>xml $ersion1"5.?" enco in*1"Win o!s:5@A@">> =con,i*/ration> =a##Settin*s>
<add 6ey1 FormB.Jpacity #alue1 B /=

=a 4ey1"#i" $al/e1"N.5H5AT@XANAPTKTN@NPHX@" .> =.a##Settin*s> =.con,i*/ration>

5imilarly' each dynamic property has a little document icon ne(t to it in the 8roperty Browser' as shown in #igure 11.8. Figure 11.2. *pacit! 8ar5ed as a D!namic Propert!

When a property is mar$ed as dynamic' the Aesigner writes the value you set in the 8roperty Browser to the app.config file instead of to *nitiali=e+omponent. 5ubse)uently' *nitiali=e+omponent will read the property from the .config:
$oi ... 6nitiali7eCom#onent() "
'ppSettings"eader configuration'ppSettings 1 new 'ppSettings"eader+./ this.Jpacity 1 ++System.0ou!le.+configuration'ppSettings.KetLalue+ FormB.Jpacity - typeof+System.0ou!le..../

...

!ow' when your application is deployed and your power users want to change the 9pacity of the form' they can crac$ open the .config file with any te(t editor and have at it. 1nfortunately' if the power user removes the 9pacity setting or gives it an invalid format' the application will throw an e(ception at run time' and the *nitiali=e+omponent method won%t do anything to deal with it. *f you%d li$e to guard against that' you need to provide a 1* for your users to set their preferences that is more robust than !otepad. 0nd if that%s the case' .config files are not for you. There is no 08* in .!"T for writing .config files' only for reading them. This ma$es .config files effectively read/only for applications 3although readCwrite for humans facile in T 74.
L9M L9M

5ome humans have also posted code on the Web to add write capabilities to .config files' although as of .!"T 1.1' .config files are still

officially read/only.

The /egistr!
The @egistry' on the other hand' has been the place to $eep readCwrite application settings and roaming user settings from Windows 3.1 through Windows !T 3it has fallen out of favor in more recent versions of Windows4. The @egistry gives you hierarchical' machinewide storage of arbitrary nameCvalue pairs split into application and roaming user localities based on the path to the value. The @egistry "ditor 3regedit.e(e4 is a built/in tool for setting and updating @egistry values' as shown in #igure 11.9.
L10M L10M

Be careful when editing @egistry values. Jou%re wor$ing on live data that%s used by the entire system. 9ne wrong move and you%re reinstalling

the 95' and there%s no 1ndo2

Figure 11.4. The /egistr! %ditor

The @egistry is used a lot by Win3> applications' including the "(plorer shell' so you can find yourself reading and writing @egistry values whether or not you use it to store your own application%s settings. #or e(ample' to use the @egistry to associate a particular file e(tension with your application' you use the @egistryDey class from the icrosoft.Win3> namespace:
/sin* Microso,t.WinN@; ... static $oi Main(strin*IJ ar*s) " .. Create a 4ey an set its e,a/lt $al/e /sin*( 2e*istryLey 4ey 1 2e*istry.Classes2oot.CreateS/&Ley(".tl,") ) " .. Ma# .tl, extension to a Gro*69 4ey.SetCal/e(n/ll, "tl,,ile"); .. Create another 4ey an set its e,a/lt $al/e strin* cm 4ey 1 V"tl,,ile\shell\o#en\comman "; /sin*( 2e*istryLey 4ey 1 2e*istry.Classes2oot.CreateS/&Ley(cm 4ey) ) " .. Ma# Gro*69 to an D#en action ,or the shell 4ey.SetCal/e(n/ll, )##lication.(xec/ta&leGath ; " \"]L\""); ...

The @egistryDey class is a named &folder& in the @egistry. This folder can have one or more named values' which are li$e the &files& 3a name of null denotes the defa&lt val&e for a $ey4. The values can be of several types' including string' unsigned integer' and arbitrary bytes. Writing to the @egistry is a matter of opening or creating a sub$ey from one of the "ive !e#s 3which represent the top/level localities4 and writing values. The hive $eys are properties on the @egistry ob<ect and translate into $eys with well/$nown names in the @egistry' as shown in Table 11.>. @eading values from the @egistry is similar to writing them:
static $oi Main(strin*IJ ar*s) " .. Chec4 !hether someone has hi'ac4e &ool ma#(xtension 1 tr/e; the .tl, extension

// Jpen an e*isting 6ey using+ "egistryPey 6ey 1 "egistry.Classes"oot.JpenSu!Pey+ .tlf . . 4 // ;f the reference is null- the 6ey doesn:t e*ist i,( (6ey 21 null) ZZ (6ey.KetLalue+null..ToString+..0oLo!er() 31 "tl,,ile"

) ) " strin* as4 1 ")ssociate .tl, !ith this a##lication>"; 9ialo*2es/lt res 1 Messa*e+ox.Sho!(as4, "Do#s3", Messa*e+ox+/ttons.MesBo); i,( res 11 9ialo*2es/lt.Bo ) ma#(xtension 1 ,alse;

i,( ma#(xtension ) "...... -

Ta9le 11.2. /egistr! Properties and <e! (ames


"egistry Class Static Property "egistry ,i#e Pey Name

@egistry.+lasses@oot @egistry.+urrent+onfig @egistry.+urrent1ser @egistry.AynAata @egistry.7ocal achine @egistry.8erformanceAata @egistry.1sers


LaM

6D"JQ+7055"5Q@99T 6D"JQ+1@@"!TQ+9!#*G 6D"JQ+1@@"!TQ15"@ 6D"JQAJ!QA0T0


LaM

6D"JQ79+07Q 0+6*!" 6D"JQ8"@#9@ 0!+"QA0T0 6D"JQ15"@5

Win9( only

To use the @egistry to store settings' $ey using the following format:

icrosoft recommends putting them under the hive

=hi$eLey>\So,t!are\=com#anyBame>\=#ro /ctBame>\=#ro /ctCersion>

6ere%s an e(ample:
HL(M%C<22(B0%<S(2\So,t!are\Sells +rothers, 6nc.\My Settin*s 0est\5.?.55@H.N5?KK

The variable values are' coincidentally' e(actly the same values provided by 0pplication.+ompany!ame' 0pplication.8roduct!ame' and 0pplication.-ersion' so you can construct a top/level $ey name by using the following:
strin* a##4ey 1 strin*.Format( V"So,t!are\"?-\"5-\"@-", )##lication.Com#anyBame, )##lication.Gro /ctBame, )##lication.Gro /ctCersion); /sin*( 2e*istryLey 4ey 1 2e*istry.LocalMachine.D#enS/&Ley(a##4ey) ) " ... -

5imilarly' for roaming user settings' icrosoft recommends using the same sub$ey but under the 6D"JQ+1@@"!TQ15"@ hive instead of the 6D"JQ79+07Q 0+6*!" hive. To accommodate the many people desiring to open sub$eys for application and roaming user settings in the icrosoft/recommended spots' the Win#orms 0pplication ob<ect includes two properties that provide preopened registry $eys at the right spot depending on whether you%d li$e application data or roaming user data. These properties are named +ommand0ppAata@egistry and 1ser0ppAata@egistry. #or e(ample' to save the main form%s position you could use the 1ser0ppAata@egistry $ey:
$oi
// Sa#e the form:s position !efore it closes using+ "egistryPey 6ey 1 'pplication.Iser'pp0ata"egistry . 4 // "estore the window state to sa#e location and // client si&e at restored state FormWindowState state 1 this.WindowState/ this.WindowState 1 FormWindowState.Normal/

MainForm%Closin*(o&'ect sen er, Cancel($ent)r*s e) "

6ey.SetLalue+ $ainForm.Cocation - ToString+this.Cocation../ 6ey.SetLalue+ $ainForm.ClientSi&e - ToString+this.ClientSi&e../ 6ey.SetLalue+ $ainForm.WindowState - ToString+state../

.. Con$ert an o&'ect to a strin* strin* 0oStrin*(o&'ect o&') " 0y#eCon$erter con$erter 1 0y#e9escri#tor.EetCon$erter(o&'.Eet0y#e()); ret/rn con$erter.Con$ert0oStrin*(o&'); -

This e(ample uses the +losing event to notice when the main form is about to close 3but before it does4 to save the window state' location' and client si=e. *n addition to remembering to restore the window state before saving the location and the client si=e' this code uses the To5tring helper function. This function uses a type converter' which is a helper to aid in the conversion between instances of a type and strings 3for more on type converters' see +hapter 9: Aesign/Time *ntegration4. 0ny type can have an associated type converter' and most of the simple ones do. 0fter the application is run once' notice the settings that are saved in the @egistry' as shown in #igure 11.10. Figure 11.16. 7sing the /egistr! for 7ser ,ettings

!otice that the si=e is shown as &>9>' FH& instead of &^>9>' FH_'& as would have happened if we had used the built/in 5i=e class%s To5tring method. Because 5i=e%s type converter doesn%t $now how to translate the surrounding braces' it%s important to use the type converter%s conversion to the string format instead of the type%s conversion to ensure that the value can be read bac$ during loading of the form:
$oi
// "estore the form:s position using+ "egistryPey 6ey 1 'pplication.Iser'pp0ata"egistry . 4

MainForm%Loa (o&'ect sen er, ($ent)r*s e) "

try " .. 9onFt let the ,ormFs #osition &e set a/tomatically this.StartGosition 1 FormStartGosition.Man/al; this.Location 1 (Goint)FromStrin*( ty#eo,(Goint)); this.ClientSi7e 1 (Si7e)FromStrin*(

6ey.KetLalue+ $ainForm.Cocation .-

6ey.KetLalue+ $ainForm.ClientSi&e .-

ty#eo,(Si7e)); this.Win o!State 1 (FormWin o!State)FromStrin*(

6ey.KetLalue+ $ainForm.WindowState .-

ty#eo,(FormWin o!State)); .. 9onFt let missin* settin*s scare the /ser catch "-

.. Con$ert a strin* to an o&'ect o&'ect FromStrin*(o&'ect o&', 0y#e ty#e) " 0y#eCon$erter con$erter 1 0y#e9escri#tor.EetCon$erter(ty#e); ret/rn con$erter.Con$ertFromStrin*(o&'.0oStrin*()); -

*n this case' the form%s 7oad method uses the @egistry $ey to load the settings that it saved during the +losing event' this time using the type converters to convert from a string to the appropriate type. !otice also that the 7oad method uses a try/catch bloc$ to wrap the attempts to pull values from the @egistryP in this way' we avoid throwing an e(ception if the values aren%t present. These values typically are missing when the application is first run. *f you%d li$e' you can set defaults to avoid the e(ception when a value is not found:
this.Win o!State 1 (FormWin o!State)FromStrin*( 4ey.EetCal/e("MainForm.Win o!State", ty#eo,(FormWin o!State));

Normal

),

0lthough the @egistry can be used for application and user settings' its use has fallen out of fashion. +orruption issues with early implementations of the @egistry have given it a bad reputation. There%s nothing inherently wrong with it that more modern versions of Windows haven%t fi(ed long ago. 6owever' because the @egistry doesn%t support nonroaming user settings or use from partially trusted applications' you%ll want to consider special folder/based settings media' discussed ne(t.

,pecial Folders
Special folders are folders that Windows designates as having a special purpose. #or e(ample' the default folder where programs are installed is special and is available this way:
.. Eenerally "C:\Gro*ram Files" strin* #ro*ramFiles 1

En#ironment.KetFolderPath+En#ironment.SpecialFolder.ProgramFiles.;

There are three special folders for settings: one each for the application' user' and roaming user localities. Table 11.3 shows them' along with some sample paths running on Windows T8. The special folder serves as the top/level folder in the folder under which applications can store application settings' user settings' and roaming user settings 3<ust as @egistry.7ocal achine and @egistry.+urrent1ser provide the top level for application and roaming user settings4. 1nder that folder' an application is e(pected to construct a subfolder to avoid colliding with other applications or even versions of itself. This subfolder has the following format:

=s#ecialFol er>\=com#anyBame>\=#ro /ctBame>\=#ro /ctCersion>

#or e(ample:
C:\9oc/ments an Settin*s\csells\Local Settin*s\)##lication 9ata\Sells +rothers, 6nc.\My Settin*s 0est\5.?.55@H.NNA5T

Ta9le 11. . ,pecial Folders, $ocalities, and %3amples


SpecialFolder Enum Lalue Cocality E*ample Path

+ommon0pplicationAata 0pplication 7ocal0pplicationAata 0pplicationAata 1ser @oaming user

+:OAocuments and 5ettingsO0ll 1sersO0pplication Aata +:OAocuments and 5ettingsO<user=O7ocal 5ettingsO0pplication Aata +:OAocuments and 5ettingsO<user=O0pplication Aata

0nd <ust as the 0pplication ob<ect provides shortcut access to @egistry $eys via properties' it also provides shortcut access to prefabricated folder names and folders via the +ommon0ppAata8ath' 7ocal1ser0ppAata8ath' and 1ser0ppAata8ath properties. #or e(ample' here%s how to rewrite the @egistry/based setting code using special folders:
/sin* System.6D; ... $oi MainForm%Closin*(o&'ect sen er, Cancel($ent)r*s e) " .. Sa$e the ,ormFs #osition &e,ore it closes strin* ,ileBame 1 'pplication.CocalIser'pp0ataPath ; V"\MainForm.txt"; /sin*( StreamWriter !riter 1 ne! StreamWriter(,ileBame) ) " .. 2estore the !in o! state to sa$e location an .. client si7e at restore state FormWin o!State state 1 this.Win o!State; this.Win o!State 1 FormWin o!State.Bormal; !riter.WriteLine(0oStrin*(this.Location)); !riter.WriteLine(0oStrin*(this.ClientSi7e)); !riter.WriteLine(0oStrin*(state));

$oi MainForm%Loa (o&'ect sen er, ($ent)r*s e) " )##Settin*s2ea er as2ea er 1 ne! )##Settin*s2ea er(); 9ecimal #i 1 (9ecimal)as2ea er.EetCal/e("#i", ty#eo,(9ecimal)); #i0ext+ox.0ext 1 #i.0oStrin*(); .. 2estore the ,ormFs #osition try " strin* ,ileBame 1
'pplication.CocalIser'pp0ataPath ; V"\MainForm.txt"; /sin*( Stream2ea er rea er 1 ne! Stream2ea er(,ileBame) ) "

.. 9onFt let the ,ormFs #osition &e set a/tomatically this.StartGosition 1 FormStartGosition.Man/al; this.Location 1 (Goint)FromStrin*( rea er.2ea Line(), ty#eo,(Goint)); this.ClientSi7e 1 (Si7e)FromStrin*( rea er.2ea Line(), ty#eo,(Si7e)); this.Win o!State 1 (FormWin o!State)FromStrin*( rea er.2ea Line(), ty#eo,(FormWin o!State));

.. 9onFt let missin* settin*s scare the /ser catch( (xce#tion ) "-

*n this case' only two things are different from the use of the @egistry. The first is the use of a file to hold the form%s settings instead of a @egistry $ey to hold the data. This means that the values must be read in the same order as they%re written 3unless you use a smarter seriali=ation strategy4. The second is the use of the path to the user settings data instead of roaming user settings data. With the @egistry' all user data is roaming' whether you want it to be or not 3unless you do something custom under the 6D"JQ79+07Q 0+6*!" hive4. 1ser settings that are related to the capabilities of the machine itself' such as the location' si=e' and state of a form' are better suited to nonroaming data.

,ettings and ,treams


*t is convenient to use type conversions with the @egistry because it maintains distinct values and ma$es them available via the @egistry "ditor' but when you%ve got a stream' real .!"T seriali=ation becomes an attractive option:
L11M L11M

*f you%re not familiar with .!"T seriali=ation' you can read up on the basics in 0ppendi( +: 5eriali=ation Basics.

/sin* System.2/ntime.Seriali7ation; /sin* System.2/ntime.Seriali7ation.Formatters; /sin* System.2/ntime.Seriali7ation.Formatters.Soa#; ...


// Custom type to manage seriali&a!le form data %Seriali&a!le'ttri!ute( class Form0ata 4 pu!lic Point Cocation/ pu!lic Si&e ClientSi&e/ pu!lic FormWindowState WindowState/ pu!lic Form0ata+Form form. 4 this.Cocation 1 form.Cocation/ this.ClientSi&e 1 form.ClientSi&e/ this.WindowState 1 form.WindowState/ 5 5

$oi MainForm%Closin*(o&'ect sen er, Cancel($ent)r*s e) " .. Sa$e the ,ormFs #osition &e,ore it closes strin* ,ileBame 1 )##lication.Local<ser)##9ataGath ; V"\MainForm.txt"; /sin*( Stream stream 1 ne! FileStream(,ileBame, FileMo e.Create) ) " .. 2estore the !in o! state to sa$e location an .. client si7e at restore state FormWin o!State state 1 this.Win o!State; this.Win o!State 1 FormWin o!State.Bormal;
// Seriali&e custom Form0ata o!9ect ;Formatter formatter 1 new SoapFormatter+./ formatter.Seriali&e+stream- new Form0ata+this../

$oi MainForm%Loa (o&'ect sen er, ($ent)r*s e) " .. 2estore the ,ormFs #osition try " strin* ,ileBame 1 )##lication.Local<ser)##9ataGath ; V"\MainForm.txt"; /sin*( Stream stream 1 ne! FileStream(,ileBame, FileMo e.D#en) ) " .. 9onFt let the ,ormFs #osition &e set a/tomatically this.StartGosition 1 FormStartGosition.Man/al;
// 0eseriali&e custom Form0ata o!9ect ;Formatter formatter 1 new SoapFormatter+./ Form0ata data 1 +Form0ata.formatter.0eseriali&e+stream./

.. Set ata ,rom Form9ata o&'ect this.Location 1 ata.Location; this.ClientSi7e 1 ata.ClientSi7e; this.Win o!State 1 ata.Win o!State; .. 9onFt let missin* settin*s scare the /ser catch( (xce#tion ex ) " Messa*e+ox.Sho!(ex.Messa*e, ex.Eet0y#e().Bame); -

This e(ample seriali=es an instance of a custom type that represents the setting data that we%d li$e to $eep between sessions. To seriali$e an ob<ect is to read it from or write it to a stream. 0 stream is an ob<ect that provides access to a storage medium' such as a file or a database. To have something to seriali=e' we%ve got a custom type called #ormAata' which $eeps trac$ of the location' client si=e' and window state. When it%s time to save the form data' the code creates an instance of the new type and then hands it to the formatter' along with the file stream opened in the special folder. 5imilarly' when loading' we use a formatter to deseriali=e the form data and use it to restore the form. This is a much easier way to go than the type converter because it ta$es less code. *n addition' seriali=ation provides some nice

e(tension options as time goes on and the #ormAata class needs to include e(tra information' such as font and color preferences.

+solated ,torage
9ne more technology that .!"T provides for reading and writing settings data is isolated storage. *t%s called &isolated& because it doesn%t re)uire the application to $now where on the hard drive the settings files are stored. *n fact' it%s <ust li$e using the special folder shortcuts provided by the 0pplication class e(cept that the path to the root of the path on the file system isn%t even available to the application. *nstead' named chun$s of data are called streams* and containers 3and subcontainers4 of streams are called stores. The model is such that the implementation could vary over time' although currently it%s implemented on top of special folders with subfolders and files. The special folder you get depends on the scope you specify when getting the store you want to wor$ with. Jou specify the scope by combining one or more flags from the *solated5torage5cope enumeration:
en/m 6solate Stora*eSco#e " )ssem&ly, .. )l!ays re8/ire 9omain, Bone, 2oamin*, <ser, .. )l!ays re8/ire -

*solated storage stores must be scoped by' at a minimum' assembly and user. This means that there are no user/neutral settings available from isolated storage' only user and roaming user settings 3depending on whether the @oaming flag is used4. *n addition' you can scope settings to a .!"T application domain using the Aomain flag' but typically that%s not useful in a Win#orms application. Table 11.H shows the valid combinations of scope flags as related to settings localities and sample folder roots under Windows T8. 9btaining a store to wor$ with is a matter of specifying the scope to the Get5tore method of the *solated5torage#ile class from the 5ystem.*9. *solated5torage namespace:
6solate Stora*eSco#e sco#e 1 6solate Stora*eSco#e.)ssem&ly Y 6solate Stora*eSco#e.<ser; 6solate Stora*eFile store 1 6solate Stora*eFile.EetStore(sco#e, n/ll, n/ll);

Ta9le 11.#. +solated ,torage ,cope, $ocalit!, and Folder /oots


;solatedStorageScope Flags Cocality Folder "oot

0ssembly' 1ser

1ser

+:OAocuments and 5ettingsOUuserVO7ocal 5ettingsO0pplication AataO*solated5torage +:OAocuments and 5ettingsOUuserVO0pplication AataO*solated5torage +:OAocuments and 5ettingsOUuserVO7ocal 5ettingsO0pplication AataO*solated5torage

0ssembly' 1ser' @oaming @oaming 1ser 0ssembly' 1ser' Aomain Aomain

0ssembly' 1ser' Aomain' @oaming +:OAocuments and 5ettingsOUuserVO7ocal @oaming 1ser Aomain 5ettingsO0pplication AataO*solated5torage Because getting the user store for the assembly is so common' the *solated5torage#ile class provides a helper with that scope already in place:
.. Sco#e 1 <ser Y )ssem&ly 6solate Stora*eFile store 1 6solate Stora*eFile.KetIserStoreFor'ssem!ly();

0fter you%ve got the store' you can treat it li$e a container of streams and subcontainers by using the members of the *solated5torage#ile class:
seale class 6solate Stora*eFile : 6solate .. Gro#erties #/&lic o&'ect )ssem&ly6 entity " *et; #/&lic <6ntXH C/rrentSi7e " $irt/al *et; #/&lic o&'ect 9omain6 entity " *et; #/&lic <6ntXH Maxim/mSi7e " $irt/al *et; #/&lic 6solate Stora*eSco#e Sco#e " *et; .. Metho s
pu!lic pu!lic pu!lic pu!lic pu!lic pu!lic pu!lic #oid Close+./ #oid Create0irectory+string dir./ #oid 0elete0irectory+string dir./ #oid 0eleteFile+string file./ string%( Ket0irectoryNames+string searchPattern./ static ;Enumerator KetEnumerator+;solatedStorageScope scope./ string%( KetFileNames+string searchPattern./

Stora*e, 69is#osa&le " -

#/&lic static 6solate Stora*eFile EetStore(...); #/&lic static 6solate Stora*eFile Eet<serStoreFor)ssem&ly(); #/&lic static 6solate Stora*eFile Eet<serStoreFor9omain();
pu!lic #irtual #oid "emo#e+./ pu!lic static #oid "emo#e+;solatedStorageScope scope./

Aon%t be confused by the fact that the *solated5torage#ile class is actually implemented as a directory in the file system. This is only one implementation of the *solated5torage5torage

abstract base class. 9ther implementations are certainly possible 3although none are currently provided by .!"T4. The most common thing you%ll want to do with a store is to create a stream on it using an instance of *solated5torage#ile5tream. The *solated5torage#ile5tream class is <ust another implementation of the virtual methods of the #ile5tream class to hide the details of the underlying implementation. 0fter you%ve got the stream' you can write to it or read from it <ust as if you%d opened it yourself as a file. 6ere%s the same code again to store the main form%s location using isolated storage:
$oi MainForm%Closin*(o&'ect sen er, Cancel($ent)r*s e) " .. Sa$e the ,ormFs #osition &e,ore it closes
;solatedStorageFile store 1 ;solatedStorageFile.KetIserStoreFor'ssem!ly+./ using+ Stream stream 1 new ;solatedStorageFileStream+ $ainForm.t*t File$ode.Createstore. . 4 5

...

$oi MainForm%Loa (o&'ect sen er, ($ent)r*s e) " .. 2estore the ,ormFs #osition try "

;solatedStorageFile store 1 ;solatedStorageFile.KetIserStoreFor'ssem!ly+./ using+ Stream stream 1 new ;solatedStorageFileStream+ $ainForm.t*t File$ode.Jpenstore. . 4

... .. 9onFt let missin* settin*s scare the /ser catch( (xce#tion ) "-

8anaging +solated ,torage and ,treams Jou can manage the isolated storage using the 5tore 0dmin tool 3storeadm .e(e4 from the command line. To list the user settings' use storeadm Clist' as shown in #igure 11.11. Figure 11.11. 7sing the ,tore 1dmin Tool to $ist ,tores and ,treams

5ome records indicate storage' and others indicate streams. To remove all the user isolated storage' use storeadm Cremove. Both of these commands operate by default on user settings. To act on roaming user settings' add the Croaming switch to either command. 1nfortunately' the 5tore 0dmin tool does not show the contents of any storage or stream' nor does it show the mapping in the file system. 5o your best bet is to use the dir and find shell commands' starting at the root of the isolated storage folder you%re interested in e(ploring. +solated ,torage and Partial Trust 0lthough it may seem that isolated storage is only a more complicated use of special folders' there is an important benefit to using isolated storage instead of opening settings files directly: 1sing isolated storage lets you use partially trusted assemblies. 0 partiall# tr&sted assembl# is one that runs in a security sandbo( that limits its permissions.for e(ample' a Win#orms control hosted in *nternet "(plorer. 8artially trusted assemblies are not allowed to read or write to the @egistry or the file system as needed for settings' but they are allowed to read and write to isolated storage. *f you need settings for a partially trusted assembly' your only option is to use isolated storage for readCwrite user settings and to use .config files for read/only settings. +hapter 1F: Web Aeployment covers partially trusted assemblies in detail.

=ersioned Data Paths


Jou may have noticed that all the paths provided by the 0pplication ob<ect for the @egistry and the special folders are built using a version number' including the build number and revision. This means that the ne(t version of your application will be protected from the changing format of settings data. *t also means that the ne(t version of the application cannot use the paths provided by 0pplication to find the settings written by the preceding version.

*f you%re stuc$ on using the @egistry or special folders for user settings' you can get version/independent settings in one of two ways. 9ne way is to bypass the path helper properties on the 0pplication ob<ect and build your own by omitting the version. #or e(ample' you could use a roaming user special folder path in the following format:
=s#ecialFol er>\=com#anyBame>\=#ro /ctBame>

0nother way to get version/independent settings is to migrate the settings to the new version%s path during the installation of the new version. This allows you access to the old version%s settings in the new version while still letting you change the format of any settings values that have changed between versions. 9f course' it also re)uires that you write the code to do the migration. "ach of the remaining two forms of settings management..config files and isolated storage.has a built/in form of version independence. 5ettings in the app5ettings portion of the .config file aren%t $eyed to versions at all' so those values are carried along from version to version 3whether or not you want them to be4. *solated storage has two forms of versioned access to settings. *f an assembly is &nsigned'that is' there is no $ey file associated with it at build time via the assemblywide 0ssemblyDey#ile0ttribute .then isolated storage is <ust as version/ independent as .config files are 3again' whether or not you want it to be4. 0ll versions of the assembly will share the same settings.
L1>M L1>M

Why you%d actually want to sign an assembly beyond versioning user settings data for isolated storage is beyond the scope of this boo$' but it is

covered in gory detail in

Essential .NET 30ddison/Wesley' >0034 by Aon Bo(' with +hris 5ells.

5igning an assembly re)uires first obtaining a $ey:


C:\>sn :4 4ey.sn4

0fter you have a $ey' you can sign an assembly by passing the name of the $ey as the value to the 0ssemblyDey#ile0ttribute:
Iassem&ly: )ssem&lyLeyFile(V"..\..\4ey.sn4")J

0fter an assembly is signed' access to isolated storage is versioned' but only on the ma<or part of the version number. *n other words' all 1.( versions of an application will share the same settings' but >.( versions will need their own settings. This is probably the best of both worlds. inor versions aren%t li$ely to change the format of e(isting settings and will at most gain new settings 3with appropriate defaults4. a<or versions are li$ely to change fundamental things' including the format of settings data' so it ma$es sense to give a new ma<or version number a clean slate when it comes to settings data. When using isolated storage' you%ll still have to move user settings forward during the installation of the new version' but only if it%s a ma<or version upgrade' such as 1.1 to >.0. !o migration is needed for an upgrade from 1.1 to 1.>.

!ote that this version discussion is about the paths to the data stores themselves. *f you%re seriali=ing versioned types to an isolated storage stream' for e(ample' you%ll still need to deal with standard .!"T versioning on those.

Choosing a ,ettings 8echanism


Table 11.F summari=es the characteristics of the various mechanisms for retrieving and storing settings for Win#orms applications. +hoosing which settings mechanism to use depends on what your needs are. *f you have read/only application settings' the .config file is a good choice because it%s simple' it has some built/in Aesigner support' and it wor$s from a partially trusted environment. *f you%ve got user settings' then isolated storage is a good choice because it supports reading and writing' partial trust' and roaming 3although not roaming in combination with partial trust4' and it has a nice versioning story. 5pecial folders or the @egistry is really useful only for legacy applications or readCwrite settings 3which are pretty darn rare4. Ta9le 11.'. ,ummar! of ,ettings 8echanisms
$echanism Cocalities 'ccess Notes

.config files

0pplication

@ead/only !ot versioned Aesigner support +an be used from partially trusted assembly

@egistry

0pplication @oaming user achine

@eadCwrite -ersioned when using 0pplication paths

5pecial folders 0pplication 1ser @oaming user *solated storage 1ser @oaming user Aomain

@eadCwrite -ersioned when using 0pplication paths

@eadCwrite !ot versioned when unsigned -ersioned on ma<or version number when signed +an be used from partially trusted assembly

Where Are We?

The seemingly simple application architecture in Win#orms and .!"T provides some useful capabilities' including tailored lifetime support for building single/instance and multi/5A* applications. *t also gives you several settings storage mechanisms' depending on the environment in which you%re running your application and its particular needs.

Chapter 12. Data ,ets and Designer ,upport


0 large ma<ority of e(isting and even new Windows applications are built to access a database. *f you%re building that $ind of application 3and chances are very good that you are4' you%ll need to $now how .!"T supports access to relational data providers as well as how that support is integrated into -5.!"T for ease of development of your Win#orms applications. 9f course' accessing databases is a huge underta$ing that can%t be covered completely in anything less than an entire boo$ 3at least4. This chapter introduces only the basics of 0A9.!"T' the part of the .!"T #ramewor$ responsible for providing access to the myriad of data providers. #or e(ample' although * use this chapter to e(plore data sets and e(plain how they%re used in Win#orms applications' * don%t cover data readers at all. The data reader can be useful' but it doesn%t support Win#orms data binding' a popular thing to do in Win#orms 3and the sub<ect of +hapter 13: Aata Binding and Aata Grids4. This chapter' and the chapter that follows' give you a running start on 0A9.!"T' but for the complete story' including all the sordid details' you really need another boo$.
L1M

Pragmatic ADO.NET 30ddison/Wesley' >00>4' by 5hawn Wildermuth' who was also an ama=ing help on the two data/centric chapters of this boo$. 0lso' for 5Y7 beginners 5hawn recommends ,nstant S4. Programming 3Wro( 8ress' 199F4' by Eoe +el$o.
L1M

#for 0A9.!"T coverage' *%m partial to

#ata Sets
The main unit of any data/centric application is the data set' a data/provider/neutral collection of tables with optional relationship and constraint information. "ach data set contains data tables* each of which consists of =ero or more data ro s* which hold the actual data. *n addition' each data table contains individual data col&mns* which have metadata that describes the type of data each data row can contain. 0 data set can be populated by hand but is most commonly populated via a data adapter' which $nows how to spea$ a data/provider/specific protocol to get and set the data. The data adapter uses a data connection' which is a communications pipe to the data itself' whether it lives on a file in the file system or in a database on another machine. 0 data command is used with the connection to retrieve the rows in )uestion or to otherwise act on the data provider. Aata sets' tables' rows' and columns are data/source/neutral' but data adapters and connections are specific to a data source. The specifics serve as a bridge between the data

provider and the .!"T data/provider/neutral services' such as data binding 3covered in +hapter 134. The basic pieces of the .!"T data architecture' $nown as 0A9.!"T' are shown in #igure 1>.1. Figure 12.1. .(%T Data 1rchitecture

This chapter and +hapter 13 include a lot of code samples that depend on an instance of 5Y7 5erver running with the !orthwind database installed. *f you don%t have 5Y7 5erver running' you can install the icrosoft 5Y7 5erver Aeveloper "dition 3 5A"4 that comes with the .!"T #ramewor$ 5AD. #ollow the instructions in 5tart N 8rograms N icrosoft .!"T #ramewor$ 5AD N 5amples and Yuic$5tartTutorials N *nstall the .!"T #ramewor$ 5amples Aatabase.
L>M L>M

*f you%re using 5Y7 5erver' whether it%s

5A" or not' you%ll want to watch out for the 5Y7 5lammer' which is covered at

http:CCwww.microsoft.comCsecurityCslammer.asp.

/etrie&ing Data
Given this basic architecture' the following shows an e(ample of filling a Aata5et ob<ect using the classes from the 5ystem.Aata namespace and the 5Y7 5erver data provider classes from the 5ystem.Aata.5)l+lient namespace:
/sin* System.9ata; /sin* System.9ata.S8lClient; .. )ccess to S[L Ser$er
... // ' data set for use !y the form 0ataSet dataset 1 new 0ataSet+./

#oid FormB8Coad+o!9ect sender- E#ent'rgs e. 4 // Configure the connection S>lConnection conn 1 new S>lConnection+M Ser#er1localhost/... ./

// Create the adapter from the connection S>l0ata'dapter adapter 1 new S>l0ata'dapter+conn.CreateCommand+../ adapter.SelectCommand.CommandTe*t 1 select Q from customers / // Fill the data set with the Customers ta!le adapter.Fill+dataset./

.. Go#/late list &ox Go#/lateList+ox(); $oi Go#/lateList+ox() " .. Clear the list &ox list+ox5.6tems.Clear();
// Enumerate cached data foreach+ 0ata"ow row in dataset.Ta!les%A(."ows . 4 string item 1 row% ContactTitle ( 7 - 7 row% ContactName (/

list+ox5.6tems.)
5

(item);

This code creates a connection using a data/provider/specific connection string' which tells the connection where to go to get the data. *t then creates an adapter with the appropriate command te%t to retrieve data over the connection. The adapter is used to fill the data set' which produces one table. The code then enumerates the table%s rows' pic$ing out columns by name that we happen to $now that the table will contain. Then it uses the data to populate items in a list bo(' as shown in #igure 1>.>. Figure 12.2. ,howing /etrie&ed Data

!otice that although the sample code creates a connection' it never opens or closes it. *nstead' the data adapter opens the connection for an operation.in this case' retrieving the data and filling the data set.and closes it when an operation is complete. The data set

itself never uses the connection' nor does it $now about where the data comes from. *t%s the data adapter%s <ob to translate data in provider/specific format into the provider/neutral data set. Because the data set has no concept of a connection to the provider' it is a cache of both data and operations on data. Aata can be updated and even removed from the data set' but those operations aren%t reflected to the actual provider until you tell the data adapter to do so. Before we discuss that' however' let%s ta$e a loo$ at the rest of the common data operations: creating' updating' and deleting data.

Creating Data
+reating a new row in a table is a matter of as$ing the table for an empty Aata@ow ob<ect and filling it with column data:
$oi a 2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "
// 's6 ta!le for an empty 0ata"ow 0ata"ow row 1 dataset.Ta!les%A(.New"ow+./ // Fill 0ata"ow with column data row% Customer;0 ( 1 SECCS) / ... // 'dd 0ata"ow to the ta!le dataset.Ta!les%A(."ows.'dd+row./

.. <# ate list &ox Go#/lateList+ox(); -

7pdating Data
Jou can update e(isting data by reaching into the data set' pulling out the row of interest' and updating the column data as appropriate:
$oi /# ateSelecte 2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " .. Eet selection in ex ,rom list &ox int in ex 1 list+ox5.Selecte 6n ex; i,( in ex 11 :5 ) ret/rn;
// Ket row from data set 0ata"ow row 1 dataset.Ta!les%A(."ows%inde*(/ // Ipdate the row as appropriate row% ContactTitle ( 1 CEJ /

.. <# ate list &ox Go#/lateList+ox();

Deleting Data

Aeleting a row from a table re)uires first deciding <ust how &deleted& you%d li$e it to be. *f you%d li$e the row to be gone from the table completely' leaving no trace behind' that involves the @emove method on the Aata@ow+ollection e(posed from the AataTable:
$oi eleteSelecte 2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " .. Eet selection in ex ,rom list &ox int in ex 1 list+ox5.Selecte 6n ex; i,( in ex 11 :5 ) ret/rn; .. Eet ro! ,rom ata set 9ata2o! ro! 1 ataset.0a&lesI?J.2o!sIin exJ;
// "emo#e the row from the data set dataset.Ta!les%A(."ows."emo#e+row./

.. <# ate list &ox Go#/lateList+ox();

6owever' this is probably more &deleted& than you%d li$e' especially if you plan to replicate changes made to the data set bac$ to the originating data provider. *n that case' you%ll want to mar$ a row as deleted but not remove all traces of it from the data set. Jou do that using the Aelete method on the Aata@ow itself:
$oi eleteSelecte 2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " .. Eet selection in ex ,rom list &ox int in ex 1 list+ox5.Selecte 6n ex; i,( in ex 11 :5 ) ret/rn; .. Eet ro! ,rom ata set 9ata2o! ro! 1 ataset.0a&lesI?J.2o!sIin exJ;
// $ar6 the row as deleted row.0elete+./

.. <# ate list &ox Go#/lateList+ox();

When a AataTable contains deleted rows' you%ll need to change how you access the data' because the AataTable class doesn%t allow direct access to deleted rows. This is to prevent you from accidentally treating deleted rows li$e normal rows. +hec$ing for a deleted row is a matter of chec$ing a row%s @ow5tate' which is a combination of values from the Aata@ow5tate enumeration:
en/m 9ata2o!State " ) e , 9elete , 9etache , Mo i,ie , <nchan*e , -

Ta$ing deleted rows into account loo$s li$e this:


$oi Go#/lateList+ox() " .. Clear the list &ox list+ox5.6tems.Clear(); .. (n/merate cache ata ,oreach( 9ata2o! ro! in ataset.0a&lesI?J.2o!s ) "
if+ +row."owState T 0ata"owState.0eleted. 21 0ata"owState.0eleted . continue/

strin* item 1 ro!I"Contact0itle"J ; ", " ; ro!I"ContactBame"J; list+ox5.6tems.) (item); -

By default' when you access column data' you%re getting the &current& data' which' for deleted columns' is missing 3and attempted access to it will cause a run/time e(ception4. 0ll data is mar$ed with a value from the Aata@ow-ersion enumeration:
en/m 9ata2o!Cersion " C/rrent, 9e,a/lt, Dri*inal, Gro#ose , -

To retrieve old or deleted column data' you can pass a value from Aata@ow-ersion as the second argument to the row%s inde(er:
$oi Go#/lateList+ox() " .. Clear the list &ox list+ox5.6tems.Clear(); .. (n/merate cache ata ,oreach( 9ata2o! ro! in ataset.0a&lesI?J.2o!s ) " i,( (ro!.2o!State Z 9ata2o!State.9elete ) 31 9ata2o!State.9elete ) " strin* i 1 row% Customer;0 - 0ata"owLersion.Jriginal(.0oStrin*(); list+ox5.6tems.) ("QQQ elete QQQ: " ; i ); contin/e; ...

Trac5ing Changes
When a Aata@ow ma$es its way into a Aata5et as a result of the Aata0dapter%s #ill method' the @ow5tate is set to 1nchanged and' as * mentioned' using the Aata@ow Aelete method sets the @ow5tate to Aeleted. 5imilarly' adding new rows and updating e(isting ones sets the @ow5tate to 0dded and odified' respectively. This turns the data set into

not only a repository for the current state of the cached data but also a record of the changes that have been made to the data since it was initially retrieved. Jou can get these changes on a per/table basis by using the Get+hanges method of the AataTable:
9ata0a&le ta&leChan*es 1 ataset.0a&lesI?J.EetChan*es(9ata2o!State.Mo i,ie ); i,( ta&leChan*es 31 n/ll ) " ,oreach( 9ata2o! chan*e 2o! in ta&leChan*es.2o!s ) " Messa*e+ox.Sho!(chan*e 2o!I"C/stomer69"J ; " mo i,ie "); -

The Get+hanges method ta$es a combination of Aata@ow5tate values and returns a table that has a copy of only those rows. The rows are copied so that there%s no need to worry about accessing deleted data' an attempt that would normally throw an e(ception. Jou can use the Get+hanges method to find all the modified' added' and deleted rows' together or selectively. This is a handy way to access the data that you need to replicate changes bac$ to the data provider.

Committing Changes
The combination of the Get+hanges method and the Aata@ow-ersion enumeration allows you to build commands for replicating changes made to the data set bac$ to the data provider. *n the case of database/centric data adapters' you retrieve data using an instance of a command' which is responsible for selecting the data set via the 5elect+ommand property. *n fact' recall the earlier code that set up the data adapter:
.. Con,i*/re the connection S8lConnection conn 1 ne! S8lConnection(V"..."); .. Create the a a#ter ,rom the connection strin* select 1 "select Q ,rom c/stomers"; S8l9ata) a#ter a a#ter 1 ne! S>l0ata'dapter+select- conn./

This code is really <ust a shortcut for the following code' which creates a command to perform the select directly:
.. Con,i*/re the connection S8lConnection conn 1 ne! S8lConnection(V"..."); .. Create the a a#ter ,rom the connection strin* select 1 "select Q ,rom c/stomers"; S8l9ata) a#ter a a#ter 1 ne! S>l0ata'dapter+.;
adapter.SelectCommand 1 new S>lCommand+select- conn./

*t%s the +ommand ob<ect that%s responsible for using the connection to retrieve the data' and it%s the data adapter%s <ob to $eep trac$ of the command it needs to retrieve the data. 5imilarly' the data adapter uses other commands for replicating changes bac$' where &changes& includes added rows' updated rows' and deleted rows. *t does this using commands that are set via the *nsert+ommand' 1pdate+ommand' and Aelete+ommand properties' respectively.

Jou can populate these commands yourself' but it%s generally easier to let a command builder do that wor$ for you. 0 command b&ilder is an ob<ect that uses the information it gets from the select command and populates the other three commands appropriately:
.. Create the a a#ter ,rom the connection !ith a select comman S8l9ata) a#ter a a#ter 1 ne! S8l9ata) a#ter("select Q ,rom c/stomers", conn);
// Cet command !uilder !uild commands for insert- update- and delete // using the information from the e*isting select command new S>lCommand)uilder+adapter./

The command builder is so self/sufficient that you don%t even need to $eep it around. The mere act of creating it' passing the adapter that needs commands built' is enough. 0fter the command builder has set up the adapter%s commands appropriately' you replicate changes bac$ to the data provider by calling the adapter%s 1pdate method:
$oi commitChan*esMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " .. Con,i*/re the connection S8lConnection conn 1 ne! S8lConnection(V"..."); .. Create the a a#ter ,rom the connection !ith a select comman S8l9ata) a#ter a a#ter 1 ne! S8l9ata) a#ter("select Q ,rom c/stomers", conn);
// Cet command !uilder !uild commands for insert- update- and delete new S>lCommand)uilder+adapter./ // Commit changes !ac6 to the data pro#ider try 4 adapter.Ipdate+dataset./ 5 catch+ S>lE*ception e* . 4 $essage)o*.Show+e*.$essage- Error+s. Committing Changes ./ 5

.. <# ate list &ox Go#/lateList+ox(); -

This code uses a command builder to build the other three commands needed to update the data provider and then lets the data adapter compose the command te(t as necessary. *f any of the updates causes an error' a run/time e(ception will be thrown' and that%s why the code shows the call to 1pdate wrapped in a try/catch bloc$. "rror information is $ept for each row so that you can show it to the user:
$oi Go#/lateList+ox() " .. Clear the list &ox list+ox5.6tems.Clear(); .. (n/merate cache ata ,oreach( 9ata2o! ro! in ataset.0a&lesI?J.2o!s ) " i,( (ro!.2o!State Z 9ata2o!State.9elete ) 31 9ata2o!State.9elete ) contin/e; strin* item 1 ro!I"Contact0itle"J ; ", " ; ro!I"ContactBame"J;

i,( row.,asErrors ) item ;1 "(QQQ" ; list+ox5.6tems.) (item); -

row."owError

; "QQQ)";

The 6as"rrors Boolean property of each row reports whether there was an error during the last update' and the @ow"rror string reports what that error is. *f there are errors during an update' the @ow5tate of the row will not be changed. #or every row that doesn%t have errors' it will be reset to Aata@ow5tate.1nchanged in preparation for the ne(t update.

8ultita9le Data ,ets


Aata sets can hold more than one table at a time. When creating data sets that contain multiple tables' you will want to use one data adapter for each table loaded. *n addition' you must be careful when filling a data set using more than one adapter. *f you call the data adapter%s #ill method on a data set multiple times' you%ll end up appending data into a single table' so you need to be specific about what table you%re trying to fill:
.. Con,i*/re the connection S8lConnection conn 1 ne! S8lConnection(V"..."); .. Create the a a#ters
S>l0ata'dapter customers'dapter 1 new S>l0ata'dapter+./ S>l0ata'dapter orders'dapter 1 new S>l0ata'dapter+./

.. Create a ata set 9ataSet ataset 1 ne! 9ataSet(); $oi M/lti0a&leForm%Loa (o&'ect sen er, ($ent)r*s e) " .. Create the C/stomer a a#ter ,rom the connection c/stomers) a#ter.SelectComman 1 conn.CreateComman (); c/stomers) a#ter.SelectComman .Comman 0ext 1 "select Q ,rom c/stomers";
// Fill the data set with the Customers ta!le customers'dapter.Fill+dataset- Customers ./

.. Create the Dr ers a a#ter ,rom the connection or ers) a#ter.SelectComman 1 conn.CreateComman (); or ers) a#ter.SelectComman .Comman 0ext 1 "select Q ,rom or ers";
// Fill the data set with the Jrders ta!le orders'dapter.Fill+dataset- Jrders ./ // Need one command !uilder for each adapter // in anticipation of e#entually committing changes new S>lCommand)uilder+customers'dapter./ new S>lCommand)uilder+orders'dapter./

.. Go#/late list &oxes Go#/lateList+oxes();

This code fills a data set with data from two different data adapters' one for each table. When you call the #ill method of the data adapter' you must specify which table to fill with the data from the adapter. *f you fail to do this' you will get one data table 3called &Table&4 with data from both #ill methods. Jou could have used a single data adapter to fill both tables' but because the command builders use the 5elect+ommand to determine how to update the data provider' it is good form to have a one/to/one relationship between tables in the data set and data adapters. !otice that when the data adapters are created' one command builder is created for each of them in anticipation of committing changes for each table. With more than one table' the code to commit changes needs to be updated:
$oi commitChan*esMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " .. Commit c/stomer chan*es &ac4 to the ata #ro$i er try "
customers'dapter.Ipdate+dataset- Customers ./

catch( S8l(xce#tion ex ) " Messa*e+ox.Sho!(ex.Messa*e, "(rror(s) Committin* C/stomer Chan*es"); .. Commit or er chan*es &ac4 to the try "
orders'dapter.Ipdate+dataset- Jrders ./

ata #ro$i er

catch( S8l(xce#tion ex ) " Messa*e+ox.Sho!(ex.Messa*e, "(rror(s) Committin* Dr er Chan*es"); .. <# ate list &oxes Go#/lateList+oxes();

This code commits changes on each table by calling the 1pdate method of the particular data adapter while specifying the table to update. a$e sure not to get the adapter mi(ed up with the name of the table' or things won%t go so well.

Constraints
*f you%d li$e to catch problems with the data as it%s added by the user instead of waiting until the data is sent bac$ to the data provider' you can establish constraints. 0 constraint limits the $ind of data that can be added to each column. The 5ystem.Aata namespace comes with two constraints: the foreign $ey constraint and the uni)ue value constraint' which are represented by the #oreignDey+onstraint and the 1ni)ue+onstraint classes' respectively. #or e(ample' to ma$e sure that no two rows have the same value in a column' you can add a uni)ue constraint to the table%s list of constraints:

.. ) a constraint 9ata0a&le c/stomers 1 ataset.0a&lesI"C/stomers"J; <ni8/eConstraint constraint 1 ne! <ni8/eConstraint(c/stomers.Col/mnsI"C/stomer69"J); c/stomers.Constraints.) (constraint);

With the constraint in place' if a row is added to the table that violates the constraint' a run/ time e(ception will be thrown immediately' without a round/trip to the data provider. 1ni)ue constraints set up a constraint between one or more columns in a single table' whereas foreign $ey constraints set up an e(istence re)uirement between columns in multiple tables. #oreign $ey constraints are set up automatically whenever a relation is established.

/elations
Aata sets are not simply containers for multiple tables of data but instead are containers that have support for relational data. 0s with data in a database' the real power of a data set can be harnessed by relating multiple tables:
.. Eet re,erence to the ta&les 9ata0a&le c/stomers 1 ataset.0a&lesI"C/stomers"J; 9ata0a&le or ers 1 ataset.0a&lesI"Dr ers"J; .. Create the relation 9ata2elation relation 1 ne! 9ata2elation( "C/stomersDr ers", c/stomers.Col/mnsI"C/stomer69"J, or ers.Col/mnsI"C/stomer69"J); .. ) the relation ataset.2elations.) (relation);

This code creates a relation between the customer and order tables on each table%s +ustomer*A column. 0 relation is a named association of columns between multiple tables. To relate columns between tables' you use an instance of a Aata@elation class' passing a name and the columns from each of the two tables. #igure 1>.3 shows the sample relation between the +ustomers and the 9rders tables. Figure 12. . 1 ,ample /elation 9etween the Customers Ta9le and the *rders Ta9le

0fter the relation is created' it%s added to the set of relations maintained on the data set' an action that also sets up the foreign $ey constraint. *n addition' relations are used for navigation and in e(pressions.

(a&igation
When a relation is added' the second argument to the Aata@elation constructor becomes the parent col&mn' and the third argument becomes the c"ild col&mn. Jou can navigate between the two using the Aata@ow methods Get8arent@ows and Get+hild@ows' respectively. This allows you to show' for e(ample' related child rows when a parent row is selected' as shown in #igure 1>.H. Figure 12.#. ,howing the /esults of :etChild/ows 7sing a /elation

*n #igure 1>.H' the top list bo( shows the customers' which form the parent in the +ustomers9rders relation. When a customer is selected' the bottom list bo( is populated with the related rows:
$oi Go#/lateChil List+ox() " .. Clear the list &ox or ersList+ox.6tems.Clear();
// Ket the currently selected parent customer row int inde* 1 customersCist)o*.Selected;nde*/

i,( in ex 11 :5 ) ret/rn;
// Ket row from data set 0ata"ow parent 1 dataset.Ta!les% Customers (."ows%inde*(/

.. (n/merate chil ro!s ,oreach( 9ata2o! ro! in ... -

parent.KetChild"ows+ CustomersJrders .

) "

5imilarly' from any child row' a relation can be navigated bac$ to the parent using Get8arent@ows.

%3pressions
@elations can also be used in e(pressions. 0n e%pression is a column of values that are calculated on/the/fly. The "(pression property on the Aata+olumn class provides this functionality. #or e(ample' here%s an e(pression to combine the +ontactTitle and +ontact!ame fields:
.. Create the ex#ression col/mn 9ataCol/mn ex# 1 ne! 9ataCol/mn(); ex#.Col/mnBame 1 "Contact0itleBame"; ex#.9ata0y#e 1 ty#eo,(strin*); ex#.(x#ression 1 "Contact0itle ; F, F ; ContactBame"; .. ) it to the c/stomer ta&le ataset.0a&lesI"C/stomers"J.Col/mns.) (ex#);

This code creates a new Aata+olumn and specifies the name' data type' and e(pression. The e(pression synta( is fairly straightforward and is syntactically similar to 5Y7. Jou can find the complete reference to the e(pression synta( in the Aata+olumn%s "(pression property documentation.
L3M L3M

http:CCmsdn.microsoft.comClibraryCen/usCcprefChtmlCfrlrf5ystemAataAata+olumn+lass"(pressionTopic.asp

0fter you%ve created an e(pression column' you can use it li$e any other column:
$oi Go#/lateList+oxes() " .. Clear the list &ox c/stomersList+ox.6tems.Clear(); .. (n/merate cache ata ,oreach( 9ata2o! ro! in ataset.0a&lesI"c/stomers"J.2o!s ) "
// Ise the e*pression instead of composing the string //string item 1 row% ContactTitle ( 7 - 7 row% ContactName (/ string item 1 row% ContactTitleName (.ToString+./

c/stomersList+ox.6tems.)

(item);

Go#/lateChil List+ox();

0n e(pression can navigate a relation from child to parent or vice versa. #or e(ample' the orders table doesn%t have a contact name for the parent customer' but you can use an e(pression to grab the data from the parent:
.. Create the ex#ression col/mn 9ataCol/mn ex#@ 1 ne! 9ataCol/mn(); ex#@.Col/mnBame 1 "C/stomerContactBame"; ex#@.9ata0y#e 1 ty#eo,(strin*); .. ) it to the c/stomer ta&le ataset.0a&lesI"Dr ers"J.Col/mns.)

e*pG.E*pression 1 parent+CustomersJrders..ContactName /

(ex#@);

This code uses the parent3relation!ame4.column!ame synta( to navigate from the child orders table to the parent customers table. *f there%s only one relationship' then you can <ust use parent.column!ame. 5imilarly' when going from a parent to a child' you use the child3relation!ame4.column!ame synta(.

#esigner Su""ort
0lthough 0A9.!"T provides a great deal of power in a small amount of code' you still may prefer not to do all the hand coding re)uired in the earlier e(amples. #or that reason' -5.!"T provides integrated data access directly in the Windows #orms designer. *n the Toolbo( window there is a Aata Tab' as shown in #igure 1>.F. Figure 12.'. Data Tool9o3

This Toolbo( contains the various $inds of data access ob<ects you%ll commonly need. These ob<ects are instances of components' so they don%t have 1*s of their own. When they%re added to a design surface' they cluster along the bottom <ust li$e any other component.

Connection *9jects
0 connection ob<ect e(ists to establish the connection to the data provider' and this means that you%re primarily concerned with the +onnection5tring property. *f you%re overly eager' you can type this in manually. ore commonly' you%ll let the 8roperty Browser provide you access to the Aata 7in$ 8roperties dialog' as shown in #igure 1>.S. Figure 12... Data $in5 Properties Dialog

Command *9jects
0s with connections' dropping a command ob<ect does not immediately add any assistance to creating a connection' but again' the 8roperty Browser does' as shown in #igure 1>.I. Figure 12.0. Command Properties

When setting the +onnection property' you%ll get a drop/down list of all the $nown connections on the form. 0fter you select one' you can set the +ommandTe(t property using Yuery Builder' as shown in #igure 1>.8. Figure 12.2. Euer! "uilder

When the +ommandType property is set to Te(t' Yuery Builder will help you specify the type of )uery your command will need to generate. Jou can add tables' views' and functions from your database and customi=e the )uery by using the Aesigner as necessary.

*f +ommandType is set to 5tored 8rocedure or to TableAirect' you%re on your own with the +ommandTe(t.

Data 1dapter *9jects


The data adapter continues to build on the connection ob<ects as well as the command ob<ects. When you drag a data adapter ob<ect onto your form' the Aesigner starts a Aata 0dapter +onfiguration Wi=ard to create a data adapter' which will include a connection ob<ect and one or more command ob<ects. The Aata 0dapter +onfiguration Wi=ard can create any missing ob<ects' saving you those steps. The wi=ard wal$s you through very specific steps: 1. Jou specify and create a connection. >. Jou create one or more commands for the data adapter' including the select' insert' update' and delete commands. 3. Jou create the data adapter to tie all the ob<ects together. When you%re finished with this wi=ard' the generated code creates the connection' the command ob<ects' and the data adapter' associating ob<ects with each other as needed.

T("ed #ata Sets


*n addition to potential problems in creating and relating the various data ob<ects' another source of much coding' and therefore potential for error' is in the access to columns. By default' columns are named with a string and then cast to the specific type that is e(pected:
strin* #ostalCo e 1 (strin*) ataset.0a&lesI"C/stomers"J.2o!sI?JI"GostalCo e"J;

*f you get it wrong' the best thing that can happen is an e(ception at run timeP the worst is a silent failure that hides the error. *t would be useful if we could ma$e the compiler help figure out when there are errors so that we can find and correct them before we even run the code. That%s the <ob of the typed data set.

Creating a T!ped Data ,et


To create a typed data set' you add a new data set to your pro<ect from the 0dd !ew *tem menu. This adds an .(sd file to the pro<ect. *n -isual 5tudio .!"T you use an T 7 schema document 3.(sd4 to generate the classes and use Aata5et Aesigner to configure a typed data set. #igure 1>.9 shows an empty typed data set. Figure 12.4. 1n %mpt! T!ped Data ,et

0pparently this picture is worth 18 words' and' as it states' you can drag ob<ects from either 5erver "(plorer or the Toolbo(. ost often it will be 5erver "(plorer' as shown in #igure 1>.10. Figure 12.16. ,er&er %3plorer

5erver "(plorer allows you to navigate to various data providers. *f you are using 5Y7 5erver' you can navigate directly to the servers to find databases. 9therwise' you will need to create a data connection or select an e(isting one. *f you created new data connections in the earlier e(amples for adding a connection to a form' you should notice that connection shown here as well.

To create a typed data set' you drag any number of tables' stored procedures' views' or functions from the database onto the Aesigner surface. Aragging and dropping the +ustomers and 9rders tables form the !orthwind database will show you something li$e #igure 1>.11. Figure 12.11. T!ped Data ,et with (ew Ta9les

6ere' dropping the tables onto the design surface created two tables: one for +ustomers and one for 9rders. *n each table' the Aesigner was able to as$ the database for the primary $ey' as indicated by the $ey icons. *n addition' notice that each column is typed' which is what puts the &typed& in &typed data set.& 5aving the schema generates a new type having the same name as the schema and deriving from the Aata5et base class. *nside this new type are nested types that provide type/safe wrappers around each row:
#/&lic class C/stomerSet : 9ataSet " ... #/&lic class C/stomers2o! : 9ata2o! " ...
pu!lic string Customer;0 4...5 pu!lic string CompanyName 4...5

With the new typed data set' the code to pull data out of a column is now shorter and more robust:
.. Create a ty#e ata set C/stomerSet ataset 1 ne! C/stomerSet(); .. Fill the ata set as normal

.. ... .. <nty#e access .. strin* #ostalCo e 1 .. (strin*) ataset.0a&lesI"C/stomers"J.2o!sI?JI"GostalCo e"J; .. 0y#e access strin* #ostalCo e 1 ataset.C/stomersI?J.GostalCo e;

Because it derives directly from the Aata5et class' a +ustomer5et ob<ect can be filled and manipulated e(actly li$e a regular data set. *t is a typed data set' so the new typed properties simplify the code. 0s an additional benefit' the tables and columns in the typed data are also available in -5.!"T%s *ntelli5ense' causing typed data sets to further decrease our typing LsicM. 1sing typed data sets in this way is very helpful' but what about constraints' relations' and e(pressions? Aata 5et Aesigner supports them' too.

Constraints in T!ped Data ,ets


0dding a uni)ue constraint to a table is a matter of dropping a $ey onto the table from the T 7 5chema Toolbo(' as shown in #igure 1>.1>. Figure 12.12. >8$ ,chema Tool9o3

Aropping a $ey produces the "dit Dey dialog' as shown in #igure 1>.13. Figure 12.1 . %dit <e! Dialog

To add the uni)ue constraint' you specify the table in the "lement drop/down' and the column in the #ields. *f you want to create a multicolumn uni)ue constraint' such as re)uiring that the combination of first and last name be uni)ue' you can specify more than one column under #ields. 0dding a foreign $ey constraint re)uires adding a @elation from the Toolbo(.

/elations in T!ped Data ,ets


To add a relation to a typed data set' drop a @elation onto the table that will serve as the parent of the relation. This opens the "dit @elation dialog' where you finish the <ob' as shown in #igure 1>.1H. Figure 12.1#. %dit /elation Dialog

The 8arent element will be set based on the table you dropped the @elation onto' so you%ll need to set the +hild element. 0fter you do' the !ame will be set to something fairly intuitive' although you can change it if you li$e. By default' the Dey #ields 3which form the relation4 will be the two primary $eys from the two tables' which is li$ely what you want to relate in the first place. *n most cases this dialog will give you all the options you need 3the documentation can e(plain the subtleties of this dialog if you need more4. 0 relation will show in the Aesigner something li$e the one shown in #igure 1>.1F. Figure 12.1'. T!ped Data ,et with a (ew /elation

!ot only does setting a relation in the Aesigner free us from writing the code to establish the relation' but it also e(poses the relation as a type/safe method for navigation:
.. Ba$i*ate the relation ,rom the #arent to the chil ren ,oreach( CustomerSet.Jrders"ow row in dataset.Customers%A(.KetJrders"ows+. ) "...-

%3pressions in T!ped Data ,ets


Jou can add e(pression columns to a table by typing in a new column name and type at the end of a table' as shown in #igure 1>.1S. Figure 12.1.. 1dding a (ew Column to "e 7sed as an %3pression Column

When you%ve got the new column' you add an "(pression property in the 8roperty Browser:
Contact0itle ; F, F ; ContactBame

7i$e all the other columns' the new e(pression column will be accessible through a strongly typed property:
.. Eet the ,irst ContactFs 0itle an Bame strin* titleBame 1 dataset.Customers%A(.ContactTitleName;

1dding a T!ped Data ,et to a Form


0fter you%ve designed the typed data set class' you can add an instance of it to a form 3or any other design surface4 by dropping a Aata5et from the Aata Toolbo( onto a form. This action produces the 0dd Aataset dialog' as shown in #igure 1>.1I. Figure 12.10. 1dding a T!ped Data ,et to a Form

This dialog allows you to specify a typed or untyped data set. 0ny typed data sets that have been created in the pro<ect will show up in the drop/down list under Typed Aataset. 0s you%d e(pect' pressing 9D generates the code to create an instance of the typed data set class. With the typed data set in place' and using the Aata 0dapter +onfiguration Wi=ard and Yuery Builder' we need to enter only a few lines of code to achieve the functionality of #igure 1>.H:

$oi .. .. ..

Form5%Loa (o&'ect sen er, ($ent)r*s e) " Connection alrea y create ) a#ters alrea y create 9ata set alrea y create

// Fill the data sets this.customers'dapter.Fill+this.customerSetB- Customers ./ this.orders'dapter.Fill+this.customerSetB- Jrders ./

.. .. .. ..

Comman s ,or /# atin* alrea y create <ni8/e constraint alrea y a e 2elation alrea y esta&lishe (x#ression col/mn alrea y a e

.. Go#/late list &oxes Go#/lateList+oxes();

$oi Go#/lateList+oxes() " c/stomersList+ox.6tems.Clear();


// Enumerate typed customers foreach+ CustomerSet.Customers"ow row in this.customerSetB.Customers."ows . 4

i,( (ro!.2o!State Z 9ata2o!State.9elete ) 31 9ata2o!State.9elete ) contin/e;


5 // Ise the typed e*pression column customersCist)o*.;tems.'dd+row.ContactTitleName./

Go#/lateChil List+ox(); $oi Go#/lateChil List+ox() " or ersList+ox.6tems.Clear(); int in ex 1 c/stomersList+ox.Selecte 6n ex; i,( in ex 11 :5 ) ret/rn;
// Ket row from data set CustomerSet.Customers"ow parent 1 this.customerSetB.Customers%inde*(/ // Enumerate typed child order rows using // typed relation method KetJrders"ow foreach+ CustomerSet.Jrders"ow row in parent.KetJrders"ows+. . 4

i,( (ro!.2o!State Z 9ata2o!State.9elete ) 31 9ata2o!State.9elete ) contin/e;


// Ise typed properties ordersCist)o*.;tems.'dd+row.Jrder;0 7 7 row.Jrder0ate./

$oi c/stomersList+ox%Selecte 6n exChan*e (o&'ect sen er, ($ent)r*s e) " Go#/lateChil List+ox(); -

0dding +@1A 3createCretrieveCupdateCdelete4 functionality is also more convenient using a typed data set:

$oi

// 'dd a new typed row CustomerSet.Customers"ow row 1 this.customerSetB.Customers.NewCustomers"ow+./ row.Customer;0 1 SECCS) / row.CompanyName 1 Sells )rothers- ;nc. / row.ContactName 1 Chris Sells / row.ContactTitle 1 Chief Coo6 and )ottle Washer / row.'ddress 1 HHH Not $y Street / row.City 1 )ea#erton / row."egion 1 J" / row.PostalCode 1 DFAAF / row.Country 1 IS' / row.Phone 1 HA@?HHH?BG@S / row.Fa* 1 HA@?HHH?S@GB / this.customerSetB.Customers.'ddCustomers"ow+row./

2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "

.. <# ate list &oxes Go#/lateList+oxes();

$oi /# ateSelecte 2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " int in ex 1 c/stomersList+ox.Selecte 6n ex; i,( in ex 11 :5 ) ret/rn;
// Ipdate a typed row CustomerSet.Customers"ow row 1 this.customerSetB.Customers%inde*(/ row.ContactTitle 1 CEJ /

.. <# ate list &oxes Go#/lateList+oxes();

$oi eleteSelecte 2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " int in ex 1 c/stomersList+ox.Selecte 6n ex; i,( in ex 11 :5 ) ret/rn;
// $ar6 a typed row as deleted CustomerSet.Customers"ow row 1 this.customerSetB.Customers%inde*(/ row.0elete+./

$oi commitChan*esMen/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " try "


// Ipdate a typed ta!le this.customers'dapter.Ipdate+dataset- Customers ./

.. <# ate list &oxes Go#/lateList+oxes();

catch( S8l(xce#tion ex ) " Messa*e+ox.Sho!(ex.Messa*e, "(rror(s) Committin* C/stomer Chan*es"); try "
// Ipdate a typed ta!le this.orders'dapter.Ipdate+dataset- Jrders ./

catch( S8l(xce#tion ex ) " Messa*e+ox.Sho!(ex.Messa*e,

"(rror(s) Committin* Dr er Chan*es");

.. <# ate list &oxes Go#/lateList+oxes();

With typed data sets' the code we%re responsible for typing is smaller and more robust. The Aata5et Aesigner generates the code for coercing data' and the #orms Aesigner generates the code for establishing the connection' the commands' the adapters' and the data set. That leaves us to write only the fun stuff 3mostly4.

Where Are We?


This chapter discusses data sets and the basics of data manipulation. *t e(plains how they%re integrated into -5.!"T and how to use typed data sets to produce leaner' better code. The one part that nags us through the entire chapter' however' is the need to constantly update the list bo(es whenever the underlying data set changes in any way. +hapter 13 e(plains how to use data binding to eliminate that re)uirement.

Chapter 1 . Data "inding and Data :rids


The data support in ado.net and integrated into -5.!"T discussed in +hapter 1> is only half the story for Win#orms programmers. Win#orms provides a rich infrastructure for bidirectional data binding between arbitrary controls and arbitrary data sources. The control that ta$es the most advantage of this architecture is the data grid. This chapter covers data binding and data grids and wraps up with a discussion of custom data sources.

#ata Binding
*n Windows #orms development' there often comes a time when you need to populate a number of controls with data from a database or other data source. @ecall our e(ample from +hapter 1>: Aata 5ets and Aesigner 5upport' which used a data set populated from a database. Whenever the data set was changed' such as when the user added a row or deleted a row' we had to repopulate the list bo(es so that the display was $ept in sync with the data:
$oi
// 'dd a new typed row CustomerSet.Customers"ow row 1 this.customerSetB.Customers.NewCustomers"ow+./

2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) "

ro!.C/stomer69 1 "S(LLS+";
... this.customerSetB.Customers.'ddCustomers"ow+row./

.. <# ate list &ox Go#/lateList+ox();

Writing code to do this repopulation of the control is boring:


$oi
// 0on:t show any item more than once

Go#/lateList+ox() "

c/stomersList+ox.6tems.Clear();
// Show all rows in the Customers ta!le

,oreach( C/stomerSet.C/stomers2o! ro! in this.c/stomerSet5.C/stomers.2o!s ) "


// E*cept don:t show the deleted rows

i,( (ro!.2o!State Z 9ata2o!State.9elete ) 31 9ata2o!State.9elete ) " contin/e; // Jn each row- show the ContactTitleName column

c/stomersList+ox.6tems.)

(ro!.Contact0itleBame);

This code implements the following intention: &Deep the list bo( up/to/date by showing the +ontactTitle!ame column from the undeleted rows of the +ustomers table.& But what we%d really li$e to do is to declare this intention and let some part of Win#orms $eep a control up/to/date as the underlying data changes. That is the service provided by Win#orms data binding.

"indings and Data ,ources


0t the heart of the data binding architecture is the idea of a binding. 0 binding is an association between a control and a data so&rce' which can be any ob<ect 3although it%s often an instance of the Aata5et class4. #or e(ample' binding a data set to a list bo( re)uires a binding be established between the list bo( and the data set. #or the list bo( to be populated with the appropriate data' we must also specify' as part of the binding' the data table and the column name' which is the data member portion of the binding. The data member specifies the name of the data to pull from the data source. #igure 13.1 shows a logical binding from a data set to a list bo(' using the +ontactTitle!ame from the +ustomers table as the data member. Figure 1 .1. Comple3 Data "inding

#igure 13.> shows the results of establishing this binding. Figure 1 .2. /esult of Comple3 Data "inding

The $ind of binding shown in #igures 13.1 and 13.> is $nown as comple% data binding. The binding is &comple(& not because it%s hard to use' but because the 7istBo( control must have specific support for it. 9ther built/in controls' such as +omboBo( and AataGrid' support comple( binding' but not all controls do.
L1M L1M

7ist-iew is an e(ample of a control that you%d e(pect to support comple( binding but doesn%t.

9n the other hand' simple data binding wor$s for all controls. 5imple data binding is an association from a data source to a specific property of a control. The property can be nearly any public property that a control e(poses' such as Te(t or Bac$+olor. What ma$es this binding &simple& is that the control doesn%t have to do anything to support simple bindingP the data binding infrastructure itself sets the value of a control%s bound property. 0s an e(ample of simple binding' #igure 13.3 shows binding the Te(t property of a te(t bo( to a data set' using +ustomers.+ontactTitle!ame as the data member. Figure 1 . . ,imple Data "inding

Because te(t bo(es can%t show lists of data' only one row from the data set can be displayed at a time' as shown in #igure 13.H. Figure 1 .#. ,imple Data "inding

*t may not seem useful to be able to see only one row from a data set' but' as you%ll see' it%s possible to &scroll& through the rows of a data set' allowing a column from each row to be displayed and edited one at a time. Both of these e(amples of data binding show binding against a data set' which provides a list of ob<ects to display either all at once 3in the case of comple( binding4 or one at a time 3in the case of simple binding4. 0 data set is an e(ample of a list data so&rce' because it provides a list of ob<ects against which to bind. 9ther list data sources include arrays' collections from the 5ystem.+ollections namespace' and custom collections 3more on that later4. 6owever' data binding can also happen against an item data so&rce* which is an instance of any type that can )ueried for values to bind against. #or e(ample' #igure 13.F shows a logical binding from a string ob<ect to the Te(t property of a Te(tBo( control. Figure 1 .'. ,imple Data "inding to an +tem Data ,ource

#igure 13.S shows the results of establishing this binding. Figure 1 ... ,imple "inding to a ,tring *9ject

When you%re using data binding' you need to $eep trac$ of both the $ind of binding you%re doing 3simple or comple(4 and the $ind of data source to which you%re binding 3item or list4. Those two factors largely determine how data binding wor$s' as you%ll see.

,imple Data "inding to +tems


The binding scenario in #igure 13.F shows the simple binding of a Te(tBo( control to a string item data source. Jou implement simple binding by creating an instance of a Binding ob<ect and adding it to the list of bindings e(posed by the control via the AataBindings property:
$oi
// 0ata source string string0ataSource 1 ,ello- There /

Form5%Loa (o&'ect sen er, ($ent)r*s e) "

// )inding +without a data mem!er. )inding !inding 1 new )inding+ Te*t - string0ataSource- null./ // // // // )ind the control to the data source Control3 te*t)o*B PropertyName3 Te*t 0ataSource3 string0ataSource

// 0ata$em!er3 null te*t)o*B.0ata)indings.'dd+!inding./

Jou create the Binding ob<ect by passing the name of the control%s property as the first argument' the data source ob<ect as the second argument' and the optional name of a data member. Then you add the new binding ob<ect to the list of the data bindings of the te(t bo(' and that%s all that%s needed to bind the string ob<ect%s value to the Te(t property of the control' as shown earlier in #igure 13.S. The Binding class' which is used to implement simple binding' is from the 5ystem.Windows.#orms namespace:
L>M L>M

0lthough simple and comple( data binding are logically similar' only simple binding uses instances of the Binding class. The implementation

details of comple( binding are specific to the control and are hidden from the client of the control' as you%ll see later in this chapter.

class +in in* " .. Constr/ctors #/&lic +in in*( strin* #ro#ertyBame, o&'ect

ataSo/rce, strin*

ataMem&er);

.. Gro#erties #/&lic +in in*Mana*er+ase +in in*Mana*er+ase " *et; #/&lic +in in*Mem&er6n,o +in in*Mem&er6n,o " *et; pu!lic Control Control 4 get/ 5 pu!lic o!9ect 0ataSource 4 get/ 5

#/&lic &ool 6s+in in* " *et; pu!lic string PropertyName 4 get/ 5

.. ($ents #/&lic e$ent Con$ert($entHan ler Format; #/&lic e$ent Con$ert($entHan ler Garse; -

0s a shortcut to creating a Binding ob<ect and adding it to a control%s list of bindings' you can use the overload of the 0dd method 3which ta$es the three Binding constructor arguments4 and let the 0dd method create the Binding ob<ect for you:
.. 9ata so/rce strin* strin*9ataSo/rce 1 "Hello, 0here";
// )ind the control to the data source// letting the 'dd method create the )inding o!9ect // Control3 te*t)o*B // PropertyName3 Te*t // 0ataSource3 string0ataSource // 0ata$em!er3 null te*t)o*B.0ata)indings.'dd+ Te*t - string0ataSource- null./

8assing null as the data member parameter means that we can obtain the contents of the data source by calling the string ob<ect%s To5tring method. *f you%d li$e to bind to a specific property of a data source' you can do so by passing the name of the property as the data member of the binding. #or e(ample' the following code binds the te(t bo(%s Te(t property to the 7ength property of the string ob<ect:

.. +in the control to the Len*th #ro#erty o, the ata so/rce .. Control: text+ox5 .. Gro#ertyBame: 0ext .. 9ataSo/rce: strin*9ataSo/rce .. 9ataMem&er: Len*th text+ox5.9ata+in in*s.) ("0ext", strin*9ataSo/rce, Cength );

Binding to the 7ength property of the string instead of binding the string itself wor$s as shown in #igure 13.I. Figure 1 .0. ,imple "inding to the $ength Propert! of a ,tring *9ject

0ny public property of a data source can serve as a data member. 5imilarly' any public property of a control can serve as the property for binding. The following shows an e(ample of binding a data source to the #ont property of a te(t bo(:
.. 9ata so/rces strin* strin*9ataSo/rce 1 "Hello, 0here"; .. +in the control #ro#erties to the

Font font0ataSource 1 new Font+ Cucida Console - BY./

ata so/rces

.. Control: text+ox5 .. Gro#ertyBame: 0ext .. 9ataSo/rce: strin*9ataSo/rce .. 9ataMem&er: n/ll text+ox5.9ata+in in*s.) ("0ext", strin*9ataSo/rce, n/ll); .. .. .. .. Control: text+ox5 Gro#ertyBame: Font 9ataSo/rce: ,ont9ataSo/rce 9ataMem&er: n/ll

te*t)o*B.0ata)indings.'dd+ Font - font0ataSource- null./

!otice that the code provides two different bindings against the same control. 0 control can have any number of bindings as long as any single property has only one. The results of the combined bindings are shown in #igure 13.8. Figure 1 .2. "inding the Te3t and Font Properties of a Te3t"o3 Control

!otice also that the data from the data source needs to be a string ob<ect for the Te(t property and needs to be a #ont ob<ect for the #ont property. By providing data sources that e(pose those types directly' we avoided the need for conversion. *f the data being bound to isn%t already in the appropriate format' a type converter is used' as discussed in +hapter 9: Aesign/Time *ntegration. Type converters are also covered in more detail later in this chapter.

,imple Data "inding to $ists


Jou implement simple binding to a list data source without a data member in the same way as binding to an item data source:
.. .. .. .. Control: text+ox5 Gro#ertyBame: 0ext 9ataSo/rce: list9ataSo/rce 9ataMem&er: n/ll

string%( list0ataSource 1 4 apple - peach - pump6in 5/ te*t)o*B.0ata)indings.'dd+ Te*t - list0ataSource- null./

When you bind to a list data source' only one item is displayed at a time' as shown in #igure 13.9. Figure 1 .4. ,imple Data "inding to a $ist Data ,ource

5imilarly' you%re allowed to bind to a particular property of the ob<ects in the list data source by specifying a data member:
.. Control: text+ox5 .. Gro#ertyBame: 0ext .. 9ataSo/rce: list9ataSo/rce .. 9ataMem&er: Len*th text+ox5.9ata+in in*s.) ("0ext", list9ataSo/rce, "Len*th");

0gain' this techni)ue displays only a single item at a time' as shown in #igure 13.10. Figure 1 .16. ,imple Data "inding to a Propert! of a $ist Data ,ource

Before we loo$ at how to display other items from a list data set using simple binding' let%s tal$ about binding to a data set.

,imple "inding to Data ,ets


0lthough arrays and other $inds of list data sources are useful' the most popular list data source is the data set. Jou can bind to columns in a table from a data set in two ways:
.. Fill the ata set c/stomers) a#ter.Fill(c/stomerSet5, "C/stomers"); or ers) a#ter.Fill(c/stomerSet5, "Dr ers");
// B. )ind to data set 7 ta!le.column +good2.

text+ox5.9ata+in in*s.) ( "0ext", customerSetB- Customers.ContactTitleName );


// G. )ind the ta!le 7 column +)'02.

text+ox5.9ata+in in*s.) ( "0ext", customerSetB.Customers-

ContactTitleName

);

"ither way you do the binding' what you%ll see is shown in #igure 13.11. Figure 1 .11. ,imple "inding to a Data ,et

0lthough both techni)ues of binding to a column seem to display e)uivalent results' there is an &issue& in .!"T 1.( that causes inconsistencies if you mi( the two methods. The reason to always use techni)ue 1 3data set ; table.column4 is that the Aesigner/generated code uses this techni)ue when you choose a data member in the 8roperty Browser' as shown in #igure 13.1>. Figure 1 .12. 1dding a Data "inding in the Propert! "rowser

The AataBindings property in the 8roperty Browser allows you to add bindings without writing the code manually. When you choose a column from a data set hosted on your form' the generated code will use the data set ; table.column techni)ue:
$oi 6nitiali7eCom#onent() " ... this.text+ox5.9ata+in in*s.) ( ne! +in in*( "0ext", this.customerSetB- Customers.ContactTitleName .); ... -

Jou can feel free to add bindings in custom code or to use the 8roperty Browser' whichever you find more convenient. But if you also use the data set ; table.column techni)ue' you%ll avoid the inconsistencies caused by data binding support in Win#orms 1.(. 5o' as convenient as the 8roperty Browser is for generating the binding code for us' it doesn%t seem very useful to show only a single value from a list data source. 6owever' when you understand binding managers 3discussed ne(t4' simple binding to list data sources can be very useful.

"inding 8anagers
#or every bound data source' the infrastructure creates a binding manager for you. 5inding managers manage a set of bindings for a particular data source and come in two flavors: property managers and currency managers. 0 propert# manager is an instance of the 8roperty anager class and is created for an item data source. 0 c&rrenc# manager is an instance of the +urrency anager class and is created for a list data source. Both of these managers are implementations of the abstract base class Binding anagerBase:
a&stract class +in in*Mana*er+ase " .. Constr/ctors #/&lic +in in*Mana*er+ase();

.. Gro#erties #/&lic +in in*sCollection +in in*s " *et; #/&lic int Co/nt " $irt/al *et; #/&lic o&'ect C/rrent " $irt/al *et; #/&lic int Gosition " $irt/al *et; $irt/al set; .. ($ents #/&lic e$ent ($entHan ler C/rrentChan*e ; #/&lic e$ent ($entHan ler GositionChan*e ; .. Metho s #/&lic $irt/al #/&lic $irt/al #/&lic $irt/al #/&lic $irt/al #/&lic $irt/al #/&lic $irt/al $oi $oi $oi $oi $oi $oi ) Be!(); CancelC/rrent( it(); (n C/rrent( it(); 2emo$e)t(int in ex); 2es/me+in in*(); S/s#en +in in*();

*t%s the <ob of the binding manager.whether it%s a property binding manager or a currency binding manager.to $eep trac$ of all the bindings between all the controls bound to the same data source. #or e(ample' it%s the binding manager%s <ob 3through the bindings4 to $eep all the controls up/to/date when the underlying data source changes and vice versa. To access a data source%s binding manager' you can retrieve a binding for a bound property and get the binding manager from the binding%s Binding anagerBase property:
$oi #osition+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "
// Ket the !inding )inding !inding 1 te*t)o*B.0ata)indings% Te*t (/ // Ket the !inding manager )inding$anager)ase manager 1 !inding.)inding$anager)ase/ // Ket current position int pos 1 manager.Position/

Messa*e+ox.Sho!(#os.0oStrin*(), "C/rrent Gosition");

!otice that after the binding manager is retrieved' the code chec$s the current position. This is $nown as the binding manager%s c&rrenc#* which is the current position in its list of items. #igure 13.13 shows how a currency manager maintains the position into a list data source. Figure 1 .1 . 1 Currenc! 8anager 8aintaining a Position into a $ist Data ,ource

0lthough only a currency manager manages an actual list' for simplicity the position is pushed into the base binding manager for both the currency manager and the property manager. This allows easy access to the current position for both $inds of binding managers through the Binding anagerBase class. #or a property manager' which manages the data source of only a single ob<ect' the position will always be =ero' as shown in #igure 13.1H. Figure 1 .1#. 1 Propert! 8anager 8anagers *nl! a ,ingle +tem

#or a currency manager' the position can be changed' and that will update all the bindings to a new &current& ob<ect in a list data source. This arrangement allows you to build controls that let your users &scroll& through a list data source' even when you%re using simple binding' as shown in #igure 13.1F. Figure 1 .1'. 8anaging Currenc!

The following code shows how to implement the list data source navigation buttons using the Binding anagerBase 8osition property:
$oi C/rrencyForm%Loa (o&'ect sen er, ($ent)r*s e) " +in in* &in in* 1 text+ox5.9ata+in in*sI"0ext"J;

.. Fill the ata set c/stomers) a#ter.Fill(c/stomerSet5, "C/stomers"); $oi


)inding !inding 1 te*t)o*B.0ata)indings% Te*t (/ )inding$anager)ase manager 1 !inding.)inding$anager)ase/ // Ket current position int pos 1 manager.Position/

#osition+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

Messa*e+ox.Sho!(#os.0oStrin*(), "C/rrent Gosition"); start+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

$oi

// "eset the position )inding !inding 1 te*t)o*B.0ata)indings% Te*t (/ )inding$anager)ase manager 1 !inding.)inding$anager)ase/ manager.Position 1 A/

$oi
// 0ecrement the position )inding !inding 1 te*t)o*B.0ata)indings% Te*t (/ )inding$anager)ase manager 1 !inding.)inding$anager)ase/ ??manager.Position/ // No need to worry a!out !eing <A

#re$io/s+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

$oi next+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "


// ;ncrement the position )inding !inding 1 te*t)o*B.0ata)indings% Te*t (/ )inding$anager)ase manager 1 !inding.)inding$anager)ase/ 77manager.Position/ // No need to worry a!out !eing =Count

$oi
// Set position to end )inding !inding 1 te*t)o*B.0ata)indings% Te*t (/ )inding$anager)ase manager 1 !inding.)inding$anager)ase/ manager.Position 1 manager.Count ? B/

en +/tton%Clic4(o&'ect sen er, ($ent)r*s e) "

Ta$ing this a step further' #igure 13.1S shows two te(t bo(es bound to the same data source but in two different columns. Figure 1 .1.. Two Controls "ound to the ,ame Data ,ource

0fter the binding is in place for both controls' changing the position on the binding conte(t for one will automatically affect the other. #igure 13.1I shows the currency manager and the logical bindings for the two te(t controls.

Figure 1 .10. Two Controls "ound to the ,ame Data ,ource ,haring the ,ame Currenc! 8anager

"stablishing the bindings for the two te(t bo(es wor$s as you%d e(pect:
$oi 6nitiali7eCom#onent() " ...
this.te*t)o*B.0ata)indings.'dd+ new )inding+ Te*t - this.customerSetB- Customers.ContactTitle ../

...
this.te*t)o*G.0ata)indings.'dd+ new )inding+ Te*t - this.customerSetB- Customers.ContactName ../

... -

0 binding manager is per data source' not per binding. This means that when the same data set and data table are used' the two te(t bo(es get routed through the same currency manager to the same data source. 0s the currency manager%s position changes' all the controls bound through the same currency manager get updated appropriately.
L3M L3M

Technically' a binding manager is uni)ue per data source only within a binding conte%t' which holds groups of binding managers. "very control

can have its own binding conte(t' and that allows two controls in the same container to show the same data source but different currencies. 0lthough it%s interesting' the need for multiple binding conte(ts is rare and not e(plored further in this boo$.

Because all controls bound against the same data source share access to the same binding manager' getting the binding manager from either control yields the same binding manager:
.. Chec4 the &in in* mana*er
)inding$anager)ase managerB 1 te*t)o*B.0ata)indings% Te*t (.)inding$anager)ase/ )inding$anager)ase managerG 1

te*t)o*G.0ata)indings% Te*t (.)inding$anager)ase/

.. )ssert that these controls are &o/n to the .. same ata so/rce System.9ia*nostics.9e&/*.)ssert(mana*er5 11 mana*er@);

*n fact' the binding managers are shared at the container level' so you can go to the containing form%s Binding+onte(t collection to get the binding manager:
+in in*Mana*er+ase mana*er 1 this.+in in*ContextIc/stomerSet5, "C/stomers"J; .. 5: *oo 3

0ccess to the binding manager from the Binding+onte(t property is another place where there%s an overload that can ta$e a data set and a table name or only a table:
+in in*Mana*er+ase mana*er 1 this.+in in*ContextIcustomerSetB.CustomersJ; .. @: +)93

This is another case of techni)ue 1 versus techni)ue > for specifying the data source. *f you use techni)ue 1' all the binding managers will be shared as planned' $eeping everything in sync. 6owever' if #o& &se tec"ni0&e 6* #o&7ll get bac! a different binding manager* and t"is means t"at t"ings on7t be in s#nc "en #o& manip&late t"e binding manager. This is the issue that data binding has in .!"T 1.(' and you%d be wise to avoid it by always stic$ing to techni)ue 1.

Current Data /ow


The 8osition property is fine when it comes to navigating the rows currently shown' but it%s not good for finding the current row in a data table. The problem is that as soon as items are mar$ed as deleted in the underlying data table' they%re automatically hidden from view by the binding infrastructure 3<ust what we want to have happen4. 6owever' deleted rows are still there at e(actly the same offset' and this means that the offsets will be mismatched between the underlying data table and the binding manager' which &counts& rows that haven%t been deleted. 7uc$ily' the Binding anagerBase class provides a +urrent property' which always provides access to the current row in the data table regardless of the mismatch between the offsets:
$oi sho!+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " .. Eet the &in in* mana*er +in in*Mana*er+ase mana*er 1 this.+in in*ContextIc/stomerSet5, "C/stomers"J;
// Ket the current row #ia the #iew 0ata"owLiew #iew 1 +0ata"owLiew.manager.Current/ CustomerSet.Customers"ow row 1 +CustomerSet.Customers"ow.#iew."ow/

Messa*e+ox.Sho!(ro!.Contact0itleBame);

Binding anagerBase%s +urrent property is of type 9b<ect' so it must be cast to a specific type. !ormally' that type will be the type you%d e(pect when you set up the binding. #or e(ample' when you bind to a string' the +urrent property will return an ob<ect of type string. 6owever' in the case of Aata5ets' the AataTable data source e(poses Aata@ow-iew ob<ects instead of Aata@ow ob<ects 3to tailor the view of a row when it%s displayed4. Jou%ll learn more about Aata@ow-iew later in this chapter' but the tric$ is to get the current row from the @ow property of the current Aata@ow-iew.

Changes to the Data ,et


0dding data to the underlying data wor$s in pretty much the same way it did before we started using data binding:
$oi a +/tton%Clic4(o&'ect sen er, ($ent)r*s e) " .. ) a ne! ro! C/stomerSet.C/stomers2o! ro! 1 this.c/stomerSet5.C/stomers.Be!C/stomers2o!(); ro!.C/stomer69 1 "S(LLS+"; ... this.c/stomerSet5.C/stomers.) C/stomers2o!(ro!);
// Controls automatically updated // +e*cept some controls- e.g. Cist)o*- need a nudge. // NJTE3 Ise C# as cast to as6 if the !inding // manager is a currency manager. ;f the // result of the as cast is null- then // we:#e got a property manager- which // doesn:t ha#e a "efresh method Currency$anager manager 1 this.)indingConte*t%customerSetB- Customers ( as Currency$anager/ if+ manager 21 null . manager."efresh+./

0dding a new row to the data set causes most controls to be updated' e(cept for some comple( bound controls li$e the 7istBo(. #or those cases' we cast the Binding anagerBase class to a +urrency anager to be able to call the +urrency anager/ specific @efresh method' which will bring those pes$y list bo(es up to speed on the new row. 0s far as updates and deletes go' ma$e sure to use the Binding anagerBase class%s +urrent property instead of the 8osition property to get the current row. 9therwise' you could access the wrong row:
$oi /# ate+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " .. <# ate the c/rrent ro!
)inding$anager)ase manager 1 this.)indingConte*t%customerSetB- Customers (/ 0ata"owLiew #iew 1 +0ata"owLiew.manager.Current/ CustomerSet.Customers"ow row 1 +CustomerSet.Customers"ow.#iew."ow/

ro!.Contact0itle 1 "C(D";

// Controls automatically updated

$oi elete+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " .. Mar4 the c/rrent ro! as elete
)inding$anager)ase manager 1 this.)indingConte*t%customerSetB- Customers (/ 0ata"owLiew #iew 1 +0ata"owLiew.manager.Current/ CustomerSet.Customers"ow row 1 +CustomerSet.Customers"ow.#iew."ow/

ro!.9elete();
// Controls automatically updated // +no special treatment needed to a#oid deleted rows2.

!otice that no code is needed to update the controls to ta$e into account updated or deleted rows. T"is is t"e po er of data binding. When the underlying data source is updated' we don%t want to be forced to update the related controls manually. *nstead' we bind them and let that happen automatically.
LHM LHM

!ot all data sources are as good about $eeping the bound controls up/to/date as the Aata5et/related data sources are. #or the re)uirements of a

good custom data source' see the &+ustom Aata 5ources& section later in this chapter.

/eplacing the Data ,et 5ometimes' after all the data bindings are established' it%s necessary to replace a data set with a different data set. #or e(ample' if you%re calling into a Web service that produces a Aata5et as the return type' you might find yourself writing code li$e this:
9ataSet ne!9ataSet 1 myWe&Ser$ice.Eet9ataSet(); this. ataSet5 1 ne!9ataSet; .. 9ata &in in*s lost

1nfortunately' when you replace the e(isting data set with a new one' all the data bindings remain with the old data set. 0s a shortcut' instead of manually moving all the data bindings to the new data set' you can use the erge method of the Aata5et class to bring the data from the new data set into the e(isting one:
9ataSet ne!9ataSet 1 myWe&Ser$ice.Eet9ataSet(); this. ataSet5.Clear(); this. ataSet5.Mer*e(ne!9ataSet); .. 9ata &in in*s 4e#t

The tric$ here is that all the data bindings continue to be bound to the e(isting data set' but the data from the e(isting data set is cleared and replaced with the data from the new data set. #or this to wor$' all the schema information in the new data set' such as the names of the columns' must match the schema information that the bindings are using. 9therwise' you%ll get run/time e(ceptions.

0s an optimi=ation when merging data sets' you%ll want to temporarily turn off data binding so that you don%t have to update all the bound controls after when the data set is cleared and again when the new data is merged:
9ataSet ne!9ataSet 1 myWe&Ser$ice.Eet9ataSet();
)inding$anager)ase manager 1 this.)indingConte*t%dataSetB- Customers (/ manager.Suspend)inding+./

this. ataSet5.Clear(); this. ataSet5.Mer*e(ne!9ataSet); .. 9ata &in in*s 4e#t


manager."esume)inding+./

To turn off the updates that bindings cause as the data set is updated' we suspend the binding by using a call to the binding manager%s 5uspendBinding method. Then' after we clear and merge the data set' to resume updates to the bound controls we resume the binding with the call to @esumeBinding. Temporarily suspending bindings isn%t for use only with data setsP it%s useful whenever the controls shouldn%t be updated until a set of operations on the underlying data source has been performed.

Changes to the Control


Deeping the controls updated when the data source changes is half the story of data binding. The other half is $eeping the underlying data source up/to/date as the data in the controls changes. By default' any change in the control%s data will be replicated when the position within the currency manager is changed. #or e(ample' in #igure 13.18' even though the data in the Te(tBo( has changed' the underlying row 3as shown in the message bo(4 has not' in spite of the change in focus that occurs when you move to the 5how button. Figure 1 .12. $osing Focus Does (ot Trigger an %nd to the %dit

The reason that the te(t bo( hasn%t pushed the new data to the current row is that the c&rrent edit hasn%t yet ended. When data in a control changes' that represents an &edit& of

the data. 9nly when the current edit has ended does the data get replicated to the underlying data source. *f you don%t li$e waiting for the currency to change before the current edit ta$es effect' you can push it through yourself using the "nd+urrent"dit method of the Binding anagerBase:
$oi
// Force the current edit to end

text+ox5%Lea$e(o&'ect sen er, ($ent)r*s e) "

+in in*Mana*er+ase mana*er 1 this.+in in*ContextIc/stomerSet5, "C/stomers"J;


manager.EndCurrentEdit+./

This causes the control whose data is being edited to flush it to the underlying data source immediately. *f' on the other hand' you%d li$e to replace the dirty data with the data currently in the data source' you can use +ancel+urrent"dit instead:
.. Cancel the c/rrent e it +in in*Mana*er+ase mana*er 1 this.+in in*ContextIc/stomerSet5, "C/stomers"J;
manager.CancelCurrentEdit+./

Custom Formatting and Parsing 5ometimes the data as shown in the control is already formatted automatically the way you would li$e to see it. 6owever' if you%d li$e to change the formatting' you can do so by handling the #ormat event of the Binding ob<ect:
$oi C/rrencyForm%Loa (o&'ect sen er, ($ent)r*s e) "

// Custom?format the ContactTitle // NJTE3 su!scri!e to this !efore filling the data set )inding !inding 1 te*t)o*B.0ata)indings% Te*t (/ !inding.Format 71 new Con#ertE#ent,andler+te*t)o*B8Format./

.. Fill the ...

ata set

#oid te*t)o*B8Format+o!9ect sender- Con#ertE#ent'rgs e. 4 // Show ContactTitle as all caps e.Lalue 1 e.Lalue.ToString+..ToIpper+./ 5

The #ormat event handler gets a +onvert"vent0rgs parameter:


class Con$ert($ent)r*s : ($ent)r*s " .. Constr/ctors #/&lic Con$ert($ent)r*s(o&'ect $al/e, 0y#e .. Gro#erties
pu!lic Type 0esiredType 4 get/ 5 pu!lic o!9ect Lalue 4 get/ set/ 5

esire 0y#e);

The -alue property of the +onvert"vent0rgs class is the unconverted value that will be shown if you don%t change it. *t%s your <ob to convert the current -alue property 3which will be of the same type as the underlying data4 to a value of the AesiredType 3string4' formatting it as you please. *f you are binding to a read/only property' this is all that is involved in getting what you need. *f you e(pect the data to be pushed bac$ to the data source' you should also trap the 8arse event of the binding. This allows you to undo the formatting as the data is replicated from the control bac$ to the data source:
$oi C/rrencyForm%Loa (o&'ect sen er, ($ent)r*s e) " .. C/stom:,ormat an #arse the Contact0itle .. BD0(: s/&scri&e to these &e,ore ,illin* the ata set +in in* &in in* 1 text+ox5.9ata+in in*sI"0ext"J; &in in*.Format ;1 ne! Con$ert($entHan ler(text+ox5%Format);
!inding.Parse 71 new Con#ertE#ent,andler+te*t)o*B8Parse./

.. Fill the ...

ata set

#oid te*t)o*B8Parse+o!9ect sender- Con#ertE#ent'rgs e. 4 // Con#ert ContactTitle to mi*ed case e.Lalue 1 $i*edCase+e.Lalue.ToString+../ 5

8arsing is the opposite of formatting. The type of -alue will be string' and AesiredType will be the type needed for the underlying data source. -alue will start as the data from the control' and it%s your <ob to convert it.

Comple3 Data "inding


+omple( binding and simple binding are more ali$e than not. 0ll the considerations discussed so far about using binding managers and $eeping the control and data source in sync apply e)ually well to comple( binding as they do to simple binding. 6owever' unli$e simple binding' a control that supports comple( binding must do so in a custom fashion. *t does so by e(posing a custom property to specify a data source 3typically called Aata5ource4 and =ero or more properties for specifying data members. #or e(ample' the list control family of controls.7istBo(' +hec$ed7istBo(' and +omboBo(.e(poses the following properties to support comple( data binding:

0ataSource.

This ta$es a list data source that implements *7ist or *7ist5ource. This includes arrays' 0rray7ist data tables' and any custom type that supports one of these interfaces.
LFM LFM

The implementation of the *7ist5ource interface is what allows a AataTable to e(pose multiple Aata@ow-iew ob<ects as data

sources.

0isplay$em!er .

This is the name of the property from the data source that the list control will display 3defaults to -alue ember' if that%s set' or To5tring otherwise4. Lalue$em!er. This is the name of the property from the data source that the list control will use as the value of the 5elected-alue property 3defaults to the currently selected item4. SelectedLalue. This is the value of the -alue ember property for the currently selected item. Selected;tem. This is the currently selected item from the list data source. Selected;tems. This is the currently selected items for a multiselect list control.

#or the list controls' you must set at least the Aata5ource and the Aisplay ember:
this.list+ox5.9ataSo/rce 1 this.c/stomerSet5; this.list+ox5.9is#layMem&er 1 "C/stomers.Contact0itleBame";

This is another case of remembering to set the data source to the data set and to set the display member 3and value member4 to table.column. 9therwise' you can end up with mismatched binding managers. @emembering to ma$e these settings is especially important because' unli$e simple binding' comple( binding using techni)ue > is allowed in the 8roperty Browser' as shown in #igure 13.19. Figure 1 .14. Don?t 7se the dataset.ta9le F Column TechniGue to ,pecif! the Data ,ource

*nstead' ma$e sure that you pic$ a data set for the Aata5ource property and pic$ a table.column for the Aisplay ember and -alue ember properties' as shown in #igure 13.>0. Figure 1 .26. 7se the Dataset F ta9le.column TechniGue to ,pecif! a Data ,ource

0fter you%ve set the data source and the display member' you%ll get an automatically populated list control' <ust as we%ve been pining for since this chapter began 3and as shown in #igure 13.>14. Figure 1 .21. 7sing Data "inding to Populate a $ist Control

*n addition to a nicely filled list bo(' notice that #igure 13.>1 shows the data for the same row in both of the te(t bo(es as in the currently selected list bo( item. 0s the list bo( selection changes' the position is updated for the shared currency manager. 1sing the -+@/style buttons would li$ewise change the position and update the list bo(%s selection accordingly. 0lso' notice that the status bar is updated with the +ustomer*A of the current row as the position changes. Jou do this using the 5elected*tem property of the list control' which e(poses the currently selected item:
$oi list+ox5%Selecte 6n exChan*e (o&'ect sen er, ($ent)r*s e) " i,( list+ox5.Selecte Cal/e 11 n/ll ) ret/rn;
// Ket the currently selected row:s Customer;0 using current item 0ata"owLiew #iew 1 +0ata"owLiew.list)o*B.Selected;tem/ CustomerSet.Customers"ow row 1 +CustomerSet.Customers"ow.#iew."ow/

stat/s+ar5.0ext 1 "Selecte

C/stomer691 " ; ro!.C/stomer69;

0s a convenience' you can set the -alue ember property to designate a property as the value of the selected row. This is useful for primary $eys when you want to $now what was selected without $nowing any of the other details. 1sing the -alue ember property' you can directly e(tract the *A of the currently selected row:
$oi 6nitiali7eCom#onent() " ...
this.list)o*B.Lalue$em!er 1 Customers.Customer;0 /

...

$oi list+ox5%Selecte 6n exChan*e (o&'ect sen er, ($ent)r*s e) " i,( list+ox5.Selecte Cal/e 11 n/ll ) ret/rn;
// Ket the currently selected row:s Customer;0 status)arB.Te*t 1 Selected Customer;01 7 list)o*B.SelectedLalue/

Data =iews
5o far we%ve discussed how to show specific data members' such as specific columns' from a data source. 6owever' it%s also useful to filter which rows from a data table are shown as well as to sort the rows. This $ind of functionality is provided by a data vie ' which allows you to transform a data table as it%s being displayed in ways that the #ormat and 8arse events can%t. ,orting Jou can sort on any number of columns in either ascending or descending order. 5etting the sort criteria is a matter of creating an instance of the Aata-iew class' setting the 5ort property' and then binding to the view as the data source:
$oi
// Create a sorting #iew 0ataLiew sortLiew 1 new 0ataLiew+customerSetB.Customers./ sortLiew.Sort 1 ContactTitle 'SC- ContactName 0ESC /

C/rrencyForm%Loa (o&'ect sen er, ($ent)r*s e) "

.. +in to the $ie! list+ox5.9ataSo/rce 1 sortCie!; list+ox5.9is#layMem&er 1 "Contact0itleBame"; .. Fill the ... ata set

!otice that the Aata-iew ob<ect ta$es a AataTable argument so that it $nows where to get its data. !otice also the 05+ 3default4 and A"5+ designators' which indicate ascending and descending sort order' respectively. Binding to the sorted view of our customers table yields #igure 13.>>. Figure 1 .22. "inding to a ,ort =iew

Filtering 5imilarly' filtering is a matter of creating an instance of the Aata-iew class' setting the @ow#ilter property' and binding to the view:
$oi C/rrencyForm%Loa (o&'ect sen er, ($ent)r*s e) " .. Create a ,ilterin* $ie! 9ataCie! ,ilterCie! 1 ne! 9ataCie!(c/stomerSet5.C/stomers);
filterLiew."owFilter 1 ContactTitle 1 :Jwner: /

.. +in to the $ie! list+ox5.9ataSo/rce 1 ,ilterCie!; list+ox5.9is#layMem&er 1 "Contact0itleBame"; .. Fill the ... ata set

Binding to this view of our customers data table shows only rows where the +ontactTitle column has the value of &9wner'& as shown in #igure 13.>3. Figure 1 .2 . "inding to a Filtered =iew

The e(pression language used for filtering is a subset of 5Y7. Jou can find a lin$ to the documentation in the description of the Aata-iew class%s @ow#ilter property.

8aster)Detail /elations
While we%re on the sub<ect of filtering' one of the most popular ways to filter what%s shown in one control is based on the current selection in another control. #or e(ample' when a customer is selected in the top list bo( of #igure 13.>H' the bottom list bo( shows only the related orders for that customer. Figure 1 .2#. 8aster)Detail /elations

@ecall from +hapter 1>: Aata 5ets and Aesigner 5upport that we implemented this functionality by repopulating the orders list bo( whenever the selection in the customers list bo( changed. The order list bo( populating code uses a relation to get the child order rows from the currently selected parent customer row:

.. Eet re,erences to the ta&les 9ata0a&le c/stomers 1 ataset.0a&lesI"C/stomers"J; 9ata0a&le or ers 1 ataset.0a&lesI"Dr ers"J;
// Create the relation 0ata"elation relation 1 new 0ata"elation+ CustomersJrders customers.Columns% Customer;0 (orders.Columns% Customer;0 (./ // 'dd the relation dataset."elations.'dd+relation./ ...

$oi Go#/lateChil List+ox() " .. Clear the list &ox or ersList+ox.6tems.Clear();


// Ket the currently selected parent custom row int inde* 1 customersCist)o*.Selected;nde*/

i,( in ex 11 :5 ) ret/rn;
// Ket row from data set 0ata"ow parent 1 dataset.Ta!les% Customers (."ows%inde*(/

.. (n/merate chil ro!s ,oreach( 9ata2o! ro! in ... -

parent.KetChild"ows+ CustomersJrders .

) "

*nstead of our writing this code by hand' data binding allows us to bind to a relation. Then as the selection changes in the parent control' the child control is automatically populated with only the related child rows. The format for specifying the data member for a relation is the following:
#arent0a&le.relationBame.chil Col/mn

5pecifying a parent/child relationship in this manner allows us to use a binding to display master/detail data.
$oi 6nitiali7eCom#onent() " ...
this.ordersCist)o*.0isplay$em!er 1 Customers.CustomersJrders.Jrder;0 /

...

When we use a typed data set to establish a relation' the 8roperty Browser provides a list of possible relation bindings' as shown in #igure 13.>F. Figure 1 .2'. Composing a Data 8em9er from a /elation in the Propert! "rowser

*n addition' you aren%t limited to a single level of master/detail binding. Jou can have any number of relations from parent to child to grandchild' and so on. The combination of -5.!"T design/time features' typed data sets' and data binding has reduced the amount of handwritten code in the sample shown in #igure 13.>H to the following:
$oi Master9etailForm%Loa (o&'ect sen er, ($ent)r*s e) " .. Fill the ata set c/stomers) a#ter.Fill(c/stomerSet5, "C/stomers"); or ers) a#ter.Fill(c/stomerSet5, "Dr ers"); $oi a 2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " .. ) a ne! ty#e ro! C/stomerSet.C/stomers2o! ro! 1 this.c/stomerSet5.C/stomers.Be!C/stomers2o!(); ro!.C/stomer69 1 "S(LLS+"; ... this.c/stomerSet5.C/stomers.) C/stomers2o!(ro!); .. Controls a/tomatically /# ate .. (exce#t some controls, e.*. List+ox, nee a n/ *e) C/rrencyMana*er mana*er 1 this.+in in*ContextIc/stomerSet5, "C/stomers"J as C/rrencyMana*er; i,( mana*er 31 n/ll ) mana*er.2e,resh(); $oi /# ateSelecte 2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " .. <# ate the c/rrent ro! +in in*Mana*er+ase mana*er 1 this.+in in*ContextIc/stomerSet5, "C/stomers"J; 9ata2o!Cie! $ie! 1 (9ata2o!Cie!)mana*er.C/rrent; C/stomerSet.C/stomers2o! ro! 1 (C/stomerSet.C/stomers2o!)$ie!.2o!; ro!.Contact0itle 1 "C(D"; $oi eleteSelecte 2o!Men/6tem%Clic4(o&'ect sen er, ($ent)r*s e) " .. Mar4 the c/rrent ro! as elete +in in*Mana*er+ase mana*er 1 this.+in in*ContextIc/stomerSet5, "C/stomers"J; 9ata2o!Cie! $ie! 1 (9ata2o!Cie!)mana*er.C/rrent; C/stomerSet.C/stomers2o! ro! 1 (C/stomerSet.C/stomers2o!)$ie!.2o!; ro!.9elete();

The rest' including $eeping both list bo(es up/to/date as the underlying data changes and synchroni=ing the orders list bo( based on the selection in the customers list bo(' is all written for us.

#ata /rids
0lthough the preceding sample relies heavily on .!"T and -5.!"T data features to reduce our coding' the list controls can ta$e us only so far toward data/centric application nirvana. #or e(ample' the list bo( supports the display only of a single column from the underlying data set and provides no built/in 1* for adding' editing' or deleting rows. #or those features and a boatload of others' we%ve got the data grid. The AataGrid control supports comple( binding to list data sources. 6owever' it does it a lot more thoroughly than do the list controls discussed so far. #or one thing' a data grid can show all the columns' as shown in #igure 13.>S. Figure 1 .2.. "inding to a Data:rid

Binding a data source to the AataGrid control re)uires setting the Aata5ource and Aata ember properties 3again' watch out for setting more than <ust the data set in the Aata5ource property4:
$oi 6nitiali7eCom#onent() " ... ... -

this.dataKridB.0ata$em!er 1 Customers / this.dataKridB.0ataSource 1 this.customerSetB/

The data grid supports showing not only multiple columns but also multiple tables. *f you change your data set to include multiple tables and relationships between the tables' your grid will loo$ li$e the one in #igure 13.>I.

Figure 1 .20. ,howing )D Data in a Data :rid

When you clic$ on the small plus sign on the grid' you can see lin$s to all the relations' as shown in #igure 13.>8. Figure 1 .22. ,howing /elations

+lic$ing on the lin$ brings you the related rows' shown in #igure 13.>9. Figure 1 .24. Drilling Through /elations

Jou can now see and navigate through the child rows of that relationship. 0s a convenience' the data grid also shows a history of the parent rows that you have navigated from.

Formatting Data :rids


*n spite of the ability a data grid gives you to drill down into relations' the default layout of the data grid is not very useful. #or e(ample' we don%t really want to show primary $ey identifiers to normal humans. The way to control the loo$ and feel of the data grid is to use data grid table styles. Data grid table st#les 3or <ust st#les4 allow you to control which specific columns are shown' as well as column alignment' width' and header te(t and things li$e colors and fonts. 7i$e most other things in .!"T' styles are available via an ob<ect model' but the model is sufficiently complicated that * want to brea$ with tradition and introduce you directly to AataGridTable5tyle +ollection "ditor' available from the Table5tyles property of the 8roperty Browser.
LSM LSM

*f you feel the need to learn the data grid styles ob<ect model at the code level' * suggest starting with the code generated by the style editor' if

for no other reason than it ma$es a wonderful data grid table styles macro editor.

1sing the style editor' you%ll want to create a set of styles for each data table in the grid' as shown in #igure 13.30. Figure 1 . 6. Data:ridTa9le,t!le Collection %ditor

#or each table style' you set the apping!ame property' choosing the name of the data table to which to apply the styles. By default' a new table style has no columns at all. To add columns' bring up AataGrid+olumn5tyle +ollection "ditor using the Grid+olumn5tyle property for each table. The column style editor is shown in #igure 13.31 with a few columns added. Figure 1 . 1. Data:ridColumn,t!le Collection %ditor with 1dded Columns

0gain' when you add a column' you specify the apping!ame' this time choosing the column name. Jou%ll probably also want to specify header te(t' or else the column will be blan$. Width and alignment are other things you%ll want to consider. #igure 13.3> shows an e(ample of a data grid after some table and grid styles have been built. Figure 1 . 2. 1 ,t!lish Data :rid

*f you%d li$e something even fancier' you can format a data grid using the various color/ and font/related properties on the data grid ob<ect itself. 9r' if you don%t want to spend the afternoon getting those settings <ust right' you can clic$ the 0uto #ormat lin$ in the lower/ right corner of the the 8roperty Browser for a data grid. This opens the 0uto #ormat dialog' where you can choose from an assortment of prefabricated formats' as shown in #igure 13.33. Figure 1 . . The Data :rid 1uto Format Dialog

!ow' instead of spending the afternoon twea$ing individual data grid properties' you can spend the afternoon choosing among the prepac$aged sets of data grid properties. 9r' if you%d li$e to return to the minimalist view' you can choose the Aefault format' which resets the data grid style properties to the defaults.

Data %3change and Data :rids


1nli$e the list controls' the data grid provides direct support for adding' updating' and deleting rows in the underlying data source. *f you don%t plan to replicate the data bac$ to a data source 3as discussed in +hapter 1>: Aata 5ets and Aesigner 5upport4' you may want to set the @ead9nly property of the data grid to true 3it defaults to false4. This will remove the capability to update the data.

"ringing +t 1ll Together


#igure 13.3H is an e(ample of the $ind of form you can produce using typed data sets' relations' e(pressions' the -5.!"T data integration' master/detail' data binding' and data grids. Figure 1 . #. 1n %3ample of What WinForms Pro&ides for Data Programmers

0ll the controls in this e(ample bind to the same data source 3customer5et1.+ustomers4. Because they all use the same data source' the -+@ navigation buttons 3upper right4 move the currency for all the data bound controls simultaneously. 0lso' this e(ample uses two master/detail bindings to allow you to show the orders for the current customer and the order details for the current order. With data binding' the amount of code you will need to write to get this $ind of core functionality wor$ing is minimal:
$oi $oi $oi $oi $oi &/ttonH%Clic4(o&'ect sen er, ($ent)r*s e) "
)indingConte*t%customerSetB- Customers (.Position 1 )indingConte*t%customerSetB- Customers (.Count ? B/ ??)indingConte*t%customerSetB- Customers (.Position/ )indingConte*t%customerSetB- Customers (.Position 1 A/ s>l0ata'dapterB.Fill+customerSetB./

F/ll9ataEri sForm%Loa (o&'ect sen er, ($ent)r*s e) "

&/tton5%Clic4(o&'ect sen er, ($ent)r*s e) "

&/tton@%Clic4(o&'ect sen er, ($ent)r*s e) " &/ttonN%Clic4(o&'ect sen er, ($ent)r*s e) "

77)indingConte*t%customerSetB- Customers (.Position/

9f course' there%s a whole lot more code behind #igure 13.3H than only these few lines' but the bul$ of it is generated for you by -5.!"T and provided by the Win#orms data binding infrastructure. That' after all' is the whole point of data binding in the first place.

Custom #ata Sources


0lthough the data set is a popular data source' it%s by no means the only one. 0ny custom type can be a data source.

Custom +tem Data ,ources


The re)uirements of an item data source are only that it e(pose one or more public properties:
.. (x#ose t!o #ro#erties ,or &in in* class Bame)n B/m&er " #/&lic strin* Bame " *et " ret/rn name; set " name 1 $al/e; #/&lic int B/m&er " *et " ret/rn n/m&er; set " n/m&er 1 $al/e; strin* name 1 "Chris"; int n/m&er 1 HA@;

Bame)n B/m&er so/rce 1 ne! Bame)n B/m&er(); $oi C/stom6tem9ataSo/rceForm%Loa (o&'ect sen er, ($ent)r*s e) "
// )ind to pu!lic properties te*t)o*B.0ata)indings.'dd+ Te*t - source- Name ./ te*t)o*G.0ata)indings.'dd+ Te*t - source- Num!er ./

*n this case' we%ve created a simple class that e(poses two public properties and binds each of them to the Te(t property of two te(t bo(es' as shown in #igure 13.3F. Figure 1 . '. "inding to a Custom +tem Data ,ource

The binding wor$s <ust as it did when we implemented binding against columns in a table. +hanges in the bound control will be used to set the value of readCwrite properties on the ob<ect. 6owever' by default' the binding doesn%t wor$ the other wayP changes to the data source ob<ect won%t automatically be reflected to the bound controls. "nabling that re)uires the data source to e(pose and fire an event of type "vent6andler to which the binding can subscribe. "ach property that must notify a bound control of a change must e(pose an event using the following naming convention:
#/&lic e$ent ($entHan ler
<propertyName=Changed;

When firing' the data source ob<ect passes itself as the sender of the event:
class Bame)n B/m&er "
// For !ound controls pu!lic e#ent E#ent,andler NameChanged/

#/&lic strin* Bame " *et " ret/rn name; set " name 1 $al/e; ... -

// Notify !ound control of changes if+ NameChanged 21 null . NameChanged+this- E#ent'rgs.Empty./

!ow' when the data source ob<ect%s properties are changed' bound controls are automatically updated:
$oi setBame+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "
// Changes replicated to te*t)o*B.Te*t source.Name 1 Uoe /

T!pe Descriptors and Data "inding


The information that the property manager uses to determine the list of properties that can be used for binding is provided by the TypeAescriptor class from the 5ystem.+omponent odel namespace. The TypeAescriptor class uses .!"T @eflection to gather information about an ob<ect%s properties. 6owever' before falling bac$ on @eflection' the TypeAescriptor class first calls the Get8roperties methods of the *+ustomTypeAescriptor interface to as$ the ob<ect for a list of properties. The list of 8ropertyAescriptor ob<ects returned from this method can e(pose a custom set of properties or no properties at all:
LIM LIM

#or a discussion of .!"T @eflection' see

Essential .NET 30ddison/Wesley' >0034' by Aon Bo(' with +hris 5ells.

class Bame)n B/m&er :

;CustomType0escriptor " pu!lic Property0escriptorCollection KetProperties+.... 4 // E*pose no properties for run?time !inding

return new Property0escriptorCollection+null./

...

#or e(ample' this implementation of Get8roperties causes any call to the 0ddBinding method on our custom data source to fail at run time' because the list of properties e(posed from the class is empty. "(posing a subset of properties for binding re)uires passing an array of ob<ects of a custom type that derives from the 8ropertyAescriptor base class. *f you%re willing to go that far' you can use the same techni)ue to build properties at run time out of whole cloth from e(ternal data. This is how Aata@ow-iew e(poses columns on tables for binding that it doesn%t $now about until run/time.
L8M L8M

1nfortunately' .!"T provides no public' creatable classes that derive from 8ropertyAescriptor' but Type+onverter.5imple8ropertyAescriptor is

a start.

T!pe Con&ersion
!otice that our custom data source class uses simple types that are easy to convert to and from strings. That%s important because the Te(t property of a control is a string. 6owever' you don%t always want to bind simple types to string properties. #or e(ample' you may need to bind to a custom data type from a property instead of binding to a simple type. #or that to wor$' the data must be convertible not only to a string but also bac$ from a string to the custom data type. 9therwise' the changes that the user ma$es to the bound control will be lost. #or e(ample' consider beefing up the sample code to e(pose a custom type as the value to one of the properties being bound to:
class Fraction " #/&lic Fraction(int n/merator, int this.n/merator 1 n/merator; this. enominator 1 enominator; #/&lic int B/merator " *et " ret/rn this.n/merator; set " this.n/merator 1 $al/e; #/&lic int 9enominator " *et " ret/rn this. enominator; set " this. enominator 1 $al/e; int n/merator; int enominator; enominator) "

class Bame)n B/m&er " ... .. (x#ose a c/stom ty#e ,rom a &o/n

#ro#erty

#/&lic e$ent ($entHan ler B/m&erChan*e ; #/&lic Fraction B/m&er " *et " ret/rn n/m&er; set " n/m&er 1 $al/e; .. Boti,y &o/n control o, chan*es i,( B/m&erChan*e 31 n/ll ) B/m&erChan*e (this, ($ent)r*s.(m#ty); strin* name 1 "Chris"; Fraction n/m&er 1 ne! Fraction(HA@, 5); Bame)n B/m&er so/rce 1 ne! Bame)n B/m&er(); $oi C/stom6tem9ataSo/rceForm%Loa (o&'ect sen er, ($ent)r*s e) " .. +in to a c/stom ty#e text+ox@.9ata+in in*s.) ("0ext", so/rce, "B/m&er"); -

By default' this binding will show the name of the type instead of any meaningful value' as shown in #igure 13.3S. Figure 1 . .. "inding to a Custom +tem Data ,ource without a Con&ersion to ,tring

To get the string value to be set as the Te(t property' the binding falls bac$ on the To5tring method of the custom #raction class' which defaults to the 9b<ect base class%s implementation of returning the name of the type. 9verriding the To5tring method of the #raction class solves the display problem 3as shown in #igure 13.3I4:
class Fraction " ...
5

pu!lic o#erride string ToString+. 4

ret/rn strin*.Format(""?-."5-", n/merator,

enominator);

Figure 1 . 0. "inding to a Custom +tem Data ,ource with a Con&ersion to ,tring

6owever' implementing To5tring fi(es only half of the conversion problem. The other half is ta$ing the value that the user enters and putting it bac$ into the property. There is no method to override in the #raction class that will allow that. *nstead' you%ll need a type converter. 0s discussed in +hapter 9: Aesign/Time *ntegration' a type converter is a class that derives from the Type+onverter class in the 5ystem. +omponent odel namespace. 5pecifically' the virtual methods that you must override when converting between types are the +an+onvert#rom and +an+onvertTo methods and the +onvert#rom and +onvertTo methods. This is an e(ample of a type converter that $nows how to convert between our custom #raction type and the string type:
class Fraction0y#eCon$erter :
pu!lic o#erride !ool CanCon#ertFrom+ ;Type0escriptorConte*t conte*tType sourceType. 4 5 TypeCon#erter

"

ret/rn so/rce0y#e 11 ty#eo,(strin*);

pu!lic o#erride !ool CanCon#ertTo+ ;Type0escriptorConte*t conte*tType destinationType. 4

ret/rn

estination0y#e 11 ty#eo,(strin*);

pu!lic o#erride o!9ect Con#ertFrom+ ;Type0escriptorConte*t conte*tCulture;nfo cultureo!9ect #alue. 4

.. Cery sim#le con$ersion i*nores context, c/lt/re, an errors strin* ,rom 1 (strin*)$al/e; int slash 1 ,rom.6n exD,("."); int n/merator 1 int.Garse(,rom.S/&strin*(?, slash)); int enominator 1 int.Garse(,rom.S/&strin*(slash ; 5)); ret/rn ne! Fraction(n/merator, enominator);

pu!lic o#erride o!9ect Con#ertTo+ ;Type0escriptorConte*t conte*tCulture;nfo cultureo!9ect #alueType destinationType. 4

i,( estination0y#e 31 ty#eo,(strin*) ) ret/rn n/ll; Fraction n/m&er 1 (Fraction)$al/e; ret/rn strin*.Format( ""?-."5-", n/m&er.B/merator, n/m&er.9enominator);

0ssociating the type converter with the type is a matter of applying Type+onverter0ttribute:
%TypeCon#erter'ttri!ute+typeof+FractionTypeCon#erter..(

class Fraction "...-

!ow' instead of using the To5tring method to get the #raction string to display in the bound control' the binding will use the #ractionType+onverter class%s +an+onvertTo and +onvertTo methods. 5imilarly' when new data is available' the binding will use the +an+onvert#rom and +onvert#rom methods. 9r rather' it would' if that wor$ed. 1nfortunately' as of .!"T 1.1' converting control data bac$ into data of the custom type never ma$es it bac$ to the custom type converter. *nstead' an untested hun$ of code throws an e(ception that%s caught and ignored' and the conversion bac$ from the control to the custom data type silently fails. 9ne wor$around is to handle the Binding class%s 8arse event' as discussed earlier in this chapter' and let the client do the conversion:
Bame)n B/m&er so/rce 1 ne! Bame)n B/m&er(); $oi C/stom6tem9ataSo/rceForm@%Loa (o&'ect sen er, ($ent)r*s e) " text+ox5.9ata+in in*s.) ("0ext", so/rce, "Bame");
// )ind to a property of a custom type // and handle the parsing )inding !inding 1 te*t)o*G.0ata)indings.'dd+ Te*t - source- Num!er ./ !inding.Parse 71 new Con#ertE#ent,andler+te*t)o*G8Parse./

#oid te*t)o*G8Parse+o!9ect sender- Con#ertE#ent'rgs e. 4

strin* ,rom 1 (strin*)e.Cal/e; int slash 1 ,rom.6n exD,("."); int n/merator 1 int.Garse(,rom.S/&strin*(?, slash)); int enominator 1 int.Garse(,rom.S/&strin*(slash ; 5)); e.Cal/e 1 ne! Fraction(n/merator, enominator);

6owever' instead of building the parsing of the type into the form itself' it%s much more robust to let the parsing be handled by the type%s own type converter:
$oi
// Cet the type con#erter do the wor6 TypeCon#erter con#erter 1 Type0escriptor.KetCon#erter+e.0esiredType./ if+ con#erter 11 null . return/ if+ 2con#erter.CanCon#ertFrom+e.Lalue.KetType+.. . return/

text+ox@%Garse(o&'ect sen er, Con$ert($ent)r*s e) "

e.Lalue 1 con#erter.Con#ertFrom+e.Lalue./

0nother solution that doesn%t re)uire manually handling the parsing of every property of a custom type is to derive from the Binding base class and override the 9n8arse method:
class Wor4)ro/n +in in* : +in in* " #/&lic Wor4)ro/n +in in*(strin* name, o&'ect src, strin* mem&er) : &ase(name, src, mem&er) " protected o#erride #oid JnParse+Con#ertE#ent'rgs e. 4

try " .. Let the &ase class ha$e a crac4 &ase.DnGarse(e); catch( 6n$ali Cast(xce#tion ) " .. 0a4e o$er ,or &ase class i, it ,ails .. 6, one o, the &ase class e$ent han lers con$erte it, .. !eFre ,inishe i,( e.Cal/e.Eet0y#e().6sS/&classD,(e.9esire 0y#e) YY (e.Cal/e.Eet0y#e() 11 e.9esire 0y#e) YY (e.Cal/e is 9+B/ll) ) " ret/rn; .. )s4 the esire ty#e ,or a ty#e con$erter 0y#eCon$erter con$erter 1 0y#e9escri#tor.EetCon$erter(e.9esire 0y#e); i,( (con$erter 31 n/ll) ZZ con$erter.CanCon$ertFrom(e.Cal/e.Eet0y#e()) ) " e.Cal/e 1 con$erter.Con$ertFrom(e.Cal/e); 5

Jou use the Wor$0roundBinding class instead of the standard Binding class when adding a binding to a property of a custom type:
$oi Form5%Loa (o&'ect sen er, ($ent)r*s e) " text+ox5.9ata+in in*s.) ("0ext", so/rce, "Bame"); .. Let the c/stom &in in* han le the #arsin* !or4aro/n
)inding !inding 1 new Wor6'round)inding+ Te*t - source- Num!er ./ te*t)o*G.0ata)indings.'dd+!inding./

!either of these techni)ues is as nice as if custom types were fully supported' but both show the power that comes from understanding the basics of the data binding architecture.

$ist Data ,ources

The basics of simple data binding support re)uire only that you e(pose public properties. *n the same way' setting the content for comple( bound controls' such as the 7istBo( or the +omboBo(' re)uires only a collection of your custom data source ob<ects:
Name'ndNum!er%( source 1

" ne! Bame)n B/m&er("Wohn", P), ne! Bame)n B/m&er("0om", K) -; $oi Com#lexC/stom9ataSo/rceForm%Loa (o&'ect sen er, ($ent)r*s e) "
// )ind a collection of custom data sources comple*ly list)o*B.0ataSource 1 source/

0s you recall from the earlier discussion' setting only the Aata5ource and not the Aisplay ember property shows the type of each ob<ect instead of anything useful from them' as shown in #igure 13.38. Figure 1 . 2. "inding to a Custom $ist Data ,ource without a ,tring Con&ersion

*f you%d li$e the ob<ect itself to decide how it%s displayed' you can leave the Aisplay ember property as null and let the type%s implementation of To5tring or its type converter determine what%s shown:
class Bame)n B/m&er " ... .. For /se &y sin*le:$al/e com#lex controls, .. e.*. List+ox an Com&o+ox
pu!lic o#erride string ToString+. 4

ret/rn this.name ; ", " ; this.n/m&er;


5

$oi C/stomList9ataSo/rceForm%Loa (o&'ect sen er, ($ent)r*s e) "


// Cet the o!9ect determine how it:s displayed list)o*B.0ataSource 1 source/

Binding to a collection of !ame0nd!umber ob<ects with its own To5tring implementation yields the $ind of display shown in #igure 13.39. Figure 1 . 4. "inding to a Custom $ist Data ,ource with a ,tring Con&ersion

The To5tring method is used only in the absence of values in both the Aisplay ember and the -alue ember properties. When used against custom data sources' the list controls allow the Aisplay ember and -alue ember properties to be set with the names of properties to pull from the custom types in the collection:
Bame)n B/m&erIJ so/rce 1 " ne! Bame)n B/m&er("Wohn", P), ne! Bame)n B/m&er("0om", K) -; $oi Com#lexC/stom9ataSo/rceForm%Loa (o&'ect sen er, ($ent)r*s e) " list+ox5.9ataSo/rce 1 so/rce;
list)o*B.0isplay$em!er 1 Name / list)o*B.Lalue$em!er 1 Num!er /

$oi // Show selected #alue as selected inde* changes status)arB.Te*t 1 selected Num!er1 7 list)o*B.SelectedLalue/

list+ox5%Selecte 6n exChan*e (o&'ect sen er, ($ent)r*s e) "

The results of this binding are shown in #igure 13.H0. Figure 1 .#6. "inding Properties as Data 8em9ers

"inding to Custom Hierarchies 5ometimes ob<ects are arranged in hierarchies. Jou can accommodate this using a property that e(poses a collection class. #or e(ample' you might use an 0rray7ist' which implements the *7ist interface from the 5ystem. +ollections namespace:
.. Lo!est:le$el o&'ects: class Fraction " #/&lic Fraction(int n/merator, int this.n/merator 1 n/merator; this. enominator 1 enominator; #/&lic int B/merator " *et " ret/rn this.n/merator; set " this.n/merator 1 $al/e; -

enominator) "

#/&lic int 9enominator " *et " ret/rn this. enominator; set " this. enominator 1 $al/e; int n/merator; int enominator; .. 0o#:le$el o&'ects: class Bame)n B/m&ers " #/&lic Bame)n B/m&ers(strin* name) " this.name 1 name; #/&lic e$ent ($entHan ler BameChan*e ; #/&lic strin* Bame " *et " ret/rn name; set " name 1 $al/e; i,( BameChan*e 31 n/ll ) BameChan*e (this, ($ent)r*s.(m#ty); // E*pose second?le#el hierarchy3 // NJTE3 0ataKrid doesn:t !ind to arrays pu!lic 'rrayCist Num!ers 4 get 4 return this.num!ers/ 5 5 // 'dd to second?le#el hierarchy pu!lic #oid 'ddNum!er+Fraction num!er. 4 this.num!ers.'dd+num!er./ 5

#/&lic o$erri e strin* 0oStrin*() " ret/rn this.name; strin* name 1 "Chris";
'rrayCist num!ers 1 new 'rrayCist+./ // su!o!9ects

When you%ve got a multilevel hierarchy of custom types' you can bind it against any control that supports hierarchies' such as the AataGrid control:
// Top?le#el collection3

)rrayList so/rce 1 ne! )rrayList(); $oi C/stomList9ataSo/rceForm@%Loa (o&'ect sen er, ($ent)r*s e) " .. Go#/late the hierarchy Bame)n B/m&ers nan5 1 ne! Bame)n B/m&ers("Wohn"); nan5.) B/m&er(ne! Fraction(5, @)); nan5.) B/m&er(ne! Fraction(@, N)); nan5.) B/m&er(ne! Fraction(N, H)); so/rce.) (nan5);

Bame)n B/m&ers nan@ 1 ne! Bame)n B/m&ers("0om"); nan@.) B/m&er(ne! Fraction(H, A)); nan@.) B/m&er(ne! Fraction(A, X)); so/rce.) (nan@); .. +in a collection o, c/stom ataEri 5.9ataSo/rce 1 so/rce; ata so/rces com#lexly

#igure 13.H1 shows the top/level collection bound to a data grid' showing a lin$ to the second/level collection. Figure 1 .#1. ,howing the Top $e&el of an *9ject Hierarch! in a Data :rid

The data grid shows each public property from the ob<ect at each level' along with lin$s to subob<ects e(posed as collection properties. #igure 13.H> shows the second/level collection after the lin$ has been followed. Figure 1 .#2. ,howing the ,econd $e&el of an *9ject Hierarch! in a Data :rid

The data grid is pretty fle(ible' but each level of hierarchy needs to be a type that e(poses one or more public properties. *t needs property names for column names. This means that' unli$e the list controls' a data grid won%t use a type converter or the To5tring method to show the value of the ob<ect as a whole. 0 more full/featured integration with the data grid.including enabling the data grid 1* to edit the data and the ability to $eep the data grid up/to/date as the list data source is

modified.re)uires an implementation of the *Binding7ist interface' something that is beyond the scope of this boo$.

Where Are We?


Aata binding is the act of connecting a control to a data source. Binding lets you create an association between controls and data sources to $eep a control and a data source in sync automatically. The control can be simple' li$e a Te(tBo( or a 7istBo(' or full/featured' li$e the AataGrid. *f the data source is an item data source' either it%s an ob<ect having =ero' one' or more public properties or you need a conversion to the data type to properly bind to what the control needs. 0 data source can also be a list data source' in which case it%s a list of ob<ects that can be shown one at a time 3when simply bound4 or all at once 3when comple(ly bound4. 5imple binding re)uires no support from the control itself and allows any number of control properties to be bound to any number of data sources. +omple( binding re)uires support from the control but allows the control greater fle(ibility in the way it interacts with the data source. #or e(ample' it can show multiple items from a list or show multiple columns from a table 3or multiple properties from an ob<ect4.

Chapter 1#. 8ultithreaded 7ser +nterfaces


Winforms applications often need to start long/running operations' such as an intensive calculation or a call to a Web service. *n those cases' it%s important to run the operation so that you allow the application to continue to interact with the user without free=ing' while still allowing the user to see the progress of the operation and even to cancel it. Before we get started' * should mention that this chapter discusses the issues surrounding multithreaded user interfaces only. This simplifies the discussion and covers the information you%ll need most of the time when handling long/running operations in a Win#orms application. #or more details of threading specifics in .!"T.including the details of the Thread class' thread pooling' loc$ing' synchroni=ation' race conditions' and deadloc$s.you should turn to your favorite low/level .!"T boo$.

)ong.Running -"erations
*magine that the value of pi in 5ystem. ath.8*' at only >0 digits' <ust isn%t accurate enough for you. *n that case' you may find yourself writing an application li$e the one in #igure 1H.1 to calculate pi to an arbitrary number of digits. Figure 1#.1. Digits of Pi 1pplication

This program ta$es as input the number of digits of pi to calculate and' when the +alc button is pressed' shows the progress as the calculation happens.

Progress +ndication

0lthough most applications don%t need to calculate the digits of pi' many $inds of applications need to perform long/running operations' whether it%s printing' ma$ing a Web service call' or calculating the interest earnings of a certain billionaire in the 8acific !orthwest. 1sers are generally content to wait for such things as long as they can see that progress is being made. That%s why even this simple application has a progress bar. The algorithm it uses calculates pi 9 digits at a time. 0s each new set of digits is available' the application updates the te(t and the progress bar. #or e(ample' #igure 1H.> shows progress on the way to calculating 1'000 digits of pi 3if >1 digits are good' then 1'000 must be better4. Figure 1#.2. Calculating Pi to 1,666 Digits

The following shows how the 1* is updated as the digits of pi are calculated:
class )syncCalcGiForm : Form " ...
#oid ShowProgress+string pi- int total0igits- int digitsSoFar. 4 piTe*t)o*.Te*t 1 pi/ piProgress)ar.$a*imum 1 total0igits/ piProgress)ar.Lalue 1 digitsSoFar/ 5

$oi CalcGi(int i*its) " Strin*+/il er #i 1 ne! Strin*+/il er("N",


// Show progress ShowProgress+pi.ToString+.- digits- A./

i*its ; @);

i,( i*its > ? ) " #i.)##en ("."); ,or( int i 1 ?; i = i*its; i ;1 T ) " int nine9i*its 1 Bine9i*itsD,Gi.Startin*)t(i;5); int i*itCo/nt 1 Math.Min( i*its : i, T); strin* s 1 strin*.Format(""?:9T-", nine9i*its); #i.)##en ( s.S/&strin*(?, i*itCo/nt));
// Show progress ShowProgress+pi.ToString+.- digits- i 7 digitCount./

$oi -

CalcPi++int.8digits.Lalue./

calc+/tton%Clic4 (o&'ect sen er, ($ent)r*s e) "

This implementation wor$s <ust fine for a small number of digits. But suppose the user switches away from the application and then returns in the middle of calculating pi to a large number of digits' as shown in #igure 1H.3. Figure 1#. . (o Paint for HouI

The problem is that the application has a single thread of e(ecution 3this $ind of application is often called a single(t"readed application4' so while the thread is calculating pi' it can%t also be drawing the 1*. This didn%t happen before the user switched the application to the bac$ground because both the te(t bo( and the progress bar force their own painting to happen immediately as their properties are set 3although the progress bar seems to be better at this than the te(t bo(4. 6owever' after the user puts the application into the bac$ground and then the foreground again' the main form must paint the entire client area' and that means processing the 8aint event. Because no other event can be processed until the application returns from the +lic$ event on the +alc button' the user won%t see any display of further progress until all the digits of pi are calculated. What this application needs is a way to free the 1* thread to do 1* wor$ and handle the long/running pi calculation in the bac$ground. #or this' it needs another thread of e(ecution.

1s!nchronous *perations
0 t"read of e%ec&tion 3often called simply a t"read4 is a series of instructions and a call stac$ that operate independently of the other threads in the application or in any other application. *n every version of Windows since Windows 9F' Windows schedules each thread transparently so that a programmer can write a thread almost 3but not )uite4 as if it

were the only thing happening on the system. 5tarting a thread is an as#nc"rono&s operation* in that the current thread of e(ecution will continue immediately' e(ecuting independently of the new thread. To start a new thread of e(ecution in .!"T is a matter of creating a Thread ob<ect from the 5ystem.Threading namespace' passing a delegate as the constructor parameter' and starting the thread:
L1M L1M

Jou can read more about delegates in 0ppendi( B: Aelegates and "vents.

/sin* System.0hrea in*; ... class )syncCalcGiForm : Form " ... int i*its0oCalc 1 ?; $oi CalcGi0hrea Start() " CalcGi( i*its0oCalc); $oi calc+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " i*its0oCalc 1 (int)% i*its.Cal/e;

Thread piThread 1 new Thread+new ThreadStart+CalcPiThreadStart../ piThread.Start+./

This code creates a new thread and begins e(ecution of the thread by passing a delegate wrapper around the method to call. !ow' instead of waiting for +alc8i to finish before returning from the button +lic$ event' the 1* thread spawns a or!er thread' immediately returning the 1* thread to its user interaction duties. #igure 1H.H shows the two threads doing their separate <obs. Figure 1#.#. (aJ&e 8ultithreading

5pawning a wor$er thread to calculate pi leaves the 1* thread free to handle events 3which Win#orms creates as it ta$es messages off the Windows message )ueue4. When the wor$er thread has more digits of pi to share with the user' it directly sets the values of the te(t bo( and the progress bar controls. 30ctually' letting the wor$er thread access controls created by the 1* thread is dangerous' but we%ll get to that a little later.4 *n the sample code to start a thread' notice that no arguments are passed to the wor$er thread%s entry point' +alc8iThread5tart. This is because the delegate that is used to construct the Thread class allows no arguments to be passed. *nstead' we tuc$ the number of digits to calculate into a field' digitsTo+alc. Then we call the thread entry point' which uses digitsTo+alc to call the +alc8i method in turn. Because you can%t pass arguments to the thread start method' * prefer to use custom delegates for spawning threads. *n addition' an asynchronous delegate will be handled on a thread from the per/process thread pool' something that scales better than does creating a new thread for each of a large number of asynchronous operations. 6ere%s how to declare a custom delegate suitable for calling +alc8i:
ele*ate $oi CalcGi9ele*ate(int i*its);

0fter the custom delegate has been defined' the following creates an instance of the delegate to call the +alc8i method synchronously:
$oi calc+/tton%Clic4(o&'ect sen er, ($ent)r*s e) "
CalcPi0elegate calcPi 1 new CalcPi0elegate+CalcPi./ calcPi++int.digitsIp0own.Lalue./

Because calling +alc8i synchronously is the cause of our trouble' we need to call it asynchronously. Before we do that' however' we need to understand a bit more about how delegates wor$. The +alc8iAelegate declaration implicitly declares a new class derived from ulti+astAelegate with three methods: *nvo$e' Begin*nvo$e' and "nd*nvo$e:
class CalcGi9ele*ate : $ulticast0elegate " #/&lic $oi ;n#o6e(int i*its); #/&lic $oi )egin;n#o6e( int i*its, )syncCall&ac4 call&ac4, o&'ect asyncState); #/&lic $oi End;n#o6e(6)sync2es/lt res/lt); -

When the application created an instance of +alc8iAelegate and called it li$e a method' it was really calling the synchronous *nvo$e method' which simply turned around and called the +alc8i method on the same thread. Begin*nvo$e and "nd*nvo$e' however' are the pair of methods that allow asynchronous invocation of a method on a new thread for a per/ process pool of threads. To have the +alc8i method called on another thread.the aforementioned wor$er thread.the application uses Begin*nvo$e:
L>M L>M

*f you%re using -5.!"T >00>' don%t be alarmed that neither Begin*nvo$e nor "nd*nvo$e shows up in *ntelli5ense from +,. They%re there' *

assure you' and -5.!"T >003 has been updated to show them.

$oi calc+/tton%Clic4(o&'ect sen er, ($ent)r*s e) " CalcGi9ele*ate calcGi 1 ne! CalcGi9ele*ate(CalcGi);
calcPi.)egin;n#o6e++int.digitsIp0own.Lalue- null- null./

!otice the nulls for the last two arguments of Begin*nvo$e. We would need these arguments if we needed to later harvest the result from the method we%re calling 3which is also what "nd*nvo$e is for4. Because the +alc8i method updates the 1* directly' we don%t need anything but nulls for these two arguments. 0t this point' we%ve got an application with a fully interactive 1* that shows progress on a long/running operation. 1nfortunately' we%re not )uite finished yet.

,afet! and 8ultithreading


0s it turns out' we%re luc$y that this application wor$s at all. Because we start the +alc8i method on a wor$er thread' when +alc8i calls the 5how8rogress method it%s accessing the te(t bo( and progress bar controls from the wor$er thread' even though those controls were created on the 1* thread. This violates a $ey re)uirement that%s been present since Windows first got support for threads: &Thou shalt operate on a window only from its creating thread.& *n fact' the Win#orms documentation is clear on this point: &There are four methods on a control that are safe to call from any thread: *nvo$e' Begin*nvo$e' "nd*nvo$e' and +reateGraphics. #or all other method calls' you should use one of the invo$e methods to marshal the call to the control%s thread.& 5o when the +alc8i method calls the 5how8rogress method' which in turn accesses two controls created by the 1* thread' our application is clearly violating this rule. 7uc$ily' long/running operations are common in Windows applications. 0s a result' each 1* class in Win#orms.meaning every class that ultimately derives from 5ystem.Windows.#orms.+ontrol.has a property that you can use to find out whether it%s safe to act on the control from that thread. The property is *nvo$e@e)uired' which returns true if the calling thread needs to pass control to the 1* thread before calling a method on the control. 0 simple 0ssert in the 5how8rogress method would have immediately shown the error in our sample application:
/sin* System.9ia*nostics; $oi ... Sho!Gro*ress(strin* #i, int total9i*its, int i*itsSoFar) "
// $a6e sure we:re on the I; thread 0e!ug.'ssert+this.;n#o6e"e>uired 11 false./

Because the wor$er thread is clearly not allowed to show progress directly' we need to pass control from the wor$er thread bac$ to the 1* thread. #rom the names of the first three methods that are safe to call from any thread.*nvo$e' Begin*nvo$e' and "nd*nvo$e.it should be clear that you%ll need another delegate to pass control appropriately. The delegate will be created on the wor$er thread and then e(ecuted on the 1* thread so that we can have safe' single/threaded access to 1* ob<ects.

,!nchronous Call9ac5s 0synchronous operations' such as the call to a delegate%s Begin*nvo$e method' return immediately' so they are nonbloc!ing. This means that the thread isn%t bloc$ed waiting for the method to complete. 5ynchronous operations' on the other hand' are bloc!ing' because they do cause the calling thread to bloc$ until the method returns. Aepending on the bloc$ing behavior you%re interested in' you can call *nvo$e or Begin*nvo$e to bloc$ or not to bloc$' respectively' when calling into the 1* thread:
class System.Win o!s.Forms.Control : ... "
pu!lic pu!lic pu!lic pu!lic o!9ect ;n#o6e+0elegate method./ #irtual o!9ect ;n#o6e+0elegate method- o!9ect%( args./ ;'sync"esult )egin;n#o6e+0elegate method./ #irtual ;'sync"esult )egin;n#o6e +0elegate method- o!9ect%( args./

#/&lic $irt/al o&'ect (n 6n$o4e(6)sync2es/lt async2es/lt); ... -

+ontrol.*nvo$e will bloc$ until the 1* thread has processed the re)uest: that is' until +ontrol.*nvo$e has put a message on the 1* thread%s message )ueue and has waited for it to be processed li$e any other message 3e(cept that the delegate%s method is called instead of an event handler4. Because *nvo$e ta$es a Aelegate argument' which is the base class for all delegates' it can form a call to any method' using the optional array of ob<ects as arguments and returning an ob<ect as the return value for the called method. 1sing +ontrol.*nvo$e loo$s li$e this:
$oi Sho!Gro*ress(strin* #i, int total9i*its, int ... delegate #oid ShowProgress0elegate+ string pi- int total0igits- int digitsSoFar./

i*itsSoFar) "

$oi CalcGi(int i*its) " Strin*+/il er #i 1 ne! Strin*+/il er("N",


// Ket ready to show progress ShowProgress0elegate showProgress 1 new ShowProgress0elegate+ShowProgress./ // Show progress this.;n#o6e+showProgressnew o!9ect%( 4 pi.ToString+.- digits- A5./

i*its ; @);

i,( i*its > ? ) " #i.)##en ("."); ,or( int i 1 ?; i = ... i*its; i ;1 T ) "

// Show progress this.;n#o6e+showProgressnew o!9ect%( 4 pi.ToString+.- digits- i 7 digitCount5./

!otice the declaration of a new delegate' 5how8rogressAelegate. This delegate matches the signature of the 5how8rogress method we%d li$e to be called on the 1* thread. Because 5how8rogress ta$es three arguments' the code uses an overload to *nvo$e that ta$es an array of ob<ects to form the arguments to the 5how8rogress method. !ow the 1* thread uses a delegate' calling Aelegate.Begin*nvo$e to spawn a wor$er thread' and the wor$er thread uses +ontrol.*nvo$e to pass control bac$ to the 1* thread when the progress controls need updating. #igure 1H.F shows our safe multithreading architecture. Figure 1#.'. ,afe 8ultithreading

!otice that #igure 1H.F shows only the 1* thread ever touching the controls and shows that the wor$er thread uses the message )ueue to let the 1* thread $now when progress needs reporting. 1s!nchronous Call9ac5s 9ur use of the synchronous call to +ontrol.*nvo$e wor$s <ust fine' but it gives us more than we need. The wor$er thread doesn%t get any output or return values from the 1* thread when calling through the 5how8rogressAelegate. By calling *nvo$e' we force the wor$er thread to wait for the 1* thread' bloc$ing the wor$er thread from continuing its calculations. This is a <ob tailor/made for the asynchronous +ontrol.Begin*nvo$e method:
$oi CalcGi(int i*its) " Strin*+/il er #i 1 ne! Strin*+/il er("N", .. Eet rea y to sho! #ro*ress i*its ; @);

Sho!Gro*ress9ele*ate sho!Gro*ress 1 ne! Sho!Gro*ress9ele*ate(Sho!Gro*ress); .. Sho! #ro*ress this.)egin;n#o6e(sho!Gro*ress, ne! o&'ectIJ " #i.0oStrin*(), i,( i*its > ? ) " #i.)##en ("."); ,or( int i 1 ?; i = i*its; i ;1 T ) " ... .. Sho! #ro*ress this.)egin;n#o6e(sho!Gro*ress, ne! o&'ectIJ " #i.0oStrin*(), i*its, i ; -

i*its, ?-);

i*itCo/nt-);

!otice that the only thing different in this code is the call to Begin*nvo$e instead of *nvo$e. 0ll the arguments are the same' and we can ignore the return value from Begin*nvo$e. The Begin*nvo$e method returns an *0sync@esult interface' which provides access to the current status of the re)uest and allows the wor$er thread to harvest any results. Because there are no results to harvest' there%s no need to worry about the *0sync@esult or ever calling "nd*nvo$e. *f you need to retrieve results from the 1* thread to the wor$er thread' you can:
// Call !ac6 to the I; thread

6)sync2es/lt res 1 this.+e*in6n$o4e(some9ele*ateWith2es/lts, ...); .. Chec4 ,or res/lts !hile( res.6sCom#lete 11 ,alse ) System.0hrea in*.0hrea .Slee#(5??); .. Har$est res/lts o&'ect metho 2es/lts 1 this.(n 6n$o4e(res); .. 9o somethin* !ith res/lts...

*0sync@esult.*s+ompleted is meant to be called periodically while doing other things 3such as during the 0pplication class%s *dle event' mentioned in +hapter 11: 0pplications and 5ettings4. 9f course' if you%re doing nothing e(cept polling for results' the +ontrol.*nvo$e method is what you want.

,implified 8ultithreading
The call to Begin*nvo$e is a bit cumbersome' especially because it happens twice in the +alc8i method. We can simplify things by updating the 5how8rogress method to ma$e the asynchronous call itself. *f 5how8rogress is called from the 1* thread' it will update the controls' but if it%s called from a wor$er thread' it uses Begin*nvo$e to call itself bac$ on

the 1* thread. This lets the application go bac$ to the earlier' simpler +alc8i implementation:
$oi
// $a6e sure we:re on the I; thread if+ this.;n#o6e"e>uired 11 false . 4 piTe*t)o*.Te*t 1 pi/ piProgress)ar.$a*imum 1 total0igits/ piProgress)ar.Lalue 1 digitsSoFar/ 5 else 4 // Show progress asynchronously ShowProgress0elegate showProgress 1 new ShowProgress0elegate+ShowProgress./ this.)egin;n#o6e+showProgressnew o!9ect%( 4 pi- total0igits- digitsSoFar5./ 5

Sho!Gro*ress(strin* #i, int total9i*its, int

i*itsSoFar) "

$oi CalcGi(int i*its) " Strin*+/il er #i 1 ne! Strin*+/il er("N",


// Show progress ShowProgress+pi.ToString+.- digits- A./

i*its ; @);

i,( i*its > ? ) " #i.)##en ("."); ,or( int i 1 ?; i = ... i*its; i ;1 T ) "

// Show progress ShowProgress+pi.ToString+.- digits- i 7 digitCount./

When you build multithread 1* code' it%s common for the methods that interact with the controls to chec$ that they%re running on the 1* thread. Auring development' you may need a way to trac$ down places where you%ve forgotten to ma$e the appropriate transition between threads. To ma$e those places obvious' * recommend that in every method that might be called from a wor$er thread use a call that asserts that *nvo$e@e)uired is not re)uired.

Canceling
5o far' the sample application can send messages bac$ and forth between the wor$er and the 1* threads without a care in the world. The 1* thread doesn%t have to wait for the wor$er thread to complete or even be notified on completion' because the wor$er thread communicates its progress as it goes. 5imilarly' the wor$er thread doesn%t have to wait for the 1* thread to show progress as long as progress messages are sent at regular intervals to $eep users happy. 6owever' one thing doesn%t ma$e users happy: not having full control of any processing that their applications are performing. "ven though the 1* is responsive while pi is being

calculated' users would still li$e the option to cancel the calculation if they%ve decided they really need 1'000'001 digits and they mista$enly as$ed for only 1'000'000. #igure 1H.S shows an updated 1* for +alc8i that allows cancellation. Figure 1#... $etting the 7ser Cancel a $ong)/unning *peration

*mplementing cancel for a long/running operation is a multistep process. #irst' you need to provide a 1* that lets the user cancel the operation. *n this case' the +alc button is changed to a +ancel button after the calculation has begun. 0nother popular choice is to display a separate progress dialog' which typically includes current progress details' a progress bar showing percentage of wor$ complete' and a +ancel button. *f the user decides to cancel' that is noted in a member variable. *n addition' the 1* is disabled for the short time between the time when the 1* thread $nows the wor$er thread should stop and the time the wor$er thread itself $nows and has had a chance to stop sending progress. *f you ignore this lag time' the user could start another operation before the first wor$er thread stops sending progress' ma$ing it the <ob of the 1* thread to figure out whether it%s getting progress from the new wor$er thread or the old wor$er thread' which is supposed to be shutting down. Jou could assign each wor$er thread a uni)ue *A so that the 1* thread can $eep such things organi=ed 3and' in the face of multiple simultaneous long/running operations' you may well need to do this4' but it%s often simpler to pause the 1* for this brief amount of time. The sample application $eeps trac$ of its current processing state using a value from a three/value enumeration:
en/m CalcState Gen in*, Calc/latin*, Cancele , " .. Bo calc/lation r/nnin* or cancelin* .. Calc/lation in #ro*ress .. Calc/lation cancele in <6 &/t not !or4er

CalcState state 1 CalcState.Gen in*;

!ow' depending on what state the application is in' it treats the +alc button differently:

$oi

// Calc !utton does dou!le duty as Cancel !utton switch+ state . 4 // Start a new calculation case CalcState.Pending3

calc+/tton%Clic4(o&'ect sen er, ($ent)r*s e)

"

.. )llo! cancelin* state 1 CalcState.Calc/latin*; calc+/tton.0ext 1 "Cancel"; .. )sync ele*ate metho CalcGi9ele*ate calcGi 1 ne! CalcGi9ele*ate(CalcGi); calcGi.+e*in6n$o4e((int) i*its<#9o!n.Cal/e, n/ll, n/ll); &rea4;
// Cancel a running calculation case CalcState.Calculating3

state 1 CalcState.Cancele ; calc+/tton.(na&le 1 ,alse; &rea4;


// Shouldn:t !e a!le to press Calc !utton while it:s canceling case CalcState.Canceled3

9e&/*.)ssert(,alse); &rea4; -

!otice that when the +alcC+ancel button is pressed in the 8ending state' the application sets the state to +alculating' changes the label on the button' and starts the calculation asynchronously' <ust as it did before. *f the state is +alculating when the +alcC+ancel button is pressed' the application switches the state to +anceled and disables the 1*. The 1* will remain disabled' preventing the start of a new calculation' for as long as it ta$es to communicate the canceled state to the wor$er thread. 0fter the 1* thread has communicated with the wor$er thread that the operation has been canceled' the 1* thread enables the 1* again and resets the state to 8ending 3shown later4 so that the user can start another operation. To communicate to the wor$er that it should cancel' the sample augments the 5how8rogress method to include a new o&t parameter:
#oid ShowProgress+...- out !ool cancel./

$oi CalcGi(int i*its) " &ool cancel 1 ,alse; ... ,or( int i 1 ?; i = ... i*its; i ;1 T ) "

.. Sho! #ro*ress (chec4in* ,or Cancel) Sho!Gro*ress(..., o/t cancel); i,( cancel ) &rea4; -

Jou may be tempted to ma$e the cancel indicator a Boolean return value from 5how8rogress' but * find it hard to remember whether a return value of &true& means to cancel or to continue as normal. * use the out parameter techni)ue to ma$e it very clear what%s going on. The only thing left to do is to update the 5how8rogress method to notice whether the user has as$ed to cancel and to let +alc8i $now accordingly. "(actly how that information is communicated depends on which techni)ue we%d li$e to use.

Communication with ,hared Data


@emember that the 5how8rogress method is the code that actually performs the transition between the wor$er thread and the 1* thread' so it%s the one at the heart of the communication between the two threads. The obvious way to communicate the current state of the 1* is to give the wor$er thread direct access to the state member variable:
$oi Sho!Gro*ress(..., o/t &ool cancel) "
if+ state 11 CalcState.Canceled . 4 state 1 CalcState.Pending/ cancel 1 true/ return/ 5

... -

* hope that something inside you cringed when you saw this code. *f you%re going to do multithreaded programming' you%re going to have to watch out for any time that two threads have simultaneous access to the same data 3in this case' the state member variable4. 5hared access to data between threads ma$es it very easy to get into race conditions* in which one thread is racing to read data that is only partially up/to/date before another thread has finished updating it. #or concurrent access to shared data to wor$' you must monitor usage of your shared data to ma$e sure that each thread waits patiently while the other thread wor$s on the data. To monitor access to shared data' .!"T provides the onitor class. Jou use onitor on a shared ob<ect to act as the loc$ on the data' which +, wraps with the handy loc$ bloc$:
o!9ect stateCoc6 1 new o!9ect+./

$oi Sho!Gro*ress(..., o/t &ool cancel) " loc4( stateLoc4 ) " .. Monitor the loc4 i,( state 11 CalcState.Cancel ) " state 1 CalcState.Gen in*; cancel 1 tr/e; ret/rn; ... -

The data has now been properly protected against race conditions' but the way we%ve done it invites another problem $nown as a deadloc!. When two threads are deadloc$ed' each of them waits for the other to complete its wor$ before continuing' thereby ensuring that neither will actually progress. *f all this tal$ of race conditions and deadloc$s has caused you concern' that%s good. ultithreaded programming with shared data is hard. 5o far we%ve been able to avoid these issues because we have passed copies of data around so that no two threads need to share access to any one piece of data. *f you don%t have shared data' there%s no need for synchroni=ation. *f you find that you need access to shared data.maybe because the overhead of copying the data is too great a space or time burden.then you%ll need to read up on multithreading and shared data synchroni=ation' topics that are beyond the scope of this boo$. 7uc$ily' the vast ma<ority of multithreading scenarios' especially as related to 1* multithreading' seem to wor$ best with the simple message/passing scheme used so far. ost of the time' you don%t want the 1* to have access to data being wor$ed on in the bac$ground' such as the document being printed or the collection of ob<ects being enumerated. #or these cases' passing data to be owned by the receiving thread is <ust the tic$et.

Communicating &ia 8ethod Parameters


Because 5how8rogress has already been updated with an out parameter' the sample lets it chec$ the state variable when it%s e(ecuting on the 1* thread:
$oi Sho!Gro*ress(..., o/t &ool cancel) " .. Ma4e s/re !eFre on the <6 threa i,( this.6n$o4e2e8/ire 11 ,alse ) " ...
// Chec6 for Cancel cancel 1 +state 11 CalcState.Canceled./ // Chec6 for completion if+ cancel XX +digitsSoFar 11 total0igits. . 4 state 1 CalcState.Pending/ calc)utton.Te*t 1 Calc / calc)utton.Ena!led 1 true/ 5

.. 0rans,er control to <6 threa else " ... -

The 1* thread is the only one to access the state member variable' so no synchroni=ation is needed. !ow it%s <ust a matter of passing control to the 1* thread in such a way as to harvest the cancel output parameter of the 5how8rogressAelegate. 1nfortunately' our use of +ontrol.Begin*nvo$e ma$es this complicated' because it doesn%t wait for results from the

1* thread. Waiting for the 5how8rogress method to complete and the cancel flag to be set re)uires a call to the bloc$ing +ontrol.*nvo$e' but even this is a bit tric$y:
$oi Sho!Gro*ress(..., o/t &ool cancel) " i,( this.6n$o4e2e8/ire 11 ,alse ) " ... .. 0rans,er control to <6 threa else " Sho!Gro*ress9ele*ate sho!Gro*ress 1 ne! Sho!Gro*ress9ele*ate(Sho!Gro*ress);
// '#oid !o*ing and losing our return #alue o!9ect inoutCancel 1 false/ // Show progress synchronously +so we can chec6 for cancel. ;n#o6e+showProgress- new o!9ect%( 4...- inoutCancel5./ cancel 1 +!ool.inoutCancel/

*t would have been nice to simply pass a Boolean variable directly to +ontrol.*nvo$e to harvest the cancel parameter' but there is a problem. The problem is that a Boolean is a value type' whereas *nvo$e ta$es an array of ob<ects' which are reference types. 0 val&e t#pe is a simple type' such as a Boolean' that is meant to be managed on the stac$. 0 reference t#pe' on the other hand' comes out of the heap. 0lthough passing a value type where a reference type is e(pected is certainly legal' it causes a copy of the value type 3this copying is called bo%ing4. 5o even though 5how8rogress would be able to change the cancel flag' the change would occur on a temporary variable created by the run time on/ the/fly in the heap' and we have no access to that variable.
L3M L3M

#or the full treatment of value types' reference types' bo(ing' and unbo(ing' see

Essential .NET 30ddison/Wesley' >0034' by Aon Bo('

with +hris 5ells.

To avoid losing updates to the cancel flag' 5how8rogress instead manually creates and passes a reference type variable 3inout+ancel4' avoiding the copy. 0fter the synchronous call to *nvo$e' the code casts the ob<ect variable bac$ to a Boolean to see whether or not the operation should be canceled.

Communication &ia 8essage Passing


The simplicity of the +alc8i e(ample' and the resulting comple(ity of sending around a single Boolean indicating whether to cancel' may cause you to try a solution li$e the following:
$oi Sho!Gro*ress(..., o/t &ool cancel) " .. Ma4e s/re !eFre on the <6 threa i,( this.6n$o4e2e8/ire 11 ,alse ) "..... 0rans,er control to the <6 threa else " Sho!Gro*ress9ele*ate sho!Gro*ress 1 ne! Sho!Gro*ress9ele*ate(Sho!Gro*ress);

.. Sho! #ro*ress synchrono/sly (so !e can chec4 ,or cancel) 6n$o4e(sho!Gro*ress, ne! o&'ectIJ "...-);
// Chec6 for Cancel the easy- !ut special?purpose- way cancel 1 +state 11 CalcState.Canceled./

#or our simple application' and others li$e it' that would wor$ <ust fine. Because the wor$er thread reads only from the state field' it will always be valid 3although a race condition could cause it to be old4. 6owever' don%t be tempted to ta$e this path. 0s soon as you have multiple outstanding re)uests and you $eep them in an array or any $ind of data structure at all' you run the ris$ of attempting to access data that%s been invalidated 3those darn race conditions again4' something you%ll have to protect against using synchroni=ation 3remember deadloc$s?4. *t%s much simpler and safer to pass around ownership of the data instead of sharing access to the same data. To avoid the comple(ity of bo(ing' * recommend that you follow the delegate idiom used by the rest of .!"T:
class Sho!Gro*ress)r*s : E#ent'rgs " #/&lic strin* Gi; #/&lic int 0otal9i*its; #/&lic int 9i*itsSoFar; #/&lic &ool Cancel; #/&lic Sho!Gro*ress)r*s(strin* #i, int total9i*its, int this.Gi 1 #i; this.0otal9i*its 1 total9i*its; this.9i*itsSoFar 1 i*itsSoFar; delegate #oid ShowProgress,andler+o!9ect sender- ShowProgress'rgs e./

i*itsSoFar) "

This code declares the class 5how8rogress0rgs' which derives from the "vent0rgs base class' to hold event arguments. *t also declares a delegate that ta$es a sender and an instance on the custom arguments ob<ect. With this in place' we can use the new delegate to update 5how8rogress to call itself:
$oi Sho!Gro*ress(...) " .. Ma4e s/re !eFre on the <6 threa i,( this.6n$o4e2e8/ire 11 ,alse ) "..... 0rans,er control to the <6 threa else " .. Create an instance o, the ele*ate to call .. the han ler on the <6 threa

ShowProgress,andler showProgress 1 new ShowProgress,andler+'syncCalcPiForm8ShowProgress./

.. 6nitiali7e the messa*e #arameters

o!9ect sender 1 System.Threading.Thread.CurrentThread/

ShowProgress'rgs e 1 new ShowProgress'rgs+pi- total0igits- digitsSoFar./

.. Sen the messa*e, !aitin* ,or the <6 threa to .. ret/rn !hether the o#eration sho/l &e cancele
this.;n#o6e+showProgress- new o!9ect%( 4sender- e5./ cancel 1 e.Cancel/

.. Calle
#oid 'syncCalcPiForm8ShowProgress+o!9ect sender- ShowProgress'rgs e. 4

on the <6 threa

.. <n#ac4 the messa*e an


5

ShowProgress+e.Pi- e.Total0igits- e.0igitsSoFar- out e.Cancel./

,or!ar

it to the Sho!Gro*ress metho

5how8rogress hasn%t changed its signature' so +alc8i still calls it in the same simple way. 6owever' now the wor$er thread will compose an instance of the 5how8rogress0rgs ob<ect to pass to the 1* thread via a handler that loo$s li$e any other event handler' including a sender and an "vent0rgs/derived ob<ect. The handler calls the 5how8rogress method again' brea$ing out the arguments from the 5how8rogress0rgs ob<ect. 0fter +ontrol.*nvo$e returns in the wor$er thread' the wor$er thread pulls out the cancel flag without any concern about bo(ing because the 5how8rogress0rgs type is a reference type. 6owever' even though it is a reference type and the wor$er thread passes control of it to the 1* thread' there%s no danger of race conditions because the wor$er thread waits until the 1* thread is finished wor$ing with the data before accessing it again. Jou can further simplify this usage by updating the +alc8i method to create an instance of the 5how8rogress0rgs class itself' eliminating the need for an intermediate method:
$oi Sho!Gro*ress(o!9ect sender- ShowProgress'rgs e) " .. Ma4e s/re !eFre on the <6 threa i,( this.6n$o4e2e8/ire 11 ,alse ) " #i0ext+ox.0ext 1 e.Pi; #iGro*ress+ar.Maxim/m 1 e.Total0igits; #iGro*ress+ar.Cal/e 1 e.0igitsSoFar; .. Chec4 ,or Cancel e.Cancel 1 (state 11 CalcState.Cancele ); .. Chec4 ,or com#letion i,( e.Cancel XX +e.0igitsSoFar 11 e.Total0igits. ) " state 1 CalcState.Gen in*; calc+/tton.0ext 1 "Calc"; calc+/tton.(na&le 1 tr/e; .. 0rans,er control to the <6 threa else "

ShowProgress,andler showProgress 1 new ShowProgress,andler+ShowProgress./ ;n#o6e+showProgress- new o!9ect%( 4 sender- e 5./

$oi CalcGi(int i*its) " Strin*+/il er #i 1 ne! Strin*+/il er("N", i*its ; @);

o!9ect sender 1 System.Threading.Thread.CurrentThread/ ShowProgress'rgs e 1 new ShowProgress'rgs+pi.ToString+.- digits- A./ // Show progress +ignoring Cancel so soon. ShowProgress+sender- e./

i,( i*its > ? ) " #i.)##en ("."); ,or( int i 1 ?; i = i*its; i ;1 T ) " int nine9i*its 1 Bine9i*itsD,Gi.Startin*)t(i;5); int i*itCo/nt 1 Math.Min( i*its : i, T); strin* s 1 strin*.Format(""?:9T-", nine9i*its); #i.)##en ( s.S/&strin*(?, i*itCo/nt));
// Show progress +chec6ing for Cancel. e.Pi 1 pi.ToString+./ e.0igitsSoFar 1 i 7 digitCount/ ShowProgress+sender- e./ if+ e.Cancel . !rea6/

This techni)ue represents a message passing model. This model is clear' safe' general/ purpose' and scalable. *t%s clear because it%s easy to see that the wor$er is creating a message' passing it to the 1*' and then chec$ing the message for information that may have been added during the 1* thread%s processing of the message. *t%s safe because the ownership of the message is never shared' starting with the wor$er thread' moving to the 1* thread' and then returning to the wor$er thread' with no simultaneous access between the two threads. *t%s general/purpose because if the wor$er or 1* thread needed to communicate information in addition to a cancel flag' that information can be added to the 5how8rogress0rgs class. #inally' this techni)ue is scalable because it uses a thread pool' which can handle a large number of long/running operations more efficiently than nacvely creating a new thread for each one. #or long/running operations in your Win#orms applications' you should first consider message passing.

As(nchronous We0 Services


9ne specific area in which you%ll want to use asynchronous Win#orms applications is when calling Web services. +alling a Web service is similar to passing a message between threads' e(cept that Web services messages travel between machines using standard protocols such as 6TT8 and T 7. *magine a .!"T Web service that calculated digits of pi using some way/cool fast pi calculation engine:
LHM LHM

#or thorough coverage of the whys and wherefores of Web services' you can read

.NET Web Services- Arc"itect&re and

,mplementation it" .NET 30ddison/Wesley' >0034' by Deith Ballinger.

pu!lic class CalcPiSer#ice 3 System.We!.Ser#ices.We!Ser#ice 4

IWe&Metho J #/&lic strin* CalcGi(int i*its) " Strin*+/il er #i 1 ne! Strin*+/il er("N",

i*its ; @);

.. Way:cool ,ast #i calc/lator r/nnin* on a h/*e #rocessor... ret/rn #i.0oStrin*(); -

!ow imagine a version of the +alc8i program that used the Web service instead of our slow client/side algorithm to calculate pi on giant machines with huge processors 3or even better' databases with more digits of pi cached than anyone could ever want or need4. 0lthough the underlying protocol of Web services is 6TT8/ and T 7/based and we could form a Web service re)uest fairly readily to as$ for the digits of pi we%re after' it%s simpler to let -5.!"T generate a class to ma$e the Web services calls for you. Jou can do this in the 8ro<ect menu using the 0dd Web @eference item. The 0dd Web @eference dialog allows you to enter the 1@7 of the W5A7 3Web 5ervice Aescription 7anguage4 that describes the Web service you%d li$e to call. #or e(ample' after installing the Web service sample that accompanies this boo$' you can access the W5A7 via the following 1@7:
LFM LFM

5ee the @ead e.htm file that comes with the boo$%s samples for detailed instructions on setting them up.

htt#:..localhost.CalcGiWe&Ser$ice.CalcGiSer$ice.asmx>WS9L

0ccepting the W5A7 in the 0dd Web @eference dialog will generate a client(side Web services pro%# class' a helper class that turns your method calls into Web services messages. The generated pro(y code for the +alc8i Web service loo$s li$e this:
names#ace )syncCalcGi.localhost " IWe&Ser$ice+in in*)ttri&/te(Bame1"CalcGiSer$iceSoa#", ...)J #/&lic class CalcGiSer$ice : Soa#Htt#ClientGrotocol " #/&lic CalcGiSer$ice() " this.<rl 1 "htt#:..localhost.CalcGiWe&Ser$ice.CalcGiSer$ice.asmx"; ISoa#9oc/mentMetho )ttri&/te("htt#:..tem#/ri.or*.CalcGi", ...)J
pu!lic string CalcPi+int digits. 4...5 pu!lic ;'sync"esult )eginCalcPi+ int digitsSystem.'syncCall!ac6 call!ac6o!9ect asyncState. 4...5 pu!lic string EndCalcPi+;'sync"esult async"esult. 4...5

+alling the Web service is now a matter of creating an instance of the pro(y and calling the method you%re interested in:

localhost.CalcPiSer#ice ser#ice 1 new localhost.CalcPiSer#ice+./

$oi calc+/tton%Clic4(o&'ect sen er, System.($ent)r*s e) " #i0ext+ox.0ext 1 ser$ice.CalcGi((int) i*its<#9o!n.Cal/e); -

Because Web services ma$e calls across machine and often networ$ boundaries' you should assume they%ll ta$e a long time' and' if called synchronously' they%ll bloc$ the 1* thread. Jou can use the standard techni)ues discussed in this chapter to call Web service methods asynchronously' but as you can tell in the generated pro(y code' there%s built/in support for asynchronous operations via the BeginT((C"ndT(( method pairs' one for each method on the Web service. The first step in retrofitting the sample application to use the Web service is to call the Web service pro(y%s Begin+alc8i method:
en/m CalcState " Gen in*, Calc/latin*, Cancele , CalcState state 1 CalcState.Gen in*;
localhost.CalcPiSer#ice ser#ice 1 new localhost.CalcPiSer#ice+./

$oi calc+/tton%Clic4(o&'ect sen er, System.($ent)r*s e) " s!itch( state ) " .. Start a ne! calc/lation case CalcState.Gen in*: .. )llo! cancelin* state 1 CalcState.Calc/latin*; calc+/tton.0ext 1 "Cancel";
// Start We! ser#ice re>uest ser#ice.)eginCalcPi+ +int.digitsIp0own.Laluenew 'syncCall!ac6+PiCalculated.null./

&rea4; .. Cancel a r/nnin* calc/lation case CalcState.Calc/latin*: state 1 CalcState.Cancele ; calc+/tton.(na&le 1 ,alse; &rea4; .. Sho/l nFt &e a&le to #ress Calc &/tton !hile itFs cancelin* case CalcState.Cancele : 9e&/*.)ssert(,alse); &rea4;

ser#ice.'!ort+./ // Fail all outstanding re>uests

#oid PiCalculated+;'sync"esult res. 4...5

The Begin+alc8i method ta$es the method parameters and an instance of an 0sync+allbac$ delegate. The application provides the 8i+alculated event handler to match the 0sync+allbac$ signature. The 8i+alculated method' which will be called when the Web service returns' is responsible for harvesting results. 0lso' even though there is no way to get progress from a Web service 3all the more reason to call it asynchronously4' calls to a Web service can be canceled by a call to the 0bort method. Be careful with this one' however' because it will cancel all outstanding re)uests' not <ust a specific one. When the 0sync+allbac$ event handler is called' it means that the Web service has returned something:
$oi GiCalc/late (6)sync2es/lt res) "
try 4

Sho!Gi(ser#ice.EndCalcPi+res.); 5 catch+ We!E*ception e* . 4


5

Sho!Gi(ex.Messa*e);

The call to "nd+alc8i' passing in the *0sync@esult parameter' provides any results of calling the Web service. *f the Web service call was successful' the return value is pi. *f' on the other hand' the Web service call was unsuccessful' because it timed out or was canceled' "nd+alc8i will throw a Web"(ception. That%s why 8i+alculated wraps the call to "nd+alc8i in a try/catch bloc$. 5how8i will be called on to show either the digits of pi that were calculated by the Web service or the e(ception message 3the result of a canceled Web service call is shown in #igure 1H.I4. Figure 1#.0. The /esult of a Canceled Call to the Pi We9 ,er&ice

The 5how8i method displays the results of the Web service invocation:
delegate #oid ShowPi0elegate+string pi./

$oi Sho!Gi(strin* #i) " i,( this.6n$o4e2e8/ire 11 ,alse ) " #i0ext+ox.0ext 1 #i; state 1 CalcState.Gen in*; calc+/tton.0ext 1 "Calc"; calc+/tton.(na&le 1 tr/e; else " Sho!Gi9ele*ate sho!Gi 1 ne! Sho!Gi9ele*ate(Sho!Gi); this.+e*in6n$o4e(sho!Gi, ne! o&'ectIJ "#i-); -

!otice that the 5how8i method contains the chec$ to see whether an invo$e is re)uired' and it calls +ontrol.Begin*nvo$e to transfer from the current thread to the 1* thread' <ust as we%ve been doing for most of this chapter. This is necessary because the Web service completion notification will come in on a thread other than the 1* thread.

Where Are We?


The pi calculator e(ample demonstrates how to perform long/running operations while still displaying a progress dialog and $eeping the 1* responsive to user interaction. !ot only does it leverage multiple threads to split the 1* from a long/running operation' but also the 1* thread communicates further user input bac$ to the wor$er thread to ad<ust its behavior. 0lthough it could use shared data' the application uses a message passing scheme between threads to avoid the complications of synchroni=ation. .!"T has e(tensive threading support' so techni)ues other than message passing can certainly be used to achieve the same ends. 6owever' whatever method you choose' it%s important to remember that a wor$er thread cannot call methods nor set properties on a control. 9nly the 1* thread can do that. The +ontrol.*nvo$e@e)uire property tells you whether you need to transition from a wor$er thread to a 1* thread' and the +ontrol.*nvo$e and +ontrol.Begin*nvo$e methods perform the transition for you.

Chapter 1'. We9 Deplo!ment


5o far' this boo$ has been targeted at using Win#orms to build applications' forms' and controls. The traditional means of deploying such code is via a setup application' or more recently' a icrosoft *nstaller 3 5*4 file. The problem is getting the setup e(ecuted on the desired client machines and' after that%s done' $eeping the clients up/to/date. Web applications' on the other hand' offer a much more primitive application and control implementation framewor$' but a much simpler deployment model. 0ll that%s needed to $eep a Web client user up/to/date is to $eep the files on the Web server itself up/to/date. Win#orms supports this model not only for controls hosted on a Web page but also for Win#orms applications themselves' marrying the simplicity and power of Win#orms development with the simplicity and power of the Web deployment model.

,osting Controls in 'nternet !+"lorer


9ne way to deploy Win#orms code over the Web is to use *nternet "(plorer 3*"4 F.01; to host a Win#orms control on a Web page. Jou do this using an 6T 7 ob<ect tag formatted appropriately:
=o&'ect i 1"iectrl"
classid1 iectrl.dll#iectrl.IserControlB

!i th1"5??" hei*ht1"5??"> =.o&'ect>

The ob<ect tag is very much li$e the ob<ect tag used to pull in a +9 control' something we%ve had for several versions of *". The *A defines the variable name to be used in script code. The width and height define the dimensions that the control will draw itself into.

Control Creation
The only thing that%s different is the classid attribute. *nstead of specifying a +9 3+75*A4' classid specifies the name of the .!"T type to create in the form:
= ll>U=names#ace>.=class>

class *A

The sample classid attribute shown in #igure 1F.1 refers to the 1ser+ontrol1 class in the iectrl namespace in the iectrl.dll assembly. This classid will cause *" to download the A77 3if it%s not already cached4' create an instance of the iectrl.1ser+ontrol1 class' and show it on the Web page. Figure 1'.1. 1 WinForms Control Hosted in +%

The control in #igure 1F.1 is a 1ser+ontrol that loo$s li$e #igure 1F.> in the Aesigner. Figure 1'.2. The ,ample Control ,hown in the Designer

0s part of the creation process' you can augment the ob<ect tag with nested param tags that set public properties on the control after a successful creation:
=o&'ect i 1"iectrl" ...> =.o&'ect>
<param name1 somethingPrefi* #alue1 hi2 /=

This code sets the 5omething8refi( property of the newly created 1ser+ontrol1 ob<ect to the value &hi2 &. 5etting of parameters is pretty loose in that the case doesn%t matter' nor do the double )uotes around the value 3unless you%re embedding spaces' as in this e(ample4. 0ll reasonable means will be used to find the appropriate property and to convert the value to the appropriate type. *f there is no public property named 5omething8refi(' the parameter will be ignored.

Control +nteraction

0fter you%ve created the ob<ect' you can set properties and call methods in client/side script using the same synta( you%d use for the native ob<ects of the scripting language itself:
=3:: #a*e.htm ::> ... =in#/t ty#e1"&/tton" $al/e1"Say Somethin*"
onclic61 iectrl.saySomething+:something:.

.>

This 6T 7 creates a button that' when pressed' calls the 5ay5omething method of the iectrl ob<ect' which <ust happens to be the Win#orms control created earlier in the ob<ect tag. 5imilar 6T 7 can be used to handle the control%s events:
=3:: #a*e.htm ::> ...
<script for1 iectrl e#ent1 JuchE#ent+degree. =

alert("D/ch to the " ;


</script=

e*ree ; "th

e*ree3");

This 6T 7 creates an event handler for the iectrl ob<ect that handles the 9uch"vent' ta$ing a single argument. 1ntil now' all the interoperability mappings between unmanaged +9 ob<ects on the *" side and managed ob<ects on the .!"T side have been seamless as far as the control was concerned. To be hosted in *"' a Win#orms control merely has to derive ultimately from the +ontrol class' implement a public constructor that ta$es no arguments' and e(pose public properties and methods. 6owever' e(posing events re)uires greater wor$. #or e(ample' the 1ser+ontrol1 class e(poses a single public event:
L1M L1M

Technically' to be hosted in *"' a Win#orms control must also set the assembly/side 0llow8artiallyTrusted+allers0ttribute' which is discussed

in more detail later in this chapter.

.. <serControl5.cs ... names#ace iectrl "


pu!lic delegate #oid JuchCall!ac6+int degree./

#/&lic class <serControl5 : <serControl "


pu!lic e#ent JuchCall!ac6 JuchE#ent/

...

To e(pose a control%s events to *" re)uires bundling them into a +9 so&rce interface' which is a list of methods that constitute events that a +9 ob<ect will fire. To bundle a set of .!"T events into a +9 source interface' you must list the events as methods whose names match the names of the events in the .!"T control and whose signatures match the delegates of which the methods are instances:
.. <serControl5.cs ... I6nter,ace0y#e(Com6nter,ace0y#e.6nter,ace6s69is#atch)J

#/&lic inter,ace 6D/ch($ents " I9is#6 (5)J #oid JuchE#ent+int degree./ -

*n addition to listing each event as a method' you must use the Aisp*A attribute to mar$ each method in the interface with an interface/uni)ue' positive integer needed by +9 . 0lso' you must use the *nterfaceType attribute to mar$ the interface as a whole as a +9 source interface. Both attributes come from the 5ystem.@untime.*nterop5ervices namespace. 0fter defining the interface' you also must use the +om5ource*nterfaces attribute 3also from the 5ystem.@untime.*nterop5ervices namespace4 to mar$ the control that fires the events from that interface:
.. <serControl5.cs ...

%ComSource;nterfaces+typeof+;JuchE#ents..(

#/&lic class <serControl5 : <serControl "...-

This combination of attributes will provide the +9 wrapper around your .!"T control with enough metadata to perform the mapping needed to e(pose events to 6T 7 in *". 1nfortunately' e(posing events to *" isn%t enough to actually fire events. Jou must do two more things before this will wor$. The first additional re)uirement is that you increase permissions so that code deployed from the Web has permission to ma$e the transition from managed to unmanaged code. This can be accomplished by awarding #ull Trust to the assembly that contains the control. #or this' you use the Trust 0n 0ssembly Wi=ard available from 5tart N 5ettings N +ontrol 8anel N 0dministrative Tools N icrosoft .!"T #ramewor$ Wi=ards. This process is discussed at length later in this chapter. The second re)uirement for firing events into unmanaged code is that you grant the permission to call unmanaged code to the managed code hosting your Win#orms controls. Jou use the 0ssert method of the permission ob<ect:
.. <serControl5.cs ... $oi la&el5%Clic4(o&'ect sen er, ($ent)r*s e) "
// 'ssumes assem!ly granted unmanaged code permissions SecurityPermissionFlag flag 1 SecurityPermissionFlag.InmanagedCode/ SecurityPermission perm 1 new SecurityPermission+flag./ perm.'ssert+./// 0'NKE"2 +read on2.

i,( D/ch($ent 31 n/ll ) D/ch($ent(5?);

6owever' it%s very li$ely that neither of these re)uirements ma$es sense unless you%re familiar with .!"T%s security model' which * discuss ne(t.

Code Access Securit(

.!"T brings with it a new security model for deployed code. *nstead of an assembly getting the permissions of the process running the code' the .!"T +ode 0ccess 5ecurity 3+054 model grants code permissions based on where the code originates. To view the current permission settings on your machine' use the icrosoft .!"T #ramewor$ +onfiguration tool 3available in your 0dministration Tools menu4. Arilling into the 8ermission 5ets for the achine%s @untime 5ecurity 8olicy reveals a number of entries' including #ullTrust' 7ocal*ntranet' *nternet' and so on. #igure 1F.3 shows the set of *nternet permissions.
L>M L>M

#or a much more detailed discussion of +05' you%ll want to read

Essential .NET 30ddison/Wesley' >0034' by Aon Bo(' with +hris

5ells.

Figure 1'. . Default +nternet Permission ,et

Table 1F.1 compares the 7ocal*ntranet permission set to the *nternet permission set. 0ssemblies are associated with a permission set in any number of ways: according to the publisher' the site' the strong name' the security =one' and so on. ost of the default code groups associate code with a =one. #or e(ample' the yQ+omputerQKone is associated with the #ullTrust permission set' and the 7ocalQ*ntranetQKone is associated with the 7ocal*ntranet permission set. *n release 1.0 of .!"T' the *nternetQKone was associated with the *nternet permission set' but as of service pac$ 1 of the .!"T runtime' code from the *nternetQKone is associated with the !othing permission set by default. This change in 581 reflects some doubt that icrosoft had as to the full safety of the .!"T #ramewor$ +lass 7ibrary code. 0s of .!"T 1.1' the doubts have been resolved' and the *nternetQKone is again associated with the *nternet permission set.

Ta9le 1'.1. +ntranet and +nternet C1, Permissions


Permission Ce#el Cocal;ntranet ;nternet

#ileAialog #ileAialog *solated5torage#ile *solated5torage#ile 8rinting 8rinting @eflection 5ecurity 5ecurity 1* 1* 1* 1* Web Web

1nrestricted 0ccessR9pen 0llowR0ssembly*solationBy1ser 0llowRAomain*solationBy1ser 7evelRAefault8rinting 7evelR5afe8rinting #lagsR@eflection"mit #lagsR0ssertion #lagsR"(ecution 1nrestricted +lipboardR9wn+lipboard WindowR5afe5ubWindows WindowR5afeTop7evelWindows +onnectRhttp to originating site +onnectRhttps to originating site

Jes Jes Jes Jes Jes Jes Jes Jes Jes Jes Jes Jes Jes Jes Jes

!o Jes !o Jes !o Jes !o !o Jes !o Jes Jes Jes Jes Jes

The =one where an assembly originates is determined by the path used to find the assembly' as shown in Table 1F.>. *f an assembly needs to $now the =one it%s running in' it can access the =one via the Kone class in 5ystem.5ecurity.8olicy:
/sin* System.Sec/rity; /sin* System.Sec/rity.Golicy; ... strin* a##&ase 1 )##9omain.C/rrent9omain.+ase9irectory; Sec/rity\one 7one 1 \one.CreateFrom<rl(a##&ase).Sec/rity\one;

By default' the loaded assembly gets the union of all the permissions from all the code groups to which it belongs and must live within the confines of those permissions. 0ny attempt to perform an action for which the assembly does not have the corresponding permission will result in a security e(ception.

Chec5ing for Permissions

0lthough an application can catch a 5ecurity"(ception if it violates its set of permissions' it can also chec$ first to see whether it%s got the permissions it%s after. This allows an application to downgrade its capabilities if appropriate permissions aren%t available. +hec$ing for permission is a matter of creating a permission ob<ect' demanding that permission' and catching the security e(ception if the demand fails. Ta9le 1'.2. How an 1ssem9l!?s ;one +s Determined
Path E*ample [one

7ocal file

c:O fooOfoo.e(e

y+omputer

1!+ name or OOserverOfooOfoo.e(e or http:CCserverCfooCfoo.e(e or 7ocal*ntranet non/dotted site http:CClocalhostCfooCfoo.e(e or =:OfooOfoo.e(e if = is mapped 1@7 to a networ$ share 0ll numeric *8 http:CC111FIS8SS3CfooCfoo.e(e or address or http:CCwww.sellsbrothers.comCfooCfoo.e(e or dotted/site http:CC1>I.0.0.1CfooCfoo.e(e 1@7 *nternet

#or e(ample' to chec$ whether your control is allowed to fire an event into unmanaged code' you use an instance of the 5ecurity8ermission class from the 5ystem.5ecurity.8ermissions namespace:
/sin* System.Sec/rity; /sin* System.Sec/rity.Germissions;
!ool ,a#ePermission+;Permission perm. 4 try 4 perm.0emand+./ 5 catch+ SecurityE*ception . 4 return false/ 5 return true/ 5

$oi

la&el5%Clic4(o&'ect sen er, ($ent)r*s e) "

SecurityPermissionFlag flag 1 SecurityPermissionFlag.InmanagedCode/ SecurityPermission perm 1 new SecurityPermission+flag./ if+ 2,a#ePermission+perm. . return/

... .. Fire e$ent -

*f you wonder which permission you need for a specific call' you should start with the security e(ception itself. 1nfortunately' unli$e most other e(ceptions in .!"T' the security e(ception provides somewhat terse information. That%s to prevent bad people from learning too much about an application%s implementation' although it does tend to ma$e debugging security e(ceptions a bit harder. *n these cases' * chec$ the documentation' which is surprisingly good about telling you which permissions are needed and when.
L3M L3M

To chec$ on all $inds of details' including permissions' for a Win#orms control hosted in *"' you might try ad<usting the undocumented

Aebug*"6ost @egistry setting as discussed at http:CCdiscuss.develop.comCarchivesCwa.e(e?0>Rind01090[7RA9T!"T[8R@9>FS.

1warding Permissions
"ven if your assembly has permission to perform an action' such as calling unmanaged code' it%s not necessarily the case that another assembly calling your assembly has those permissions. +05 is set up so that a re)uest for a permission will be denied unless everyone in the call chain has the needed permission. #or e(ample' even if an assembly hosted in *" has permission to call into unmanaged code' as of .!"T 1.( the managed code hosting that assembly does not have that permission. *f the control needs to e(ercise a permission that the code above it in the call chain doesn%t have' the control must grant the permission it has to everyone hosting it. Jou do this using the 0ssert method on the permission ob<ect:
$oi la&el5%Clic4(o&'ect sen er, ($ent)r*s e) " .. Ma4e s/re !e ha$e #ermission to call /nmana*e i,( 3Ha$eGermission(#erm) ) ret/rn;
// Krant managed hosting code permission to call unmanaged code perm.'ssert+./

SecurityPermissionFlag flag 1 SecurityPermissionFlag.InmanagedCode/ SecurityPermission perm 1 new SecurityPermission+flag./

co e

i,( D/ch($ent 31 n/ll ) D/ch($ent(5?); -

*n this code' we create a permission ob<ect again' but after chec$ing to ma$e sure we have the appropriate permission' we also grant that permission' albeit temporarily' until the method returns. *n general' granting permissions in this way is a really bad idea and should be used only in a very narrow scope. The reason that .!"T is chec$ing everyone in the call chain is to ma$e sure that bad assemblies don%t coerce good assemblies into performing their evil deeds. By asserting permission' you%re telling .!"T that you%ll vouch for everyone in the call chain.a weighty responsibility to ta$e onto your shoulders.

o.Touch #e"lo(ment
*n addition to letting you deploy Win#orms controls' .!"T also lets you use *" to deploy entire Win#orms applications. This is a completely new feature for the Windows platform. Jou can best see its value by trying it: 1. 1sing the !ew 8ro<ect dialog' create a new Windows application and call it Aeployment#un. >. #eel free to drag and drop some controls from the Toolbo(' but before going too far' compile your application. 3. *n the shell e(plorer' navigate to your Aeployment#unObin folder and right/clic$ on the Aebug folder' choosing 8roperties. H. +hoose Web 5haring and turn it on' using Aeployment#un as the name of the share. This will create a new *nternet *nformation 5erver 3**54 Web application containing Aeployment#un.e(e. F. !ow surf to your Win#orms app using 5tart N @un and the following 1@7: http:CClocalhostCAeployment#unCAeployment#un.e(e.

S. 0fter bas$ing in the glory of using no(to&c" deplo#ment 3!TA4 to deploy a real Windows application over the Web without doing any setup' stop playing around and read the rest of this chapter2

1pplication Download
0s a test of the no(to&c" deplo#ment model for Win#orms applications' * built a simple game' as shown in #igure 1F.H.
LHM LHM

0ny similarity to any other very popular game that you may already be familiar with is strictly intentional.

Figure 1'.#. The :ame of WahooI

9n the Web server' deployment of a .!"T application is merely a matter of dropping the .e(e file into a Web application directory so that the Web server can hand out the bits on demand. The .!"T runtime is not re)uired on the server' nor is icrosoft%s *nternet *nformation 5erver nor even Windows itself. 9n the client side' however' things are a bit more interesting. When you feed *nternet "(plorer an 1@7 such as http:CClocalhostCwahooCwahoo.e(e' it forms an 6TT8 re)uest for the wahoo.e(e file to be streamed bac$ to the client:
KET /wahoo.e*e ,TTP/B.B

)cce#t: Q.Q )cce#t:Lan*/a*e: en:/s )cce#t:(nco in*: *7i#, e,late <ser:)*ent: Mo7illa.H.? (com#ati&le; MS6( X.?; Win o!s B0 A.5; [N5@HX5; .B(0 CL2 5.?.NK?A) Host: localhost Connection: Lee#:)li$e

The response from the server is <ust a stream of bytes:


,TTP/B.B GAA JP

Ser$er: Microso,t:66S.A.5 9ate: Fri, ?5 Fe& @??@ ?@:55:@T EM0 Content:0y#e: a##lication.octet:stream )cce#t:2an*es: &ytes Last:Mo i,ie : Fri, ?5 Fe& @??@ ?5:H5:5X EM0 (0a*: "A?aae?PTc5aac55:T5X" Content:Len*th: HA?AX
<<stream of !ytes from wahoo.e*e==

*n addition to the bytes themselves' the last modified date and time are cached by *" on the client side. This is used to form a re)uest each subse)uent time that the application is launched using the same 1@7:
KET /wahoo.e*e ,TTP/B.B

)cce#t: Q.Q )cce#t:Lan*/a*e: en:/s )cce#t:(nco in*: *7i#,

e,late

;f?$odified?Since3 Fri- AB Fe! GAAG AB3SB3BE K$T

6,:Bone:Match: "A?aae?PTc5aac55:T5X" <ser:)*ent: Mo7illa.H.? (com#ati&le; MS6( X.?; Win o!s B0 A.5; [N5@HX5; .B(0 CL2 5.?.NK?A) Host: localhost Connection: Lee#:)li$e

The *f/ odified/5ince header is $ept in *"%s download cache and is sent bac$ with each re)uest. *n this way' if the bits on the server haven%t changed' the server can respond with a header that indicates that the cache is still good' reducing the payload that needs to be downloaded to the client:
,TTP/B.B @AS Not $odified

Ser$er: Microso,t:66S.A.5 9ate: Fri, ?5 Fe& @??@ ?@:H@:?N EM0

(0a*: "a?,aT@&cPaac55:T5X"
Content?Cength3 A

The bytes themselves are cached in two places: *"%s download cache' managed by *"' and the .NET do nload cac"e' which is a cache for .!"T assemblies 3both .e(e and .dll files4 downloaded on demand. The contents of the .!"T download cache can be e(amined using gacutil.e(e Cldl and cleared using gacutil.e(e Ccdl. *f' during your testing' you%d li$e to ensure that a download happens' ma$e sure to clear *"%s cache using the *nternet control panel and clear .!"T%s cache using gacutil.

=ersioning
While we%re tal$ing about caching and downloading the &latest version'& you may be curious about how actual versions affect things. 0s you may $now' you can tag a .!"T assembly with a specific version using the 0ssembly-ersion0ttribute:
LFM LFM

!"T versioning is discussed at length in

Essential .NET 30ddison/Wesley' >0034' by Aon Bo(' with +hris 5ells.

.. Stron* namin* is re8/ire !hen $ersionin* Iassem&ly: )ssem&lyLeyFile)ttri&/te("!ahoo.4ey")J


%assem!ly3 'ssem!lyLersion'ttri!ute+ B.G.@.S .(

With this in mind' you may wonder whether .!"T versioning affects caching and downloading. *t does.sometimes. When a re)uest is formed for http:CClocalhostCwahooCwahoo.e(e' the runtime doesn%t have any idea which version we%d li$e' so it simply as$s the Web server for the latest 8TTP version as indicated by the *f/ odified/5ince header sent along with the 6TT8 re)uest. *f the runtime already has the latest 6TT8 version' no download needs to happen. 6owever' if the server has a newer binary 3based on the dateCtime stamp on the file4.even if that binary is of a lower .NET version 3based on the 0ssembly-ersion0ttribute4.the latest 6TT8 version will be downloaded. *n other words' the .!"T version plays no role in what constitutes the &latest version& of the application launched with an 1@7. 9n the other hand.and this is the &sometimes& part.any assembly can reference other assemblies' either implicitly as part of the manifest or e(plicitly via the 0ssembly.7oad method. #or e(ample' wahoo.e(e references wahoo+ontrol.dll. Whatever the .!"T version is of wahoo+ontrol.dll that wahoo.e(e compiles against will be the version that the assembl# resolver 3the part of the .!"T runtime responsible for finding code4 e(pects to find at run time. *f that .!"T version of wahoo+ontrol.dll is in the cache' the assembly resolver will not send a re)uest to the Web server as$ing whether it has a newer .!"T version. This means that if you%d li$e the client to have a newer .!"T version of a referenced assembly' you must ma$e sure that the Web server is serving up an updated "T" assembly that refers to the referenced assembly.

/elated Files

0s * <ust mentioned' assemblies can reference other assemblies. #or e(ample' an assembly may already be present in the /lobal Assembl# )ac"e 3G0+4' the place where systemwide shared assemblies are $ept in .!"T. *f an assembly is in the G0+' such as 5ystem.Windows.#orms.dll' then that%s where the assembly will be loaded from. *f the assembly is not in the G0+' the .!"T download cache is chec$ed. *f the download cache doesn%t contain the assembly' the assembly resolver goes bac$ to the originating server using the application base 9appbase: of the assembly. The appbase is the &directory& from which the initial assembly was loaded.for e(ample' c:Owahoo or http:CClocalhostCwahoo. The appbase is available using the GetAata function of the currently e(ecuting application domain 9appdomain:' which is the conte(t under which all .!"T code runs. The appbase can be obtained for the appdomain in this way:
LSM LSM

#or details of appdomains and appbases' you should read

Essential .NET 30ddison/Wesley' >0034' by Aon Bo(' with +hris 5ells.

strin* a##&ase 1 )##9omain.C/rrent9omain.+ase9irectory;

#or e(ample' when wahoo.e(e needs wahoo+ontrols.dll and it%s not present in the G0+ or the download cache' the assembly resolver will go bac$ to the originating Web server' as you%ve seen. @eferenced assemblies' however' are not the only files that the assembly resolver will loo$ for when an e(ecutable is loaded. *n fact' on my machine when * surf to the initial version of the signed wahoo.e(e after the caches have been cleared' 3F re)uests are made for files' as shown in Table 1F.3. Ta9le 1'. . Files /eGuested When ,urfing to wahoo.e3e
"e>uest # KET "e>uest

1 > 3 H F S I 8 9 10 11 1>

CwahooCwahoo.e(e CwahooCwahoo.e(e.config CwahooCWahoo+ontrol.A77 CwahooCen/15CWahoo.resources.A77 CwahooCen/15CWahoo.resourcesCWahoo.resources.A77 CwahooCbinCen/15CWahoo.resources.A77 CwahooCbinCen/15CWahoo.resourcesCWahoo.resources.A77 CwahooCen/15CWahoo.resources."T" CwahooCen/15CWahoo.resourcesCWahoo.resources."T" CwahooCbinCen/15CWahoo.resources."T" CwahooCbinCen/15CWahoo.resourcesCWahoo.resources."T" CwahooCenCWahoo.resources.A77

Ta9le 1'. . Files /eGuested When ,urfing to wahoo.e3e


"e>uest # KET "e>uest

13 1H 1F 1S 1I 18 19 >0 >1 >> >3 >H >F >S >I >8 >9 30 31 3> 33 3H 3F

CwahooCenCWahoo.resourcesCWahoo.resources.A77 CwahooCbinCenCWahoo.resources.A77 CwahooCbinCenCWahoo.resourcesCWahoo.resources.A77 CwahooCenCWahoo.resources."T" CwahooCenCWahoo.resourcesCWahoo.resources."T" CwahooCbinCenCWahoo.resources."T" CwahooCbinCenCWahoo.resourcesCWahoo.resources."T" CwahooCen/15CWahoo.resources.A77 CwahooCen/15CWahoo.resourcesCWahoo.resources.A77 CwahooCbinCen/15CWahoo.resources.A77 CwahooCbinCen/15CWahoo.resourcesCWahoo.resources.A77 CwahooCen/15CWahoo.resources."T" CwahooCen/15CWahoo.resourcesCWahoo.resources."T" CwahooCbinCen/15CWahoo.resources."T" CwahooCbinCen/15CWahoo.resourcesCWahoo.resources."T" CwahooCenCWahoo.resources.A77 CwahooCenCWahoo.resourcesCWahoo.resources.A77 CwahooCbinCenCWahoo.resources.A77 CwahooCbinCenCWahoo.resourcesCWahoo.resources.A77 CwahooCenCWahoo.resources."T" CwahooCenCWahoo.resourcesCWahoo.resources."T" CwahooCbinCenCWahoo.resources."T" CwahooCbinCenCWahoo.resourcesCWahoo.resources."T"

The .config File The first re)uest in Table 1F.3 ma$es sense because it%s the assembly we as$ed for to begin with. The second re)uest is for a .config file' which you may recall from +hapter 11: 0pplications and 5ettings. *f you%re serving your Win#orms application from an **5

installation' you%ll need to enable anonymous access to your .e(e file%s Web application. 0lso' if 058.!"T is installed on the server' you may need to override the default behavior in a virtual directory to disable any .config files being supplied. *f you find that your application%s .config is available on the server but is not being downloaded to the client' use the following settings in a web.config file at the base of the virtual directory:
=con,i*/ration> =system.!e&>
<2?? "emo#e the Q.config handler so that we can ser#e up Q.e*e.config files- !ut ma6e it for!idden to ser#e up the we!.config file itself. ??= <http,andlers= <remo#e #er!1 Q path1 Q.config /= <add #er!1 Q path1 we!.config type1 System.We!.,ttpFor!idden,andler /= </http,andlers=

=.system.!e&> =.con,i*/ration>

These configuration settings remove the restriction on all .config files and reinstate it for only web.config files' which still need protection from download. 5ome versions of 058.!"T disable all .config files' whereas others don%t' so if the UremoveV element causes an error' you shouldn%t need these e(tra settings in your web.config at all. /esources The third re)uest in Table 1F.3 is for the wahoo+ontrol.dll assembly that wahoo.e(e references' as we e(pect. The ne(t re)uest is for a resources assembly called Wahoo.resources. Jou should recogni=e this re)uest 3and the rest of the re)uests4 as made by the resource manager loo$ing for locali=ed resources' as discussed in +hapter 10: @esources. These re)uests are being made for the Wahoo+ontrol ob<ect%s bac$ground image' which is stored in the wahoo+ontrol assembly%s resources. 9n the local machine or a 70!' the locali=ation probe performed by the resource manager is chewy goodness' but that%s not necessarily so in a W0! environment. When the latest wahoo.e(e and wahoo+ontrol.dll are already cached on my machine' *%ve seen these additional re)uests ta$e more than IF percent of the load time. These e(tra round/trips can ma$e for a very poor user e(perience' especially when culture/neutral resources are the only resources in your application. *n this case' you have a couple of options. 9ne option is to avoid using Win#orms Aesigner to set properties that use resources. *n this way' the Aesigner/generated code doesn%t create a resource manager. Then' to load resources' you write the code yourself in a culture/ neutral way:
#/&lic MainForm() " .. Let the 9esi*ner:*enerate 6nitiali7eCom#onent(); co e r/n

// ;nit culture?neutral properties this.game.)ac6ground;mage 1 new )itmap+typeof+$ainForm.- s!logo.gif ./

7oading resources manually is handy for resources that you $now are culture/neutral and never want to locali=e' but it does mean giving up Win#orms Aesigner for resource management. *f you share my addiction to WJ5*WJG form design' the resource manager supports an optimi=ation that often ma$es this $ind of handwritten code unnecessary. @ecall !eutral@esources7anguage0ttribute from +hapter 10: @esources. This attribute mar$s the resources bundled in your assembly as culture/specific so that they will be found first without the round/trips to the Web server when launched from that culture:
Iassem&ly: Be/tral2eso/rcesLan*/a*e)ttri&/te("en:<S")J

This attribute reduces the number of re)uests from 3H to > when the assembly is launched from a machine with the culture of the assembly' thereby improving load times considerably. *f you%d li$e to let the Aesigner generate the code and reduce round/trips for cultures other than the one in which you%re writing the application' you can put =ero/length files in the first places that the resource manager loo$s. #or e(ample' =ero/length files on the server under the following names will be used for the "nglish language and the 15:
en:<S\assemBame.reso/rces. ll en\assemBame.reso/rces. ll

The presence of =ero/length files at the appropriate locations causes the resource manager to find the resource files it%s loo$ing for' even if they%re empty. When it comes to resolving specific resources' =ero/length files will cause an e(tra loo$up by the resource manager for a culture/ or language/specific resource' but if it%s not found' the culture/neutral resources in the resolving assembly will be used in the normal way. Wor5ing *ffline 9f course' the ultimate reduction of re)uests is to use no re)uests at all. By putting *" into offline mode 3via the #ile menu4' surfing to an !TA application will wor$ completely from the cache. 0 better model is to use the cache automatically if the client machine was not connected to the *nternet or the server was unavailable' but' as of .!"T 1.1' that feature is not part of the !TA implementation.

$artiall( Trusted Assem0l( Considerations


0s * mentioned earlier in this chapter' by default .!"T code deployed over the Web will be awarded a smaller set of permissions than code launched directly from the hard drive in the y+omputer =one. 0ny assembly' whether it%s an .e(e or a .dll' that%s launched from the y+omputer =one will be f&ll# tr&sted by default' and this means that it can do anything that the launching user is allowed to do. 0ssemblies deployed from the Web' on the other hand' will be partiall# tr&sted' because they aren%t allowed to do everything. *f you%re

building assemblies that are designed to wor$ in a partially trusted environment' you need to ta$e this into account' or else your users will see a lot of security e(ceptions. #or e(ample' the wahoo and wahoo+ontrol assemblies are designed to wor$ within the restricted set of *nternet permissions' and this meant that it was difficult to implement the functionality * wanted. The challenging areas * encountered included the following:

0llowing partially trusted callers @emembering and restoring user settings 6andling $eystro$es +ommunicating with Web services @eading and writing files 8arsing command line arguments

1llowing Partiall! Trusted Callers


The first problem * ran into when deploying my wahoo.e(e and wahoo+ontrol.dll assemblies was enabling wahoo.e(e to ma$e calls into wahoo+ontrol.dll. This happened as soon as * signed wahoo+ontrol.dll with a publicCprivate $ey pair 3as all .!"T assemblies should be signed4:
Iassem&ly: )ssem&lyLeyFile)ttri&/te("!ahoo.4ey")J

.!"T has a giant #ran$enstein switch for each signed assembly' and this switch determines whether the assembly is secure enough to be called from partially trusted assemblies. By default' a signed assembly does not allow calls from partially trusted assemblies. This means that after wahoo+ontrol.dll is signed' by default it can%t be called from a partially trusted assembly' even the one that caused it to be downloaded 3in this case' wahoo.e(e4. To enable an assembly to be called from a partially trusted assembly' you need to set the assemblywide 0llow8artiallyTrusted+allers0ttribute 308T+04:
Iassem&ly: )llo!Gartially0r/ste Callers)ttri&/teJ

0lthough setting 0T8+0 enables wahoo+ontrol.dll to be called from wahoo.e(e' it also allows it to be called from any other partially trusted assembly. Jou should be very careful when you apply 08T+0' because now you%re on the hoo$ to ma$e sure that your assembly is robust in the face of partially trusted callers. icrosoft itself was cautious in applying this attribute' as you can see in Table 1F.H' which shows the ma<or .!"T assemblies that allow partially trusted callers as of .!"T 1.1.
LIM LIM

To generate this list' * used Deith Brown%s most e(cellent #ind08T+ utility' available at http:CCwww.develop.comC$brownCsecurityCsamples.htm

Ta9le 1'.#. 8ajor .(%T 1ssem9lies and Their 1PTC1 ,etting


'ssem!ly 'CPT' Set

0ccessibility.dll

Jes

Ta9le 1'.#. 8ajor .(%T 1ssem9lies and Their 1PTC1 ,etting


'ssem!ly 'CPT' Set

+ustom arshalers.dll icrosoft.E5cript.dll icrosoft.-isualBasic.dll icrosoft.-isualBasic.-sa.dll icrosoft.-isual+.All icrosoft.-sa.dll icrosoft.-sa.-b.+odeA9 8rocessor.dll mscorcfg.dll mscorlib.dll @eg+ode.dll 5ystem.+onfiguration.*nstall.dll 5ystem.Aata.dll 5ystem.Aata.9racle+lient.dll 5ystem.Aesign.dll 5ystem.Airectory5ervices.dll 5ystem.dll 5ystem.Arawing.Aesign.dll 5ystem.Arawing.dll 5ystem."nterprise5ervices.dll 5ystem. anagement.dll 5ystem. essaging.dll 5ystem.@untime.@emoting.dll 5ystem.@untime.5eriali=ation.#ormatters.5oap.dll 5ystem.5ecurity.dll 5ystem.5ervice8rocess.dll 5ystem.Web.dll

!o Jes Jes !o !o Jes !o !o Jes !o !o Jes !o !o !o Jes !o Jes !o !o !o !o !o !o !o Jes

Ta9le 1'.#. 8ajor .(%T 1ssem9lies and Their 1PTC1 ,etting


'ssem!ly 'CPT' Set

5ystem.Web. obile.dll 5ystem.Web.@egular"(pressions.dll 5ystem.Web.5ervices.dll 5ystem.Windows.#orms.dll 5ystem.T 7.dll

Jes Jes Jes Jes Jes

0ny assembly that doesn%t have the 0T8+0 setting cannot be used from any partially trusted assembly' even those given the &"verything& permission set. 9nly fully trusted assemblies can access assemblies that aren%t trusted for partially trusted callers. *n addition' you must use 08T+0 to mar$ assemblies containing Win#orms controls to allow the controls to be hosted in *".

,ettings
0fter an application has been run once' even an !TA application' users e(pect that any settings that they have changed will be saved for the ne(t time the application is run. 5toring and restoring application and user settings is covered in +hapter 11: 0pplications and 5ettings' but there are special considerations when you manage settings from partially trusted code. #or e(ample' by default partially trusted code doesn%t have permission to access the @egistry or permission to access the file system without user interaction. 6owever' isolated storage is e(plicitly allowed from partial trust' and this ma$es isolated storage the perfect place for user settings. @ecall from +hapter 11: 0pplications and 5ettings how isolated storage wor$s:
$oi MainForm%Closin*(o&'ect sen er, Cancel($ent)r*s e) " .. Sa$e the ,ormFs #osition &e,ore it closes
;solatedStorageFile store 1 ;solatedStorageFile.KetIserStoreFor'ssem!ly+./ using+ Stream stream 1 new ;solatedStorageFileStream+ $ainForm.t*t File$ode.Createstore. .

/sin*( StreamWriter !riter 1 ne! StreamWriter(stream) ) " FormWin o!State state 1 this.Win o!State; this.Win o!State 1 FormWin o!State.Bormal; writer.WriteCine (0oStrin*(this.Location)); writer.WriteCine (0oStrin*(this.ClientSi7e)); writer.WriteCine (0oStrin*(state)); -

The To5tring method is a helper for converting simple types to strings using a type converter:
.. Con$ert an o&'ect to a strin* strin* 0oStrin*(o&'ect o&') "
TypeCon#erter con#erter 1 Type0escriptor.KetCon#erter+o!9.KetType+../ return con#erter.Con#ertToString+o!9./

0lthough type converters are easy to use' they%re not as full featured as .!"T seriali=ation 3as covered in detail in 0ppendi( +: 5eriali=ation Basics4. 6owever' because .!"T seriali=ation can be used to set an ob<ect%s private fields' its use is strictly forbidden from partial trust' ma$ing type converters a useful partial trust alternative.

Custom 7ser +nput


*n addition to saving user settings between runs' most applications need to ta$e user input of some sort. *f the user input is going to one of the standard Win#orms controls' that%s not a problem from partially trusted code. 6owever' if a control needs to handle special $eys. Wahoo+ontrol' for e(ample' needs to handle arrow $eys.then as of .!"T 1.1' it must ta$e special measures. 0rrow $eys' Tab' and 5hift;Tab are special $eys because of their use in moving between controls in a form. This means that a rogue assembly allowed to consume the arrow $eys could easily hi<ac$ an entire form. #or that reason' a control is not allowed to override 8rocessAialogDey or *s*nputDey' either of which would allow such an activity. The .!"T runtime will throw a security e(ception whenever it attempts to compile a method that contains code that creates an instance of a type that overrides these or similar methods' protecting the user from a form/<ac$ing. 1nfortunately' this means that you can%t use these methods to have Wahoo+ontrol handle the arrow $eys. 0nother way to handle the arrow $eys is to let the parent form retrieve the $eys in its own implementation of 9nDeyAown 3an action that%s allowed4 and pass them to the control for processing. #or a form to handle $eystro$es that can be handled by a child control' such as the arrow $eys' a form can set its own Dey8review property to true. 0ll this wor$ed fine until e(perimentation with .!"T 1.( showed that some of the current Win#orm controls' such as 5tatusBar and Button' don%t actually let the parent form at these special $eys in other controls that allow special $eys through aren%t on the form' li$e Te(tBo(. Because the main Wahoo2 form contains only a custom control and a status bar' this becomes an &issue.& 0s a wor$around' the main Wahoo2 form creates an invisible Te(tBo( and adds it to the list of controls that the form is hosting:
#/&lic MainForm() " ... -

// ,'CP3 'dd a te*t !o* so that we can get the arrow 6eys Controls.'dd+new Te*t)o*+../

#ran$ly' *%m not proud of this techni)ue' but it lets the arrow $eys through in a partially trusted environment' and one does what one must while waiting for a new platform to sha$e out.

Communicating &ia We9 ,er&ices


+ommunicating with the user is not the only <ob of an !TA application. -ery often an application must also communicate to the outside world. *n the partially trusted =ones' this communication is limited to tal$ing bac$ only to the originating site and only via 6TT8 re)uests and Web services. 7uc$ily' the originating site is often what we want to tal$ to anyway' and Web services are easily fle(ible enough to handle the ma<ority of our communication needs. *n addition to being fle(ible' Web services provide a much saner model for the split of server/side and client/side code. *nstead of maintaining client state on the server' as a Web application often does' Web services typically provide a stateless end point that receives re)uests for service. These re)uests are typically large/grained and atomic in order to reduce re)uests and to let the client maintain its own state. Generating the client/side pro(y code necessary to tal$ to a Web service is as easy as adding a Web @eference to your pro<ect. Jou do this by pointing -5.!"T at the 1@7 for the Web service%s W5A7 3as discussed in +hapter 1H: ultithreaded 1ser *nterfaces4. +alling a Web service is a little tric$y' however' because partially trusted code isn%t allowed to ma$e Web service calls anywhere but bac$ to the originating server. *t%s up to you to ma$e sure that the 1@7' which is hard/coded into the generated Web service pro(y code' points at the originating server. Jou can do this by replacing the site in the hard/coded 1@7 with the site that you discover dynamically using the application domain%s appbase:
// Ket a client?side We! ser#ice pro*y of any type- replacing // the localhost site with the app!ase site static Soap,ttpClientProtocol KetSer#iceFor'pp)ase+Type type. 4 // Create an instance of the ser#ice using .NET "eflection Soap,ttpClientProtocol ser#ice 1 +Soap,ttpClientProtocol. type.'ssem!ly.Create;nstance+type.FullName./ try 4 // Set I"C to ser#er where this came from string app!ase 1 'pp0omain.Current0omain.)ase0irectory/ string site 1 System.Security.Policy.Site.CreateFromIrl+app!ase..Name/ ser#ice.Irl 1 ser#ice.Irl."eplace+ //localhost/ - // 7 site 7 / ./ 5 // ;f we can:t create a site from the app!ase// then we:re not an NT0 app and there:s no reason to // ad9ust the ser#ice:s I"C catch+ 'rgumentE*ception . 45 return ser#ice/ 5

$oi EetHi*hScores() " .. Eet scores

WahooScoresSer#ice ser#ice 1 +WahooScoresSer#ice. KetSer#iceFor'pp)ase+typeof+WahooScoresSer#ice../

WahooScoreIJ scores 1 ser$ice.EetScores(); .. Sho! hi*h scores...

The Get5ervice#or0ppBase helper creates an instance of any type of client/side Web services pro(y and then' if the application is !TA' replaces the &localhost& site with the site indicated by the appbase. This ma$es it handy not only to test your !TA application on your own machine from the y+omputer =one' but also to get the appropriate site when the !TA application is launched from an 1@7.

/eading and Writing Files


0fter *%d gotten the current high scores via the Web service' * found that * wanted to be able to cache them for later access 3to savor the brief moment when * was at the top4. .!"T ma$es it easy to read and write files and to show the #ile 5ave and #ile 9pen dialogs. 1nfortunately' only a limited subset of that functionality is available in partial trust. @eferring again to Table 1F.1' notice that the *ntranet =one has unrestricted file dialog permissions but no file *C9 permissions. This means that files can be read and written' but not without user interaction. 1nrestricted access to the file system is' of course' a security hole on par with buffer overflows and fa$e password dialogs. To avoid this problem but still allow an application to read and write files' a file can be opened only via the #ile 5ave or #ile 9pen dialog. *nstead of using these dialogs to obtain a file name from the user' we use the dialogs themselves to open the file:
Sa$eFile9ialo* l* 1 ne! Sa$eFile9ialo*(); l*.9e,a/lt(xt 1 ".txt"; l*.Filter 1 "0ext Files (Q.txt)YQ.txtY)ll ,iles (Q.Q)YQ.Q"; .. BD0(: Bot allo!e /nless !e ha$e File6DGermission .. l*.) (xtension 1 tr/e; .. l*.FileBame 1 "some,ile.txt"; i,( l*.Sho!9ialo*() 11 9ialo*2es/lt.DL ) " .. BD0(: Bot allo!e to call l*.FileBame
using+ Stream stream 1 dlg.JpenFile+. .

/sin*( StreamWriter !riter 1 ne! StreamWriter(stream) ) " !riter.Write("..."); -

!otice that instead of opening a stream using the 5ave#ileAialog #ile!ame property after the user has chosen a file' we call the 9pen#ile method directly. This gives partially trusted code the ability to read from a file' but only with user intervention and providing the code no $nowledge of the file system.

Command $ine 1rguments


9ne commonly used option when launching an application that doesn%t bring to mind security restrictions 3but still has them' as we%ll see4 is passing command line parameters. *n a normal application' command line parameters are available from the string array passed to ain:
static #oid $ain+string%( args. 4

,oreach( strin* ar* in ar*s ) " Messa*e+ox.Sho!(ar*); ... -

5imilarly' 1@7s have a well/$nown synta( for passing arguments: http:CCitwebChrHF>.e(e?uidRcsells[activityRvacation The combination of the two ma$es it seem natural to be able to pass command line arguments to !TA applications using the special 1@7 synta(. 1nfortunately' the support for pulling command line arguments from the launching 1@7 is new to 1.1 and underdocumented. 0lso' because the launching 1@7 is used to create the path to the .config file' full support for command line arguments re)uires that some code be run on the server side as well.
L8M L8M

There is a wor$around to enable pulling command line arguments from the launching 1@7 that wor$s in .!"T 1.0' too' and it%s covered later in

the chapter.

Client),ide ,upport for (TD 1rguments To pull the arguments out of the launching 1@7 re)uires' first' that we have access to the launching 1@7 from within the !TA application. To access the launching 1@7' .!"T 1.1 provides the 088Q701!+6Q1@7 data variable from the application domain:
.. Wor4s only ,or .B(0 5.5;
'pp0omain domain 1 'pp0omain.Current0omain/ o!9ect o!9 1 domain.Ket0ata+ 'PP8C'INC,8I"C ./

strin* a##La/nch<rl 1 (o&' 31 n/ll > o&'.0oStrin*() : ""); System.Win o!s.Forms.Messa*e+ox.Sho!(a##La/nch<rl);

The 1@7 used to launch the !TA application' including arguments' is provided in full by 088Q701!+6Q1@7. 1nfortunately' 088Q701!+6Q1@7 isn%t available in .!"T 1.0. 6owever' because the path to an !TA application%s .config file is only the 1@7 3including arguments4 with &.config& tac$ed onto the end' we can use that to pull out the e)uivalent of the 088Q701!+6Q1@7 in .!"T 1.0. #or e(ample' suppose we launch an application from this 1@7: http:CCfooCfoo.e(e?fooRbarB)uu(

That yields the following .config file path: http:CCfooCfoo.e(e?fooRbarB)uu(.config The application domain provides access to the .config file path' so we can use that and a little string manipulation to get what we need:
.. Wor4s only ,or .B(0 5.5; )##9omain omain 1 )##9omain.C/rrent9omain; o&'ect o&' 1 omain.Eet9ata(")GG%L)<BCH%<2L"); strin* a##La/nch<rl 1 (o&' 31 n/ll > o&'.0oStrin*() : ""); .. Fall:&ac4 ,or .B(0 5.? i,( a##La/nch<rl 11 "" ) "

const string e*t 1 .config / string configFile 1 domain.Setup;nformation.ConfigurationFile/ appCaunchIrl 1 configFile.Su!string+A- configFile.Cength ? e*t.Cength./

System.Win o!s.Forms.Messa*e+ox.Sho!(a##La/nch<rl);

!o matter which way you get the 1@7 used to launch the !TA application' both default *ntranet and *nternet permissions for .!"T 1.( allow access to command line argument information from partially trusted clients. 0fter you%ve got the full 1@7' it can be parsed for the arguments. The )uestion mar$ should be used to pull off the arguments at the end' and the ampersand should be used to separate individual arguments. 1nfortunately' although .!"T provides classes for easily parsing and decoding )uery string/li$e 1@7 command line arguments' partially trusted applications don%t have permissions to use them' so parsing must be done by hand.
L9M L9M

The samples that come with this boo$ provide source code for full parsing and decoding of 1@7 arguments.

,er&er),ide ,upport for (TD 1rguments The client side is not all there is to handling command line arguments for !TA applications. The 1@7' including arguments' is used to produce the path to the .config file' so if you want to serve up a .config file' you need some server/side code to deal with re)uests for .config files formed this way:
htt#:..,oo.,oo.exe?foo=bar@quux.con,i*

These re)uests must be translated into re)uests li$e the following' with the arguments stripped away:
htt#:..,oo.,oo.exe.con,i*

The 058.!"T code needed to do this is beyond the scope of this boo$' but the included samples demonstrate one simple handler that does the <ob.
L10M

L10M

#or more information: &7aunching !o/Touch Aeployment 0pplications with +ommand 7ine 0rguments'& +hris 5ells'

5A! 9nline' Eune >'

>003

De9ugging (TD 1pplications


0s is certainly obvious by now' debugging and wor$ing around security/related issues are the hardest parts of deploying an !TA application. *f you launch an !TA application via an 1@7' you may have noticed that there are no processes running that have that name. *nstead' each application launched via an 1@7 is hosted by iee(ec.e(e' which sets up the appropriate environment before loading the application. To debug an !TA application in the appropriate environment' you must debug against an instance of iee(ec.e(e started with the appropriate arguments to launch the application. 1nfortunately' the usage of iee(ec.e(e is undocumented' but the reverse/engineered usage for .!"T 1.1 is shown here:
Isage3 iee*ec.e*e <url= url'ssem!ly to launch- e.g. http3//localhost/foo.e*e

7aunching iee(ec.e(e in this way will cause it to pull down the application' if it%s not already cached' and launch it according to the permissions re)uired based on the code group:
C:\> ieexec.exe htt#:..5@K.?.?.5.!ahoo.!ahoo.exe

The benefit of being able to launch iee(ec.e(e directly li$e this is that it can be used as the launching application for debugging in -5.!"T' as shown in a sample pro<ect%s settings in #igure 1F.F. Figure 1'.'. De9ugging an (TD 1pplication 7sing iee3ec.e3e

!otice that Aebug ode has been set to 8rogram 3it defaults to 8ro<ect4 and that 5tart 0pplication has been set to the full path of iee(ec.e(e so that it will be used to host your application. The +ommand 7ine 0rguments field has been set to the 1@7 used to launch the application. To test default *ntranet or *nternet permissions when launching !TA applications hosted on the local machine' you use the &localhost& and &1>I.0.0.1& sites' respectively. With these settings' starting an application under debugging will provide you a normal interactive debugging session' but the permissions will be reduced to match the evidence awarded as if the application were launched with an 1@7' <ust as it will be deployed. This is really the only way to catch permissions problems when you target a partially trusted environment' so * encourage you to debug in this manner before shipping your !TA application.
L11M L11M

*" loo$s for a &.& in the site to determine the =one that an 1@7 indicates. #or e(ample' &itweb& is in the *ntranet =one because there%s no &.&'

whereas &google.com& is in the *nternet =one because it has a &.&.

1nder .!"T 1.0' the iee(ec.e(e usage was considerably different. 6owever' you can achieve the same results when e(ecuting iee(ec.e(e under .!"T 1.0 by tac$ing a 0 3=ero4 onto the end of the command line:
C:\> ieexec.exe htt#:..5@K.?.?.5.!ahoo.!ahoo.exe
A

0lthough iee(ec.e(e is useful for debugging' its usage is undocumented' so future versions of the .!"T #ramewor$ may well launch !TA applications differently 3as demonstrated by the different usages between .!"T 1.0 and .!"T 1.14. 6opefully' future versions of .!"T will provide a much more seamless debugging e(perience so that we needn%t concern ourselves with the iee(ec.e(e usage.

'ncreasing $ermissions
0lthough it%s certainly possible to build full/featured applications that run within the confines of a reduced permission set' you can easily eliminate these restrictions by allowing well/$nown assemblies increased or even unrestricted permissions. This is especially useful in a corporate intranet in which client machines are configured by the same staff that deploys the !TA applications. There are a number of ways to increase permissions for an assembly. #or e(ample' if you%re unhappy with the changes that were made in .!"T 1.0 581' you can raise or lower permissions for any =one as a whole using the 0d<ust .!"T 5ecurity Wi=ard 3available via the icrosoft .!"T #ramewor$ Wi=ards item on the 0dministration Tools menu4' as shown in #igure 1F.S. Figure 1'... 1djusting .(%T ,ecurit!

*f you%d li$e to be a little less sweeping in your security changes 3a practice * heartily recommend4' you can use the *nternet +ontrol 8anel to configure your system to trust all assemblies from a specific site' as shown in #igure 1F.I.

Figure 1'.0. 7sing the +nternet Control Panel to 1dd Trusted ,ites

0ny sites listed as trusted in the *nternet +ontrol 8anel settings are awarded *nternet permissions. *f this is still too broad' you can be even more restrictive by setting permissions on an assembly/by/assembly basis. *f you%d li$e to ad<ust permission for a specific assembly' you can set up a custom code group using the icrosoft .!"T #ramewor$ +onfiguration tool' or you can use the Trust an 0ssembly Wi=ard. This tool creates a code group named Wi=ardQ!' where the ! varies according to the number of times you run the wi=ard. When you run this tool' you enter the 1@7 to the assembly you%d li$e to trust:
htt#:..tr/ste machine.hrHA@.hrHA@.exe

Based on that 1@7' you%ll be as$ed whether you%d li$e to trust only this assembly' all assemblies from the same publisher' or all of the assemblies having the same public $ey. To simplify security configuration' it%s a good idea to sign all your assemblies with the same publicCprivate $ey pair. #or e(ample' * signed both wahoo.e(e and wahoo+ontrol.dll and can ad<ust both of their permissions at once by choosing to trust assemblies having the same $ey' as shown in #igure 1F.8.

Figure 1'.2. Trusting 1ll 1ssem9lies Ha&ing the ,ame Pu9lic <e!

6ere *%ve set all assemblies having the same public $ey' regardless of version' to be trusted. This is a good policy because it allows an entire family of trusted applications to be trusted with a single code group. 0fter you%ve specified which assemblies to trust in the Trust an 0ssembly Wi=ard' you%ll be as$ed how much trust you%d li$e them to have on a sliding scale. *t%s hard to tell from the user interface shown in #igure 1F.9' but the tic$ mar$s' from bottom to top' assign the permission sets !othing' *nternet' 7ocal*ntranet' and #ullTrust. Figure 1'.4. How 8uch an 1ssem9l! +s Trusted

With the new code group in place' the ne(t time you surf to the 1@7 .!"T will match the membership condition to the new code group and will give the assembly the ad<usted permissions. 0warding permissions to your own assemblies is a matter of creating a code group with a membership condition that matches your assemblies.which site it came from' which 1@7 it came from' which $ey it%s signed with' and so on.and matching it to a set of permissions. #urthermore' you can build your own custom permission set or use one of the default permission sets. Aeciding whether to build your own custom permission set is a matter of being familiar with the built/in permission sets' although creating a new one that overlaps with a built/in one won%t hurt anyone. 6owever' if you need access to any assemblies not mar$ed with 08T+0' then you%ll need to create a code group that awards your assembly the #ullTrust permission set. !one of the other default named permission sets' even the "verything permission set' can award permissions to access assemblies not mar$ed with 08T+0.

+ncreasing Permissions Programmaticall!

0s facile as you may become with the .!"T permission policy administration tools' you never want to wander to each of your clients% machines to create the necessary code groups and permissions sets that they need to run your code' especially if that code is to be deployed across the *nternet. 7uc$ily' .!"T provides classes to create code groups and permission sets. #or e(ample' the following code is used to create a custom code group to award all *nternet permission to assemblies signed with a $nown $ey:
/sin* System.Sec/rity; /sin* System.Sec/rity.Germissions; /sin* System.Sec/rity.Golicy; .. Eenerate !ith Fsec/til :c :s !ahoo.exeF &yteIJ #/&licLey 1 " ?, NX, ... -; .. Fin the machine #olicy le$el GolicyLe$el machineGolicyLe$el 1 n/ll; System.Collections.6(n/merator #h 1 Sec/rityMana*er.GolicyHierarchy(); !hile( #h.Mo$eBext() ) " GolicyLe$el #l 1 (GolicyLe$el)#h.C/rrent; i,( #l.La&el 11 "Machine" ) " machineGolicyLe$el 1 #l; &rea4; i,( machineGolicyLe$el 11 n/ll ) ret/rn; .. Create a ne! co e *ro/# *i$in* Wahoo3 6nternet #ermissions GermissionSet #ermSet5 1 ne! Bame GermissionSet("6nternet"); Stron*BameG/&licLey+lo& 4ey 1 ne! Stron*BameG/&licLey+lo&(#/&licLey); 6Mem&ershi#Con ition mem&ershi#5 1 ne! Stron*BameMem&ershi#Con ition(4ey, n/ll, n/ll); .. Create the co e *ro/# GolicyStatement #olicy5 1 ne! GolicyStatement(#ermSet5); Co eEro/# co eEro/#5 1 ne! <nionCo eEro/#(mem&ershi#5, #olicy5); co eEro/#5.9escri#tion 1 "6nternet #ermissions ,or Sells +rothers Wahoo3"; co eEro/#5.Bame 1 "Sells +rothers Wahoo3"; .. ) the co e *ro/# machineGolicyLe$el.2ootCo eEro/#.) Chil (co eEro/#5); .. Create a ne! co e *ro/# *i$in* all o, .. sells&rothers.com (xec/te #ermission GermissionSet #ermSet@ 1 ne! Bame GermissionSet("(xec/tion"); 6Mem&ershi#Con ition mem&ershi#@ 1 ne! SiteMem&ershi#Con ition("!!!.sells&rothers.com"); .. Create the co e *ro/# GolicyStatement #olicy@ 1 ne! GolicyStatement(#ermSet@); Co eEro/# co eEro/#@ 1 ne! <nionCo eEro/#(mem&ershi#@, #olicy@); co eEro/#@.9escri#tion 1 "Minimal exec/te #ermissions ,or sells&rothers.com";

co eEro/#@.Bame 1 "sells&rothers.com minimal exec/te"; .. ) the co e *ro/# machineGolicyLe$el.2ootCo eEro/#.) .. Sa$e chan*es Sec/rityMana*er.Sa$eGolicy(); Chil (co eEro/#@);

This code actually adds two code groups: one to award *nternet permissions to all assemblies signed with a $nown strong name' and a second one to wor$ around an &issue& that emerged in .!"T 1.0 581 whereby a site as a whole must have at least "(ecute permission for any other permissions awarded a strong name to ta$e effect. We start by using 5ecurity anager to find the top of the achine runtime policy hierarchy' where we%ll add the new code groups. We then grab the *nternet !amed8ermission5et and <oin it with 5trong!ame embership+ondition to produce the new code group. We then name the new code group something that%ll ma$e sense in the administration tools and add it to the root code group along with all the e(isting code groups. 3*f we had wanted to award our assemblies full trust' we%d have passed the string &#ullTrust& instead of &*nternet& when creating this !amed8ermission5et ob<ect.4 0fter creating the first permission set' we do the same thing again with the "(ecution !amed8ermission5et and the 5ite embership+ondition' naming them and adding them as well. To commit the changes to the achine runtime security policy' we as$ 5ecurity anager to save' and that%s it. This code is all that%s necessary to award permissions from e(isting permission sets to your assemblies. *f you want to create a custom permission set' the code is similar e(cept that you create an instance of an empty !amed8ermission5et ob<ect and add permission ob<ects that derive from +ode0ccess8ermission' such as #ile*98ermission and Airectory5ervices8ermission. Jou then add new permission sets to the machine policy via the 0dd!amed8ermission5et method:
.. Create a name , em#ty #ermission set Bame GermissionSet #ermSet 1 ne! Bame GermissionSet("My Germission Set", GermissionState.Bone); .. ) a #ermission 6Germission #erm 1 ne! 9irectorySer$icesGermission(); #ermSet.) Germission(#erm); machineGolicyLe$el.) Bame GermissionSet(#ermSet);

!otice the use of 8ermission5tate.!one passed to the !amed8ermission5et constructor. Without that' you get a permission set <ust li$e #ullTrust' with all permissions. *nstead' you want an empty permission set that has only those permissions that you e(plicitly add.

Deplo!ing Permissions
0fter you%ve got managed code to award permissions' it needs to run on the machine with #ullTrust. 9therwise' it won%t have permission to modify the permission policy 3it must

also be running as a Win3> 0dministrator4. 8lacing an "T" on a Web server and as$ing users to clic$ on a lin$ won%t do' because then the code will be in a partially trusted environment and we%re bac$ where we started. The easiest way to pac$age managed code for fully trusted e(ecution on the client machine is to use a icrosoft *nstaller file. 5* files are e(ecuted by a runtime engine that downloads the code to the machine before running it' thereby giving us the permissions we need to award other permissions. 0lso' 5* files have built/in support in the intranet world for deployment tools such as 5ystems anagement 5erver and 0ctive Airectory. *n a less sophisticated deployment environment' such as smaller businesses or the *nternet' you can provide a lin$ that' after a prompt' e(ecutes the 5* file on the user%s machine. "ven if all your users have to run the 5* themselves' that still re)uires only one installation to enable the permissions needed for' say' every !TA application deployed from the *T group%s internal Web site. There are many ways to build 5* files' but the most readily available one comes with advanced versions of -5.!"T. The tric$ is to convince a setup pro<ect to e(ecute your code during installation. 0ssuming you%ve got a -5.!"T solution with a setup pro<ect and a class library pro<ect' you have only two ma<or tas$s left to do this convincing. The first tas$ is to add a class to your class library pro<ect that derives from 5ystem.+onfiguration.*nstall.*nstaller and is tagged with the @un*nstaller3true4 attribute. 0n instance of any such class will be created by the 5* engine during setup' so that%s where you put your custom code. The easiest way to get such a class is to right/clic$ on your class library pro<ect in 5olution "(plorer and choose 0dd !ew *tem N +ode N *nstaller +lass. *t will create a place for your permission award code in the constructor:
%"un;nstaller+true.(

#/&lic class 6nstaller5 :


System.Configuration.;nstall.;nstaller

#/&lic 6nstaller5() " ... -

"

// TJ0J3 'dd your permission award code here

Jour second tas$ is to add this assembly to the list of custom actions that your setup will perform during installation. To do that' right/clic$ on your setup pro<ect in 5olution "(plorer and choose -iew N +ustom 0ctions. This will show you a list of the custom actions at each phase of the setup' as shown in #igure 1F.10. Figure 1'.16. ,etup Project Custom 1ctions

To add a custom action to the install phase' right/clic$ on the *nstall custom action list and choose 0dd +ustom 0ction. This will show the list of folders to place your custom action code into' as shown in #igure 1F.11. Figure 1'.11. Choosing a Folder for a Custom 1ction

Aouble/clic$ 0pplication #older and choose 0dd 9utput to choose the output from one of the other pro<ects in the solution. a$e sure the class library pro<ect with your installer class is selected at the top' and choose 8rimary 9utput' as shown in #igure 1F.1>. Figure 1'.12. Choosing the Primar! *utput of Hour Class $i9rar! Project to 1ct as a Custom 1ction

These settings will cause the installer classes in your class library assembly to be created during the *nstall phase of your 5* setup. !ow' when you build and e(ecute the 5* file produced by the setup pro<ect' your code will e(ecute at #ullTrust and can award permissions for your assemblies.

Authenticode
9ne more detail that you may find interesting has nothing to do with code' but with the .!"T security settings for !TA applications and Win#orms controls downloaded over the Web. *n .!"T v1.0' assemblies from both the *ntranet and the *nternet =ones were allowed to run' but the *nternet permissions were considerably reduced from the *ntranet permissions. 0s of version 1.0 581' icrosoft decided that .!"T wasn%t )uite coo$ed enough to support the rough/and/tumble of the *nternet' so it changed the settings to disable code from the *nternet altogether 3although you could always turn it bac$ on4. !ow' in version 1.1' icrosoft is confident that it%s got a secure platform' and it has turned e(ecution of code from the *nternet bac$ on by default' and the *nternet permissions themselves have not been reduced. But that%s not all. .!"T v1.1 also brings the 0uthenticode model bac$ from +9 . The +9 0uthenticode model was &punitive.& *f users 9D%d a hun$ of code to run' it could do absolutely anything it wanted' sub<ect to the permissions of the users 3most of whom' let%s face it' run as 0dministrator4. *f the code did something bad' a team of e(perts could trac$ it down' find the certificate' and bring the bad people who wrote the code to

hard <ustice 3my understanding is that the 0/Team was brought out of retirement at least once to help with this effort4. The .!"T +05 model' on the other hand' is &preventive'& in that the user is never as$ed' but the code has only a limited set of permissions if it%s from a source other than the local hard drive. *n practice' it turns out that both models have their uses. +05 is great' of course' for $eeping bad things from happening. 0uthenticode' on the other hand' is good at letting users $now that code from outside their machine is about to e(ecute and as$ing them if that%s 9D. Whether or not to as$ is determined by the same *nternet security settings that determine whether or not to as$ for a +9 control. #igure 1F.13 shows the default settings for code from the *nternet =one in .!"T 1.1. Figure 1'.1 . (%T 1.1 (TD ,ecurit! ,ettings

Jou%ll notice that when code from the *nternet =one is e(ecuted' by default there are no user prompts. The same is true of the *ntranet =one. This is more permission than the default settings for 0ctiveTC+9 controls' which default to 8rompt 3for signed controls4 and Aisable 3for unsigned controls4. 9f course' for +9 controls' 0uthenticode is all the security there is' whereas in .!"T' there%s all of +05 to continue to protect the user. 0uthenticode doesn%t affect which permissions an !TA application has. *nstead' 0uthenticode is a gate that affects e(ecution permissions based on user settings before

anything else happens' as well as providing an optional prompt. 6owever' the permission set is still awarded based on other evidence. 0uthenticode behavior is as follows:

permissions awarded silently permissions awarded or denied based on user prompt results .NET Code 0isa!led3 all permissions denied
.NET Code Ena!led3 .NET Code Prompt3

5o' under .!"T 1.1' if an !TA application is allowed to run at all 3either it%s silently enabled or the user says yes to the prompt4' it gets whatever permissions it would get in .!"T 1.0' where everything was configured li$e the &.!"T +ode "nabled& setting.

Where Are We?


Because .!"T code can be deployed over the Web' you can host Win#orms controls in *nternet "(plorer as well as launch !TA applications using 1@7s. !TA applications combine the best of the 6T 7 deployment model and the Windows #orms 1* development model' without the baggage of 6T 7 1* limitations. "specially for the *ntranet =one' where you get to dictate the client/side configuration' there%s no need to restrict yourself to the limitations of the icrosoft 6T 7 runtime 3that is' *nternet "(plorer4. *nstead' you can move up to the icrosoft .!"T runtime' which has been designed to build and deploy your applications in a whole new way.

1ppendi3 1. 8o&ing from 8FC


+hances are that if you%re a +;; programmer with e(perience in Windows and an interest in client/side applications' you%ve been an #+ programmer. 0nd whether or not you found that e(perience wholly pleasurable' you probably e(pect )uite a few things from your client/tier application framewor$. This appendi( briefly e(plains which of your e(pectations will be fulfilled 3and then some4' and which are going to cause you &issues.&

A Few Words A0out %FC


*n 199>' icrosoft released #+ 1.0 as part of the 8rogrammer%s Wor$bench. #+ was a set of about S0 classes targeted mainly at wrapping the windowing and drawing parts of the 1S/bit Windows 08*. *ts goal was to wrap the implicit and inconsistent ob<ect models inherent in the operating system with an e(plicit' consistent +;; ob<ect model' and it did as good a <ob as could be e(pected given the state of icrosoft%s +;; compiler at the time.
L1M L1M

0t the time'

icrosoft%s +;; compiler was far behind the pac$ in the implementation of things such as templates' e(ceptions' and runtime type #+' and in the Windows +;; programmer community' that can still be felt

identification 3@TT*4. This tardiness caused ripples in the design of to this day.

*n >00>' icrosoft released #+ I.0 as part of -isual 5tudio .!"T >00>. #+ had grown to more than >00 classes' and' along the way' its goal had e(panded: to provide a complete +;; ob<ect model replacement of the Win3> 08*. 0s of version I.0' #+ grew to be the most feature/rich way to build commercial/)uality client/tier applications in Windows. 6ere%s a list of the ma<or features that #+ programmers have grown to rely on:
L>M L>M

*t also grew on the server side' but

#+ has always been firmly grounded on the client.

5upport for dialog/based' 5A*' multi/5A*' and A* applications Aocument/-iew 0rchitecture 8rinting' print setup' and print preview #loating toolbars' status bars' and dialog bars +onte(t/sensitive help 9b<ect 7in$ing and "mbedding 3both client and ob<ect4 97" 0utomation +9 control hosting 0ctive Aocument servers #ull integration into -5.!"T' including four of the most comprehensive wi=ards that the *A" provides Aynamic Aata "(change and -alidation +ommand routing +ommand 1* updating Windows logo compliance 5hell integration 3icons' drag and drop' AA"' and command line parsing4

Wrappers for a large percentage of the Win3> 08*' including windowing' drawing' databases' soc$ets' @egistry access' the file system' threading' and more 0uto/upgrade from 1S bits to 3> bits
L3M L3M

This isn%t important now' but man oh man' it was a big deal when we were all busy porting our 1S/bit applications to 3> bits.

Tons of third/party and community support

*f you%ve read the rest of this boo$' you%ll notice that #+ provides a lot of features that * didn%t tal$ about. *f you%re starting with this appendi( as an #+ programmer wondering what Win#orms does and doesn%t offer' you may be disappointed to hear that * haven%t covered all the features in this list 3although * certainly have covered a number of them4. "ither way' the hard' cold truth is that #+ provides more features than Win#orms does for building stand/alone' document/based applications. #or e(ample' if you want to build a te(t editor' you can do that in #+ by running a wi=ard' choosing the right options' and writing only a few lines of code. By running the wi=ard' you get an application to start with that includes a status bar' a toolbar 3floating4' all the #ile' "dit' and 6elp menu items implemented 3including a most recently used file list' printing' and conte(t/sensitive help4' all in a fully logo/compliant 5A*' multi/5A*' or A* application' based on your whim that day. 0s a document/based application framewor$' #+ has no peer. 6owever' in recent years the world seems to have moved away from document/based applications. @elatively few fol$s seem interested in building te(t editors or word processors or spreadsheets. *nstead' the bul$ of the applications are either completely 6T 7/based or n/client applications tal$ing to networ$' database' or Web services bac$ ends. *t%s for this use that .!"T as a whole and Win#orms specifically have been tailored. That%s not to say that Win#orms can%t be used to build darn nice document/based applications. *n fact' because Win#orms is only a small piece of the more than >'000 public classes provided in .!"T' it%s li$ely that if what you%re loo$ing for isn%t in Win#orms' it%s found somewhere else in .!"T. #or e(ample' Win#orms itself 3the 5ystem.Windows.#orms namespace4 doesn%t provide any custom drawing support at all. *nstead' GA*; 3in the 5ystem.Arawing namespace4 supplies that functionality. 0nd this is the chief difference between #+ and Win#orms. #+ was meant as a replacement for the underlying Win3> 08*' but that didn%t stop the Win3> 08* from growing. *n fact' as much as #+ has grown over the years' the functionality of the underlying 95 has increased at least tenfold. Win#orms' on the other hand' is meant to be a replacement only for the windowing part of Win3>. *t%s the rest of the .!"T #ramewor$ classes that are meant to replace the rest of Win3>. 9f course' .!"T will never replace the entire Win3> 08*' but because most new functionality is slated to be added to .!"T in the future' it%s clear that betting on the future of .!"T is a wise wager.

%FC Versus WinForms

5ome fol$s do need to build document/based applications' and even though Win#orms isn%t designed to support that as well as #+ is' it%s not very hard to build complete document/ based applications if you%re armed with the $nowledge of what Win#orms does and doesn%t provide as compared with #+. Table 0.1 shows a feature/based summary focused on building document/based applications. Ta9le 1.1. 8FC =ersus WinFormsK.(%T
Feature $FC WinForms/.NET

0pplication Wi=ards *A" *ntegration Aialog' 5A*' A* 0pplications

#our Jes Jes Jes Jes 3dialogs only4 Jes 3simple4 !o Jes Jes Jes Jes Jes Jes Jes Jes Jes 3simple4 !o !o 1SC3> !o Jes 3huge4 Jes Jes

#our Jes Jes !o Jes Jes Jes Jes 3no floating4 Jes !o Jes Jes 3@emoting4 Jes Jes 3AAT not needed4 Jes Jes Jes Jes 3>CSH Jes Jes 3growing4 !o !o

ulti/5A* 0pplications 1* 7ayout Aoc$ing and 5plitting 0nchoring Toolbars and the li$e 8rinting' 8review' 5etup 97"C0ctive Aocuments +9 +ontrol 6osting

0utomation #1 6elp AATCAAWin3> Wrappers Aata Binding +ross/7anguage +ross/8latform +ross/Bitness Web Aeployment Third/8arty 5upport Aocument/-iew Aocument anagement

Ta9le 1.1. 8FC =ersus WinFormsK.(%T


Feature $FC WinForms/.NET

5hell *ntegration +ommand 1nification 1* 1pdating +ommand @outing 5ource +ode anaged "nvironment

Jes Jes Jes Jes Jes !o

Jes 3via installer4 !o !o !o !o Jes

The Differences
"ven the features shared by #+ and Win#ormsC.!"T are often implemented differently in the two worlds' so the following is a short discussion of each of the features. 1pplication Wi-ards -5.!"T >003 provides #+ wi=ards to build applications' A77s' +9 controls' and *nternet 5erver 08* 3*508*4 e(tensions. -5.!"T >003 provides Win#orms wi=ards to build applications and controls for each of the four languages that are supported 3+,' -B.!"T' +;;' and E,4. -5.!"T also gives you wi=ards for producing class library and 058.!"T server/side applications and libraries. +hapter 1: 6ello' Windows #orms and +hapter 8: +ontrols discuss the Win#orms wi=ards. +D% +ntegration -5.!"T provides direct *A" integration for developing #+ and Win#orms applications and controls. The Win#orms integration is more e(tensive' mainly because of the strong 1* layout environment and data binding' which are discussed as appropriate throughout the boo$. Dialog, ,D+, and 8D+ 1pplications Both #+ and Win#orms provide complete support for dialog/based' 5ingle Aocument *nterface 35A*4' and ultiple Aocument *nterface 3 A*4 applications. 6owever' although #+ comes with a wi=ard that provides a great deal of functionality to help you get started when you%re building 5A* and A* applications' all the Win#orms wi=ards produce empty forms' which can serve as dialogs or #+/style view windows depending on how they%re used. This means that you must add all the standard 1* and features every time you need an 5A* or A* application in Win#orms. "(cept for the document management features' most of the body of this boo$ is about how to develop applications that include the $inds of

features you%d e(pect to find in an in +hapter >: #orms. 8ulti),D+ 1pplications

#+ application' with the specifics of

A* applications

ulti/5A* applications. applications that have a single instance but multiple top/level windows.are fully supported in #+. 0lthough Win#orms doesn%t come out of the bo( supporting multi/ A* applications' +hapter 11: 0pplications and 5ettings fully e(plains how to build them. 7+ $a!out Arag/and/drop design and layout of user interface are supported in #+ only for dialogs. !ormal views must be laid out in code. Win#orms' on the other hand' treats all windows in a unified manner' so the same drag/and/drop Aesigner wor$s for any $ind of window. What $ind of window it is.modal or modeless' dialog or view.depends on how it%s used' not on how it%s designed. 5omething else that%s a bit different in Win#orms is that the 1* design environment reads and writes code instead of $eeping control type and position information in a separate resource file. That code is relegated to a single method' but it is definitely mi(ed in with the rest of the code of the window' and that is very different from the way #+ dialogs are built. "ach scheme has its pros and cons' but #+ programmers will notice the difference right away 3and then may let it ma$e them unhappy before letting it grow on them4. The Win#orms Aesigner is discussed as appropriate through the boo$. Doc5ing and ,plitting Win#orms lets you doc$ controls to the edges of a window as well as designate a control to ta$e up the remaining space of the window. Jou can attach doc$ed controls to splitters so that as a splitter is moved' it resi=es the appropriate controls. 0ll this is available in the design environment so that you can see what the doc$ing and splitting will loo$ li$e at design time. #+' on the other hand' does doc$ing and splitting in code only. The dialog editor doesn%t support this feature. 0lso' splitting in #+ re)uires separate window classes' whereas in Win#orms all the doc$ed and split controls are easily accessible from within the single container. Aoc$ing and splitting are discussed in +hapter >: #orms. 1nchoring When a window is resi=ed in #+' any controls that need to change si=e with the si=e of the containing window must be resi=ed by hand in the W Q5*K" message handler. *n contrast' Win#orms anchoring 3combined with doc$ing4 allows a control to be resi=ed automatically as the container resi=es. 0nchoring is discussed in +hapter >: #orms.

Tool9ars and the $i5e #+ e(cels in providing not only industrial/strength window adornments 3such as toolbars' status bars' and dialog bars4 for building full/featured applications' but also great *A" integration for editing them and a wi=ard to place initial versions for you. Win#orms provides the same industrial/strength controls 3as discussed in +hapter 8: +ontrols4' but its support' especially for editing toolbars' is barely ade)uate' and it has no wi=ard to get you started. 0lso' Win#orms has no built/in support for &floating& windows' such as floating toolbars. Printing, Pre&iew, and ,etup Both #+ and Win#orms provide similar complete support for printing' print preview' print settings' and page settings. *$%K1cti&e Documents 9b<ect 7in$ing and "mbedding is a technology for e(posing and consuming data and the 1* from one application into another. #+ provides complete support for this technology. Win#orms supports only enough 97" to host +9 controls. 0ctive Aocuments' another +9 /based technology' is rather li$e a cross between 9b<ect 7in$ing and "mbedding and controls' but it has never really gained any traction. #+ supports it' but Win#orms does not. C*8 Control Hosting Both #+ and Win#orms provide complete support for hosting +9 controls' and both do it using wrappers that provide an 08* appropriate to their respective environments. 1nfortunately' neither gives you seamless integration. 5ee +hapter >: #orms for a discussion of how Win#orms hosts +9 controls. 1utomation Both #+ and Win#orms provide complete support for both consuming and producing +9 ob<ects for use in application automation. *n addition' .!"T gives you another way to access ob<ects between processes. +alled .!"T @emoting' this technology can be used as a means of application automation. +hapter 11: 0pplications and 5ettings has a short e(ample of .!"T @emoting for use in communicating between instances of the same application.
LHM LHM

*ngo @ammer%s

Advanced .NET Remoting 308ress' >00>4 provides complete coverage of .!"T @emoting.

F1 Help

Both #+ and Win#orms support integrating help into an application' although only provides a wi=ard to get you started. The Win#orms support for integrated help is discussed in +hapter 3: Aialogs. DD>KDD=

#+

Because #+ was developed before icrosoft%s +;; compiler supported e(ceptions' #+ has a two/stage construction model for windows. This means that the +;; ob<ect will come into e(istence before the underlying 95 ob<ect or any of the contained child controls. Because of this' #+ dialogs need to move data bac$ and forth between member variables to allow clients to provide initial child control data before the child control is created' and to ma$e the final child control data available after the child controls have been destroyed. The mechanism to ma$e this happen is called Aynamic Aata "(change 3AAT4. 5imilarly' validating the data as it moves is called Aynamic Aata -alidation 3AA-4. Whereas AAT is necessary because of the design of the library' AA- is always necessary. The Win#orms data e(change model is different. "ach Windows ob<ect is created as an ob<ect and is shown when necessary without forcing the developer to be concerned about whether or not the underlying 95 handle has been created. This means that child control properties can always be accessed directly at any time during their lifetime. *n other words' the AAT is handled transparently by the controls themselves' eliminating the need to thin$ about in it Win#orms. Aata validation is still necessary' of course' and is fully supported in Win#orms as discussed in +hapter 3: Aialogs. Win 2 Wrappers Because both #+ and .!"T are meant as replacements for the underlying Win3> 08*' it ma$es sense that both of them have a large number of wrapper classes to hide that 08*. 0nd although .!"T has #+ beat by about an order of magnitude in terms of 08*s wrapped' #+ has the edge in that it%s much easier to access unwrapped Win3> 08*s in unmanaged +;; than it is in managed code. Data "inding #+ has only to$en support for data binding. The Win#orms data binding support ta$es its cue from -isual Basic and provides e(tensive data binding support and data provider integration with the *A". +hapter 1>: Aata 5ets and Aesigner 5upport and +hapter 13: Aata Binding and Aata Grids provide an introduction to this enormous topic. Cross)$anguage #+ is a class library for +;; programmers only. Win#orms 3and the rest of .!"T4 is available to icrosoft/provided languages such as anaged +;;' Escript.!"T' -B.!"T'

and E,' as well as do=ens of third/party languages 3although only and -isual Basic .!"T have Win#orms Aesigner support4. Cross)Platform

anaged +;;' E,' +,'

#+ is supported across all versions of Windows' and is supported across some 1!*T variants by third parties. Win#orms is supported under the des$top versions of Windows starting with Windows 98' and the latest version Windows +" 3although -5.!"T >003 is re)uired for Windows +" support4. Cross)"itness #+ was originally built to support 1S/bit Windows and' because of the degree of isolation from the underlying 95' made porting to 3> bits largely a recompile in many cases. +urrently' Win#orms is supported only under 3> bits but will certainly provide a high 3and li$ely seamless4 degree of portability when you move your code to SH bits. We9 Deplo!ment #+ applications need to be installed or copied to a machine before they can be run 3with the e(ception of e(ecuting an application from a networ$ share4. Win#orms applications support this mode of deployment' of course' but they also support no/touch deployment' which allows a Win#orms application to be launched via an 1@7' downloaded automatically' and launched without an e(plicit copy or install. This model' covered in depth in +hapter 1F: Web Aeployment' combines the richness of Windows applications with the deployment of Web applications. Third)Part! ,upport #+ programmers have years% worth of boo$s' articles' sample code' #0Ys' archives' third/party tools' and general community $nowledge at their disposal. 6owever' even though Win#orms was first released in #ebruary >00>' it has already gained a lot of those assets' and the rate of ac)uisition is accelerating. Document)=iew #+ >.0 introduced Aocument/-iew' a simplified version of odel/-iew/+ontroller that separates a document%s data from the view of that data. This model so permeates #+ that it wasn%t until relatively recent versions of the *A" that the wi=ards supported generating non/Aocument/-iew code. The central idea of Aocument/-iew is a good one' but the #+ specifics of document management' such as seriali=ation and dirty bit management' made it difficult for non/document/based applications to fit the model. Win#orms went the other way. *nstead of imposing an application framewor$ model on all applications' Win#orms provides only a windowing framewor$. 6owever' the central idea

of separating the data from the view is still a good one and needs no real support beyond what%s provided by the .!"T runtime itself. Document 8anagement Beyond the idea of separating data from view' the ma<or productivity enhancement of Aocument/-iew was the document management piece' including dirty bit management' file dialog management' most recently used file lists' change notification' and so on. Those enhancements are sorely missed in .!"T when it comes to building document/based applications. 7uc$ily' that support is not very hard to build' and an e(ample is provided with this boo$. The core of document management is the ability to seriali=e an ob<ect graph that represents an application%s data. .!"T provides complete support for that $ind of seriali=ation' and a brief introduction can be found in 0ppendi( +: 5eriali=ation Basics. ,hell +ntegration 0nother part of the document/based piece provided by #+ is the automatic registration of file e(tensions with the shell and the handling of file open re)uest operations from the shell. Win#orms provides direct support for neither of these operations' but both are discussed in +hapter 11: 0pplications and 5ettings.
LFM LFM

Jou have to loo$ closely to see the coverage of shell integration in +hapter 11: 0pplications and 5ettings' but it%s there. +hec$ out the first

couple of @egistry settings e(amples' where you%ll find the core piece that you%ll need to associate a document e(tension in the shell with your Win#orms application.

Command 7nification #+ unifies interaction for multiple $inds of controls to commands that can be handled singly. #or e(ample' to the user' choosing #ile N 9pen from a menu is the same as clic$ing on the 9pen #ile toolbar button. These activities are unified at the class and *A" level' letting the developer easily handle all ways of invo$ing the same command in a single spot. Win#orms provides no such facility at the class or Aesigner level. 9nly manual coding can reduce the duplication 3although' to be fair' it%s only a couple of lines of code4. 7+ 7pdating 0nother benefit of command unification in #+ is the ability to enable or disable a command as needed without the e(plicit need to disable a menu item or a toolbar button separately. Win#orms re)uires that 1* elements be enabled or disabled e(plicitly. Command /outing #+ supports routing commands to any interested subscriber. .!"T supports this same idea with delegates' as described in 0ppendi( B: Aelegates and "vents.

,ource Code #+ provides full source code that can be read and stepped through in the debugger. The .!"T #ramewor$ source code is not provided and cannot be stepped through in the debugger. @eading the source code for the .!"T #ramewor$ re)uires a disassembler tool and a lot of patience. 8anaged %n&ironment #+ is an &nmanaged environment in the sense that memory and security must be managed by the developer e(plicitly. The .!"T runtime provides automatic handling of both memory and security' ma$ing .!"T a managed environment. 0 managed environment can sometimes cause a degradation in performance 3although it%s surprising how rare that is4' but it always results in a more robust application' especially given how hard it is to trac$ down and deal with memory and security problems. y e(perience is that even given a lac$ of some application framewor$ features' *%m much more productive in .!"T than * ever was in +;; or #+.

,trateg!
*f you%re moving from #+ as a programmer' this boo$ will help you understand the new Win#orms model' especially as focused by the discussion so far in this appendi(. The basics are similar' so a typical #+ programmer won%t have much difficulty pic$ing up Win#orms. 6owever' Win#orms is only a piece. * recommend spending some time with the +, language itself as well as the rest of the .!"T #ramewor$ to fill in what you%re going to need outside of Win#orms. *f you%re moving considerations:

#+ code to .!"T' you need some careful planning. 6ere are some

*f you can afford to start over from scratch' that will yield the most maintainable code base' but it will ta$e the longest. *f the bul$ of your #+ code is in +9 controls 3or can be moved to +9 controls4' then you can use Win#orms as a host for those controls and write new code in .!"T. *f you need to bring the #+ application itself forward' you can flip the 1se anaged "(tensions bit on your #+ pro<ect and gain the ability to host Win#orms controls from your #+ I.1 code. This also lets you write new code in .!"T.
LSM LSM

#or a discussion of how to host Win#orms controls as +9 arch >003.

controls in an

#+ application' see &Windows #orms: .!"T

#ramewor$ 1.1 8rovides "(panded !amespace' 5ecurity' and 7anguage 5upport for Jour 8ro<ects'& +hris 5ells'

MSDN

Maga$ine'

*f the new code you%d li$e to integrate into your e(isting #+ code is not a control' you can use +9 to interoperate with the .!"T code while still $eeping your #+ code unmanaged.

Which options apply to you depend on your specific circumstances' but in general' * recommend a strategy that lets you write the bul$ of your new code in .!"T' even if it means building some of the features for Win#orms that you%re missing from #+.

/enghis
Before you decide to rebuild all the #+ features' you%ll want to chec$ the Genghis library to see whether that feature has already been rebuilt. Genghis is a set of Win#orms components and controls to fill the needs of document/based application development in Win#orms. 0s of this writing' it includes the following features:
LIM LIM

The Genghis Group' http:CCwww.genghisgroup.com

+ommand line parser +ompletion combo +ontrol hosting status bar +ursor changer +ustom chec$ state treeview +ustom T8 theming controls #ile 5earch "ngine #ileAocument class 3docCdirty bit management4 #ind@eplaceAialog #older!ameAialog 6andle+ollector for the world 6eader group bo( control *mage combo ore robust validation d la Web#orms ost recently used 3 @14 files support 5! essenger/style pop/up window ultiple top/level window support ultiple/instance detection 5creen 5aver class 5crollable 8ictureBo( control 5orting listview 3including the little triangle thingy4 5plash 5creen class +ommand line helper for no/touch deployment applications 3including server/side code4 Window seriali=er Wi=ard framewor$

#igure 0.1 shows a sample document/based application built using Genghis.

Figure 1.1. 1 ,ample Document)"ased :enghis 1pplication

Genghis is a shared source pro<ect' and the license allows you to use the code in your own applications free of charge. 0nd' if you%ve got anything to contribute' the list of e(isting features is e(ceeded only by the list of desired features.

1ppendi3 ". Delegates and %&ents


The following story is reprinted from my Web site as a more thorough' although less reverent' e(planation of delegates and events. The characters in this story are fictional' and any similarity to real persons or events is unintentional.

#elegates
9nce upon a time' in a strange land south of here' lived 8eter. 8eter was a diligent wor$er who would readily accept re)uests from his boss. 6owever' his boss was a mean' untrusting man who insisted on steady progress reports. Because 8eter did not want his boss standing in his office loo$ing over his shoulder' 8eter promised to notify his boss whenever his wor$ progressed. 8eter implemented this promise by periodically calling his boss bac$ via a typed reference li$e so:
class Wor4er "
pu!lic #oid 'd#ise+)oss !oss. 4this.!oss 1 !oss/ 5

#/&lic $oi 9oWor4() " Console.WriteLine("Wor4er: !or4 starte ");


if+ !oss 21 null . !oss.Wor6Started+./

Console.WriteLine("Wor4er: !or4 #ro*ressin*");


if+ !oss 21 null . !oss.Wor6Progressing+./

Console.WriteLine("Wor4er: !or4 com#lete "); i,( &oss 31 n/ll ) "


int grade 1 !oss.Wor6Completed+./

Console.WriteLine("Wor4er *ra e1 " ; *ra e);

)oss !oss/

class +oss " #/&lic $oi Wor4Starte () ".Q &oss oesnFt care. Q. #/&lic $oi Wor4Gro*ressin*() ".Q &oss oesnFt care. Q. #/&lic int Wor4Com#lete () " Console.WriteLine("6tFs a&o/t time3"); ret/rn @; .Q o/t o, 5? Q. class <ni$erse " static $oi Main() " Wor4er #eter 1 ne! Wor4er(); +oss &oss 1 ne! +oss(); #eter.) $ise(&oss); #eter.9oWor4(); Console.WriteLine("Main: !or4er com#lete Console.2ea Line(); !or4");

+nterfaces
!ow 8eter was a special person. !ot only was he able to put up with his mean/spirited boss' but he also had a deep connection with the universe around him. 5o much so that he felt that the universe was interested in his progress. 1nfortunately' there was no way for 8eter to advise the universe of his progress unless he added a special 0dvise method and special callbac$s <ust for the universe' in addition to $eeping his boss informed. What 8eter really wanted to do was to separate the list of potential notifications from the implementation of those notification methods. 0nd so he decided to split the methods into an interface:
interface ;Wor6erE#ents 4 #oid Wor6Started+./ #oid Wor6Progressing+./ int Wor6Completed+./ 5

class Wor4er " #/&lic $oi ) $ise(6Wor4er($ents e$ents) "this.e$ents 1 e$ents; #/&lic $oi 9oWor4() " Console.WriteLine("Wor4er: !or4 starte "); i,( e$ents 31 n/ll ) e$ents.Wor4Starte (); Console.WriteLine("Wor4er: !or4 #ro*ressin*"); i,(e$ents 31 n/ll ) e$ents.Wor4Gro*ressin*(); Console.WriteLine("Wor4er: !or4 com#lete "); i,(e$ents 31 n/ll ) " int *ra e 1 e$ents.Wor4Com#lete (); Console.WriteLine("Wor4er *ra e1 " ; *ra e); -

6Wor4er($ents e$ents;

class +oss : 6Wor4er($ents " #/&lic $oi Wor4Starte () ".Q &oss oesnFt care. Q. #/&lic $oi Wor4Gro*ressin*() ".Q &oss oesnFt care. Q. #/&lic int Wor4Com#lete () " Console.WriteLine("6tFs a&o/t time3"); ret/rn N; .Q o/t o, 5? Q. -

Delegates
1nfortunately' 8eter was so busy tal$ing his boss into implementing this interface that he didn%t get around to notifying the universe' but he planned to get to that as soon as possible. 0t least he%d abstracted the reference of his boss far away from him so that others who implemented the *Wor$er"vents interface could be notified of his wor$ progress.

5till' his boss complained bitterly. &8eter2& his boss fumed. &Why are you bothering to notify me when you start your wor$ or when your wor$ is progressing?2? * don%t care about those events. !ot only do you force me to implement those methods' but you%re wasting valuable wor$ time waiting for me to return from the event' which is further e(panded when * am far away2 +an%t you figure out a way to stop bothering me?& 0nd so' 8eter decided that even though interfaces were useful for many things' when it came to events' their granularity was not fine enough. 6e wished to be able to notify interested parties only of the events that matched their hearts% desires. Toward that end' 8eter decided to brea$ the methods out of the interface into separate delegate functions' each of which acted li$e a little tiny interface of one method each:
delegate #oid Wor6Started+./ delegate #oid Wor6Progressing+./ delegate int Wor6Completed+./

class Wor4er " #/&lic $oi 9oWor4() " Console.WriteLine("Wor4er: !or4 starte ");
if+ started 21 null . started+./

Console.WriteLine("Wor4er: !or4 #ro*ressin*");


if+ progressing 21 null . progressing+./

Console.WriteLine("Wor4er: !or4 com#lete ");


if+ completed 21 null . 4 int grade 1 completed+./ Console.WriteCine+ Wor6er grade1 5 7 grade./

pu!lic Wor6Started started/ pu!lic Wor6Progressing progressing/ pu!lic Wor6Completed completed/

class +oss " #/&lic int Wor4Com#lete () " Console.WriteLine("+etter..."); ret/rn H; .Q o/t o, 5? Q. class <ni$erse " static $oi Main() " Wor4er #eter 1 ne! Wor4er(); +oss &oss 1 ne! +oss();
// NJTE3 We:#e replaced the 'd#ise method with the // assignment operation peter.completed 1 new Wor6Completed+!oss.Wor6Completed./

#eter.9oWor4(); Console.WriteLine("Main: !or4er com#lete Console.2ea Line();

!or4");

,tatic ,u9scri9ers
Aelegates accomplished the goal of not bothering 8eter%s boss with events that he didn%t want' but still 8eter had not managed to get the universe on his list of subscribers. 5ince the universe is an all/compassing entity' it didn%t seem right to hoo$ delegates to instance members 3imagine how many resources multiple instances of the universe would need...4. *nstead' 8eter needed to hoo$ delegates to static members' which delegates support fully:
class <ni$erse "
static #oid Wor6erStartedWor6+. 4 Console.WriteCine+ Ini#erse notices wor6er starting wor6 ./ 5 static int Wor6erCompletedWor6+. 4 Console.WriteCine+ Ini#erse pleased with wor6er:s wor6 ./ return F/ 5

static $oi Main() " Wor4er #eter 1 ne! Wor4er(); +oss &oss 1 ne! +oss();
// NJTE3 the use of the assignment operator is QnotQ good // practice in the following @ lines of code. // Peep reading for the right way to add a delegate

#eter.com#lete

peter.started 1 new Wor6Started+Ini#erse.Wor6erStartedWor6./ peter.completed 1 new Wor6Completed+Ini#erse.Wor6erCompletedWor6./

1 ne! Wor4Com#lete (&oss.Wor4Com#lete );

#eter.9oWor4(); Console.WriteLine("Main: !or4er com#lete Console.2ea Line(); !or4");

!vents
1nfortunately' the universe' being very busy and unaccustomed to paying attention to individuals' had managed to replace 8eter%s boss%s delegate with its own. This was an unintended side effect of ma$ing the delegate fields public in 8eter%s Wor$er class. 7i$ewise' if 8eter%s boss got impatient' he could decide to fire 8eter%s delegates himself 3which is <ust the $ind of rude thing that 8eter%s boss was apt to do4:
.. GeterFs &oss ta4in* matters into his o!n han s i,( #eter.com#lete 31 n/ll ) #eter.com#lete ();

8eter wanted to ma$e sure that neither of these could happen. 6e reali=ed that he needed to add registration and unregistration functions for each delegate so that subscribers could add or remove themselves but couldn%t clear the entire list or fire his events. *nstead of implementing these functions himself' 8eter used the event $eyword to ma$e the +, compiler build these methods for him:

class Wor4er " ... #/&lic e#ent Wor4Starte starte ; #/&lic e#ent Wor4Gro*ressin* #ro*ressin*; #/&lic e#ent Wor4Com#lete com#lete ; -

8eter $new that the event $eyword erected a property around a delegate' allowing only clients to add or remove themselves 3using the ;R and /R operators' if the client was written in +,4' forcing his boss and the universe to play nicely:
static $oi Main() " Wor4er #eter 1 ne! Wor4er(); +oss &oss 1 ne! +oss(); #eter.com#lete ;1 ne! Wor4Com#lete (&oss.Wor4Com#lete ); #eter.starte ;1 ne! Wor4Starte (<ni$erse.Wor4erStarte Wor4); #eter.com#lete ;1 ne! Wor4Com#lete (<ni$erse.Wor4erCom#lete Wor4); #eter.9oWor4(); Console.WriteLine("Main: !or4er com#lete Console.2ea Line(); !or4");

Har&esting 1ll /esults


0t this point' 8eter breathed a sigh of relief. 6e had managed to satisfy the re)uirements of all his subscribers without having to be closely coupled with the specific implementations. 6owever' he noticed that although both his boss and the universe provided grades of his wor$' 8eter was receiving only one of the grades. *n the face of multiple subscribers' he really wanted to harvest all of their results. 5o' he reached into his delegate and pulled out the list of subscribers so that he could call each of them manually:
#/&lic $oi 9oWor4() " ... Console.WriteLine("Wor4er: !or4 com#lete "); i,( com#lete 31 n/ll ) "
foreach+ Wor6Completed wc in completed.Ket;n#ocationCist+. . 4 int grade 1 wc+./ 5

Console.WriteLine("Wor4er *ra e1 " ; *ra e);

1s!nchronous (otificationL Fire and Forget


*n the meantime' his boss and the universe had been distracted with other things' and this meant that the time it too$ them to grade 8eter%s wor$ had been greatly e(panded:
class +oss " #/&lic int Wor4Com#lete () "

System.Threading.Thread.Sleep+@AAA./

Console.WriteLine("+etter..."); ret/rn X; .Q o/t o, 5? Q.

class <ni$erse " static int Wor4erCom#lete Wor4() "


System.Threading.Thread.Sleep+SAAA./

Console.WriteLine("<ni$erse is #lease ret/rn K; ... -

!ith !or4erFs !or4");

1nfortunately' because 8eter was notifying each subscriber one at a time' waiting for each to grade him' these notifications now too$ up )uite a bit of his time when he should have been wor$ing. 5o' he decided to forget the grade and <ust fire the event asynchronously:
#/&lic $oi 9oWor4() " ... Console.WriteLine("Wor4er: !or4 com#lete "); i,( com#lete 31 n/ll ) "

foreach+ Wor6Completed wc in completed.Ket;n#ocationCist+. . 4 wc.)egin;n#o6e+null- null./ 5

1s!nchronous (otificationL Polling


This clever tric$ allowed 8eter to notify the subscribers while letting him get bac$ to wor$ immediately' letting the process thread pool invo$e the delegate. 9ver time' however' 8eter found that he missed the feedbac$ on his wor$. 6e $new that he did a good <ob and appreciated the praise of the universe as a whole 3if not his boss specifically4. 5o' he fired the event asynchronously but polled periodically' loo$ing for the grade to be available:
#/&lic $oi 9oWor4() " ... Console.WriteLine("Wor4er: !or4 com#lete "); i,( com#lete 31 n/ll ) "

foreach+ Wor6Completed wc in completed.Ket;n#ocationCist+. . 4 ;'sync"esult res 1 wc.)egin;n#o6e+null- null./ while+ 2res.;sCompleted . System.Threading.Thread.Sleep+B./ int grade 1 wc.End;n#o6e+res./

Console.WriteLine("Wor4er *ra e1 " ; *ra e);


5

1s!nchronous (otificationL Delegates


1nfortunately' 8eter was bac$ to the very thing he wanted his boss to avoid with him in the beginning: loo$ing over the shoulder of the entity doing the wor$. 5o' he decided to employ another delegate as a means of notification when the asynchronous wor$ was

completed' allowing him to get bac$ to wor$ immediately but still be notified when his wor$ had been graded:
#/&lic $oi 9oWor4() " ... Console.WriteLine("Wor4er: !or4 com#lete "); i,( com#lete 31 n/ll ) "
foreach+ Wor6Completed wc in completed.Ket;n#ocationCist+. . 4 wc.)egin;n#o6e+new 'syncCall!ac6+Wor6Kraded.- wc./ 5

#oid Wor6Kraded+;'sync"esult res. 4 Wor6Completed wc 1 +Wor6Completed.res.'syncState/ int grade 1 wc.End;n#o6e+res./

Console.WriteLine("Wor4er *ra e1 " ; *ra e);


5

,a""iness in the &niverse


8eter' his boss' and the universe were finally satisfied. 8eter%s boss and the universe were allowed to be notified of the events that interested them' reducing the burden of implementation and the cost of unnecessary round/trips. 8eter could notify each of them' ignoring how long it too$ them to return from their target methods while still getting his results asynchronously. The result was the following complete solution:
ele*ate $oi Wor4Starte (); ele*ate $oi Wor4Gro*ressin*(); ele*ate int Wor4Com#lete (); class Wor4er " #/&lic $oi 9oWor4() " Console.WriteLine("Wor4er: !or4 starte "); i,( starte 31 n/ll ) starte (); Console.WriteLine("Wor4er: !or4 #ro*ressin*"); i,( #ro*ressin* 31 n/ll ) #ro*ressin*(); Console.WriteLine("Wor4er: !or4 com#lete "); i,( com#lete 31 n/ll ) " ,oreach( Wor4Com#lete !c in com#lete .Eet6n$ocationList() ) " !c.+e*in6n$o4e(ne! )syncCall&ac4(Wor4Era e ), !c); -

$oi Wor4Era e (6)sync2es/lt res) " Wor4Com#lete !c 1 (Wor4Com#lete )res.)syncState; int *ra e 1 !c.(n 6n$o4e(res); Console.WriteLine("Wor4er *ra e1 " ; *ra e); -

#/&lic e$ent Wor4Starte starte ; #/&lic e$ent Wor4Gro*ressin* #ro*ressin*; #/&lic e$ent Wor4Com#lete com#lete ;

class +oss " #/&lic int Wor4Com#lete () " System.0hrea in*.0hrea .Slee#(N???); Console.WriteLine("+etter..."); ret/rn X; .Q o/t o, 5? Q. class <ni$erse " static $oi Wor4erStarte Wor4() " Console.WriteLine("<ni$erse notices !or4er startin* !or4"); static int Wor4erCom#lete Wor4() " System.0hrea in*.0hrea .Slee#(H???); Console.WriteLine("<ni$erse is #lease ret/rn K; -

!ith !or4erFs !or4");

static $oi Main() " Wor4er #eter 1 ne! Wor4er(); +oss &oss 1 ne! +oss(); #eter.com#lete ;1 ne! Wor4Com#lete (&oss.Wor4Com#lete ); #eter.starte ;1 ne! Wor4Starte (<ni$erse.Wor4erStarte Wor4); #eter.com#lete ;1 ne! Wor4Com#lete (<ni$erse.Wor4erCom#lete Wor4); #eter.9oWor4(); Console.WriteLine("Main: !or4er com#lete Console.2ea Line(); !or4");

8eter $new that getting results asynchronously came with issues' because as soon as he fired events asynchronously' the target methods were li$ely to be e(ecuted on another thread' as was 8eter%s notification of when the target method has completed. 6owever' 8eter was familiar with +hapter 1H: ultithreaded 1ser *nterfaces' so he understood how to manage such issues when building Win#orms applications. 0nd they all lived happily ever after. The end.

1ppendi3 C. ,eriali-ation "asics


The .net framewor$ is a many/splendored thing. 8arts of this boo$ have e(plored sections of the framewor$ that are completely outside the 5ystem.Windows.#orms namespace because you need that $nowledge to build real Win#orms applications. 0 similar topic is seriali=ation. The need for seriali=ation can be found in any number of places' but for applications developers' seriali=ation is used to save and restore settings data and document data. *t%s in that narrow view that we e(plore this topic.

Streams
Seriali$ation is the ability to read and write an arbitrary ob<ect graph 3reading is sometimes called deseriali$ation4. Before we can tal$ about seriali=ing ob<ects' we need to tal$ about where they%re going seriali=ed to. Whenever an ob<ect is seriali=ed' it must go somewhere. *t may go into memory' a file' a database record' or a soc$et. Generally' where the data is actually written doesn%t matter to the ob<ect itself. *t needs to store the same data regardless of where it goes. 0ll the ob<ect generally cares about is that bytes can be written and read' and sometimes we%d li$e to s$ip around among the bytes. To satisfy these desires' .!"T provides the abstract base class 5tream from the 5ystem.*9 namespace:
a&stract class Stream : Marshal+y2e,D&'ect, 69is#osa&le " .. Fiel s #/&lic static rea only System.6D.Stream B/ll; .. Gro#erties #/&lic &ool Can2ea " $irt/al *et; #/&lic &ool CanSee4 " $irt/al *et; #/&lic &ool CanWrite " $irt/al *et; #/&lic lon* Len*th " $irt/al *et; #/&lic lon* Gosition " $irt/al *et; $irt/al set; .. Metho s #/&lic $irt/al #/&lic $irt/al #/&lic $irt/al #/&lic $irt/al #/&lic $irt/al #/&lic $irt/al 6)sync2es/lt +e*in2ea (...); 6)sync2es/lt +e*inWrite(...); $oi Close(); int (n 2ea (6)sync2es/lt async2es/lt); $oi (n Write(6)sync2es/lt async2es/lt); $oi Fl/sh();

pu!lic #irtual int "ead+!yte%( !uffer- int offset- int count./ pu!lic #irtual int "ead)yte+./ pu!lic #irtual long See6+long offset- System.;J.See6Jrigin origin./

#/&lic $irt/al $oi -

SetLen*th(lon* $al/e);

pu!lic #irtual #oid Write+!yte%( !uffer- int offset- int count./ pu!lic #irtual #oid Write)yte+!yte #alue./

.!"T provides several classes that derive from 5tream' including emory5tream' #ile5tream' and *solated5torage#ile5tream. The emory5tream class is fun to play with because it has no permanent side effects:
using System.;J/

... strin* s 1 "Wahoo3"; int n 1 HA@;


using+ Stream stream 1 new $emoryStream+. . 4

.. Write to the stream &yteIJ &ytes5 1 <nico e(nco in*.<nico e.Eet+ytes(s); &yteIJ &ytes@ 1 +itCon$erter.Eet+ytes(n); stream.Write(&ytes5, ?, &ytes5.Len*th); stream.Write(&ytes@, ?, &ytes@.Len*th); .. 2eset the stream to the &e*innin* stream.See4(?, See4Dri*in.+e*in); .. 2ea ,rom the stream &yteIJ &ytesN 1 ne! &yteIstream.Len*th : HJ; &yteIJ &ytesH 1 ne! &yteIHJ; stream.2ea (&ytesN, ?, &ytesN.Len*th); stream.2ea (&ytesH, ?, &ytesH.Len*th); .. 9o somethin* !ith the ata Messa*e+ox.Sho!(<nico e(nco in*.<nico e.EetStrin*(&ytesN) ; " " ; +itCon$erter.0o6ntN@(&ytesH, ?));
5

This code creates a specific implementation of the abstract 5tream class' ma$ing sure to close it 3even in the face of e(ceptions4. The code then uses the stream for writing and reading bytes' being careful to see$ bac$ to the beginning of the stream in between these actions. We could have written e(actly the same code for any stream. 6owever' the manual conversion of the string ob<ect bac$ and forth between the bytes is $ind of a pain. To avoid writing that code' we%ve got the 5treamWriter and 5tream@eader classes:
strin* s 1 "Wahoo3"; int n 1 HA@; /sin*( Stream stream 1 ne! MemoryStream() ) "
// Write to the stream StreamWriter writer 1 new StreamWriter+stream./ writer.WriteCine+s./ writer.WriteCine+n./ writer.Flush+./ // Flush the !uffer

.. 2eset the stream to the &e*innin* stream.See4(?, See4Dri*in.+e*in);


// "ead from the stream Stream"eader reader 1 new Stream"eader+stream./ string sG 1 reader."eadCine+./ int nG 1 int.Parse+reader."eadCine+../

.. 9o somethin* !ith the ata Messa*e+ox.Sho!(s@ ; " " ; n@); -

This code is considerably simpler because the conversion to bytes is managed by the stream writer and readers as they wor$ on the stream. 6owever' the stream writer and readers are oriented toward te(t only' and that%s why we wrote each piece of data on its own line and why we had to parse the integer bac$ out of the string when reading. To avoid the conversion to and from strings' we can write the data in its native binary format using the BinaryWriter and Binary@eader classes:
strin* s 1 "Wahoo3"; int n 1 HA@; /sin*( Stream stream 1 ne! MemoryStream() ) " .. Write to the stream
)inaryWriter writer 1 new )inaryWriter+stream./

!riter.Write(s); !riter.Write(n); !riter.Fl/sh(); .. Fl/sh the &/,,er .. 2eset the stream to the &e*innin* stream.See4(?, See4Dri*in.+e*in); .. 2ea
)inary"eader reader 1 new )inary"eader+stream./ string sG 1 reader."eadString+./ int nG 1 reader."ead;nt@G+./

,rom the stream

.. 9o somethin* !ith the ata Messa*e+ox.Sho!(s@ ; " " ; n@);

1sing BinaryWriter and Binary@eader eliminates the need for string conversion' but our code still must $eep trac$ of the types of the ob<ects we are writing and the order in which they must be read. We can group the data into a custom class and read it all at once' but BinaryWriter and Binary@eader don%t support custom classes' only built/in simple types. To read and write arbitrary ob<ects' we need a formatter.

Formatters
0 formatter is an ob<ect that $nows how to write arbitrary ob<ects to a stream. 0 formatter e(poses this functionality by implementing the *#ormatter information from the 5ystem.@untime.5eriali=ation namespace:
inter,ace 6Formatter " .. Gro#erties Seriali7ation+in er +in er " *et; set; Streamin*Context Context " *et; set; 6S/rro*ateSelector S/rro*ateSelector " *et; set; .. Metho s

o!9ect 0eseriali&e+Stream seriali&ationStream./ #oid Seriali&e+Stream seriali&ationStream- o!9ect graph./

0 formatter has two <obs. The first is to seriali=e arbitrary ob<ects' specifically their fields' including nested ob<ects. The formatter $nows which fields to seriali=e using @eflection' which is the .!"T 08* for finding out type information about a type at run time. 0n ob<ect is written to a stream via the 5eriali=e method and is read from a stream via the Aeseriali=e method.
L1M L>M L1M

#ormatters even ma$e sure that cyclic data structures are handled properly' allowing you to seriali=e entire ob<ect graphs. #or a thorough e(planation of .!"T @eflection' see

L>M

Essential .NET 30ddison/Wesley' >0034' by Aon Bo(' with +hris 5ells.

The second <ob of a formatter is to translate the data into some format at the byte level. The .!"T #ramewor$ provides two formatters: Binary#ormatter and the 5oap#ormatter. Eust li$e BinaryWriter' the Binary#ormatter class' from the 5ystem. @untime.5eriali=ation.#ormatters.Binary namespace' writes the data in a binary format. 5oap#ormatter' from the 5ystem.@untime.5eriali=ation. #ormatters.5oap namespace' writes data in T 7 according to the 5imple 9b<ect 0ccess 8rotocol 359084 specification. 0lthough 5908 is the core protocol of Web services' using the 5908 formatter for the purposes of seriali=ing settings or document data has nothing to do with Web services or even the Web. 6owever' it is a handy format for a human to read.
L3M L3M

To access this namespace you must add a reference to the 5ystem.@untime.5eriali=ation.#ormatters.5oap assembly.

There is one stipulation on any type that a formatter is to seriali=e: *t must be mar$ed with 5eriali=able0ttribute' or else the formatter will throw a run/time e(ception. 0fter the type 3and the type of any contained field4 is mar$ed as seriali=able' seriali=ing an ob<ect is a matter of creating a formatter and as$ing it to seriali=e the ob<ect:
using System."untime.Seriali&ation/ using System."untime.Seriali&ation.Formatters/ using System."untime.Seriali&ation.Formatters.Soap/ ... %Seriali&a!le'ttri!ute(

class My9ata " .. BD0(: G/&lic ,iel s sho/l &e a$oi e in *eneral, .. &/t are /se,/l to sim#li,y the co e in this case #/&lic strin* s 1 "Wahoo3"; #/&lic int n 1 HA@; static $oi 9oSeriali7e() " My9ata ata 1 ne! My9ata(); /sin*( Stream stream 1 ne! FileStream(V"c:\tem#\my ata.xml", FileMo e.Create) ) "
// Write to the stream ;Formatter formatter 1 new SoapFormatter+./ formatter.Seriali&e+stream- data./

.. 2eset the stream to the &e*innin* stream.See4(?, See4Dri*in.+e*in);


// "ead from the stream $y0ata dataG 1 +$y0ata.formatter.0eseriali&e+stream./

.. 9o somethin* !ith the ata Messa*e+ox.Sho!( ata@.s ; " " ; -

ata@.n);

0fter creating the formatter' the code ma$es a call to 5eriali=e' which writes the type information for the yAata ob<ect and then recursively writes all the data for the fields of the ob<ect. To read the ob<ect' we call Aeseriali=e and ma$e a cast to the top/level ob<ect' which reads all fields recursively. Because we chose the te(t/based 5908 formatter and a #ile5tream' we can e(amine the data that the formatter wrote:
=SD)G:(BC:(n$elo#e ...> =SD)G:(BC:+o y> =a5:FormB8*AAG)8$y0ata i 1"re,:5" ...>
<s id1 ref?@ =Wahoo2</s= <n=SHG</n= =.a5:FormB8*AAG)8$y0ata>

=.SD)G:(BC:+o y> =.SD)G:(BC:(n$elo#e>

6ere we can see that an instance of #orm1. yAata was written and that it contains two fields: one 3called s4 with the value &Wahoo2&' and a second one 3called n4 with the value &HF>.& This was <ust what the code meant to write.

,5ipping a (onseriali-ed Field


We have some control over what the formatter writes' although probably not in the way you%d e(pect. #or e(ample' if we decide that we want to seriali=e the yAata class but not the n field' we can%t stop the formatter by mar$ing the field as protected or private. To be consistent at deseriali=ation' an ob<ect will need the protected and private fields <ust as much as it needs the public ones 3in fact' fields shouldn%t be public at all24. 6owever' if we apply !on5eriali=ed0ttribute to a field' it will be s$ipped by the formatter:
ISeriali7a&le)ttri&/teJ class My9ata " #/&lic strin* s 1 "Wahoo3"; %NonSeriali&ed'ttri!ute( #/&lic int n 1 HA@; -

5eriali=ing an instance of this type shows that the formatter is s$ipping the nonseriali=ed field:
=SD)G:(BC:(n$elo#e ...>

=SD)G:(BC:+o y> =a5:FormB8*AAG)8$y0ata i 1"re,:5" ...>


<s id1 ref?@ =Wahoo2</s= =.a5:FormB8*AAG)8$y0ata>

=.SD)G:(BC:+o y> =.SD)G:(BC:(n$elo#e>

+Deseriali-ationCall9ac5
Good candidates for the nonseriali=ed attribute are fields that are calculated' cached' or transient' because they don%t need to be stored. 6owever' when an ob<ect is deseriali=ed' the nonseriali=ed fields may need to be recalculated to put the ob<ect into a valid state. #or e(ample' if we e(pand the duties of the n field of the yAata type to be a cache of the s field%s length' there%s no need to persist n' because it can be recalculated at any time. 6owever' to $eep n valid' the yAata ob<ect must be notified when s changes. 1sing properties $eeps the n and s fields controlled. 6owever' when an instance of yAata is deseriali=ed' only the s field is set' and not the n field 3recall that the n field is nonseriali=ed4. To cache the length of the s field in n after deseriali=ation' we must implement the *Aeseriali=ation+allbac$ interface:
inter,ace 69eseriali7ationCall&ac4 " $oi Dn9eseriali7ation(o&'ect sen er); -

The single method' 9nAeseriali=ation' will be called after the formatter has deseriali=ed all the fields. This is the time to ma$e sure that the nonseriali=ed fields of the ob<ect have the appropriate state:
ISeriali7a&le)ttri&/teJ class My9ata : ;0eseriali&ationCall!ac6 " strin* s 1 "Wahoo3"; %NonSeriali&ed'ttri!ute( int n 1 X; #/&lic strin* Strin* " *et " ret/rn s; set " $al/e 1 s; n 1 s.Len*th; #/&lic int Len*th " *et " ret/rn n; #region ;mplementation of ;0eseriali&ationCall!ac6 pu!lic #oid Jn0eseriali&ation+o!9ect sender. 4 // Cache the string:s length n 1 s.Cength/ 5 #endregion

*f you%ve got any fields mar$ed as nonseriali=ed' chances are you should be handling *Aeseriali=ation+allbac$ to set those fields at deseriali=ation time.

'Seriali*a0le
To gain even more control over the seriali=ation process' you can implement the *5eriali=able interface and a special constructor:
ISeriali7a&le)ttri&/teJ class My9ata : ;Seriali&a!le " strin* s 1 "Wahoo3"; int n 1 X; #/&lic strin* Strin* " *et " ret/rn s; set " $al/e 1 s; n 1 s.Len*th; #/&lic int Len*th " *et " ret/rn n; #/&lic My9ata() "#region ;mplementation of ;Seriali&a!le pu!lic $y0ata+ Seriali&ation;nfo info- StreamingConte*t conte*t. 4 // Ket #alue from name/#alue pairs s 1 info.KetString+ $yString ./

.. Cache the strin*Fs len*th n 1 s.Len*th;


5 pu!lic #oid KetJ!9ect0ata+ Seriali&ation;nfo info- StreamingConte*t conte*t. 4 // 'dd #alue to name/#alue pairs info.'ddLalue+ $yString - s./ 5 #endregion

*mplementing *5eriali=able.Get9b<ectAata puts your class on the hoo$ to populate the nameCvalue pairs that the formatter is using to fill the stream during seriali=ation. Get9b<ectAata is provided with two pieces of information: a place to put the fields to seriali=e 3called the seriali$ation information4 and the location where the ob<ect is going 3called the conte%t state4. Get9b<ectAata must add all the fields to the seriali=ation information that it would li$e to have seriali=ed' naming each one. The formatter uses these names to write the data:
=SD)G:(BC:(n$elo#e ...> =SD)G:(BC:+o y> =a5:Form5%x??@+%My9ata i 1"re,:5" ...> =$yString i 1"re,:N">Wahoo3=.s>

=.a5:Form5%x??@+%My9ata> =.SD)G:(BC:+o y> =.SD)G:(BC:(n$elo#e>

Aeseriali=ation happens with the special constructor' which also ta$es seriali=ation info and a conte(t state' this time to pull out the data. The 5eriali=ation*nfo class provides several methods for pulling out typed data. #or built/in types' you can use the specific method' such as Get5tring. #or general types' you can use the Get-alue method. #or e(ample' the following two lines of code are e)uivalent' with the latter the only choice for custom types:
s 1 in,o.EetStrin*("MyStrin*");
s 1 +string.info.KetLalue+ $yString - typeof+string../

#ata Versioning
The types that hold the data are always sub<ect to the .!"T rules of versioning' but that doesn%t really help you when it comes to reading and writing old versions of the data using new versions of the ob<ect. 9ne way to support versioned data formats is to write a version *A into the stream as part of a custom implementation of *5eriali=able:
LHM LHM

The details of .!"T type versioning are covered in

Essential .NET 30ddison/Wesley' >0034' by Aon Bo(' with +hris 5ells.

ISeriali7a&le)ttri&/teJ class My9ata : ;Seriali&a!le " strin* s 1 "Wahoo3"; int n 1 X;

'rrayCist oldStrings 1 new 'rrayCist+./ // #G.A addition static string currentLersion 1 G.A /

... Ure*ion 6m#lementation o, 6Seriali7a&le #/&lic My9ata( Seriali7ation6n,o in,o, Streamin*Context context) "
// "ead the data !ased on the #ersion string streamLersion 1 info.KetString+ Lersion ./ switch+ streamLersion . 4 case B.A 3

!rea6/ case G.A 3

s 1 in,o.EetStrin*("MyStrin*"); n 1 s.Len*th; s 1 in,o.EetStrin*("MyStrin*"); n 1 s.Len*th; &rea4;

oldStrings 1 +'rrayCist.info.KetLalue+ JldStrings - typeof+'rrayCist../

#/&lic $oi EetD&'ect9ata( Seriali7ation6n,o in,o, Streamin*Context context) "


// Tag the data with a #ersion info.'ddLalue+ Lersion - currentLersion./ info.'ddLalue+ $yString - s./

info.'ddLalue+ JldStrings - oldStrings./

Uen re*ion -

This implementation writes a -ersion string on all the data it writes' and then it uses that string to decide which data to read bac$ in at run time. 0s the data in a class changes' mar$ing it with a version gives you a way to migrate old data forward 3or to save old versions' if you%d li$e4. !otice also that the new hun$ of data added is an 0rray7ist. Eust li$e the simple types' the collection classes 3along with a large number of other classes in the .!"T #ramewor$4 can be seriali=ed' ma$ing this model useful for all $inds of things' from storing user settings 3as discussed in +hapter 11: 0pplications and 5ettings4 to saving document state.

1ppendi3 D. ,tandard WinForms Components and Controls


Winforms provides several classes meant to be composed to build applications. 6ow 3and whether4 instances of these classes need to interact with the user determines whether they are components or controls. The technical distinction isn%t important unless you%re building one 3as covered in +hapter 8: +ontrols and +hapter 9: Aesign/Time *ntegration4. What is important is $nowing what%s available out of the bo( for your use' as listed in Table A.1. This appendi( briefly covers all these e(cept for the print/related components' which are discussed in +hapter I: 8rinting. Ta9le D.1. ,tandard WinForms Components and Controls
Components Controls

+olorAialog 3page S3H4 +onte(t enu 3page SH14 "rror8rovider 3page SH>4 #ontAialog 3page S3F4 6elp8rovider 3page SH>4 *mage7ist 3page SH04 ain enu 3page SH14 9pen#ileAialog 3page S3F4 8age5etupAialog 3+hapter I: 8rinting4 8rintAialog 3+hapter I: 8rinting4 8rintAocument 3+hapter I: 8rinting4 8rint8reviewAialog 3+hapter I: 8rinting4 5ave#ileAialog 3page S3S4 Timer 3page S394 ToolTip 3page SH34

Button 3page SHH4 +hec$Bo( 3page SHF4 +hec$ed7istBo( 3page SHI4 +omboBo( 3page SHI4 AataGrid 3page SH94 AateTime8ic$er 3page SF14 Aomain1pAown 3page SF>4 GroupBo( 3page SFI4 65crollBo( 3page SF14 7abel 3page SH34 7in$7abel 3page SHH4 7istBo( 3page SHS4 7ist-iew 3page SH84 onth+alendar 3page SF04 !umeric1pAown 3page SF34 8anel 3page SFI4 8ictureBo( 3page SHS4 8rint8review+ontrol 3page SFH4

Ta9le D.1. ,tandard WinForms Components and Controls


Components Controls

8rogressBar 3page SF34 @adioButton 3page SHF4 @ichTe(tBo( 3page SFH4 5plitter 3page SFF4 Te(tBo( 3page SHH4 5tatusBar 3page SFS4 Tab+ontrol 3page SF84 ToolBar 3page SFF4 Trac$Bar 3page SF34 Tree-iew 3page SH94 -5crollBar 3page SF>4 The Win#orms documentation does a really good <ob of providing the details of each standard component and control. This appendi( is a )uic$ loo$ at each of them to give you an idea of what you have to choose from.

Com"onents and Controls #efined


0 component is a class that implements the *+omponent interface from the 5ystem.+omponent odel namespace. 6ow to implement *+omponent is covered in +hapter 9: Aesign/Time *ntegration' but any class that implements *+omponent becomes a component and can thereafter be integrated with a component hosting environment' such as -5.!"T. *n -5.!"T this integration means that the component can show up on the Toolbar' can be dropped onto a design surface 3such as a #orm4' and can have public properties set and public events consumed in the 8roperty Browser. The chief difference between a control and a component is the location where the interaction with the user occurs 3if there is an interaction with the user4. 0 control draws in a container/provided region and ta$es input from the user. #or e(ample' a Te(tBo( is a control. 9n the other hand' a component may show a 1* and ta$e some input from the user' but it doesn%t do so in a container/provided region. #or e(ample' the 9pen#ileAialog class is a component that interacts with the user' but in a separate window and not in a region on the form that creates the dialog. The distinction between a component and a control is further evident on a -5.!"T design surface itself' as shown in #igure A.1.

Figure D.1. Components =ersus Controls

Standard Com"onents
The following is a )uic$ survey of the components immediately available in the Windows #orms Toolbo( by default.
L1M L1M

.!"T provides many more components that can be used with Win#orms applications' but a survey of all those is beyond the scope of this boo$.

,tandard Dialogs
Win#orms provides several standard dialog components that act as wrappers on the standard dialogs in the Windows shell. 0ll the standard print/related components. 8age5etupAialog' 8rintAialog' 8rint8reviewAialog' and 8rintAocument.are covered in +hapter I: 8rinting.
L>M L>M

Because the need for previewing print output is prevalent' Win#orms provides a standard 8rint8reviewAialog even though the Windows shell

does not.

#igures A.>' A.3' A.H' A.F' and A.S show the four remaining standard dialogs as provided by the +olorAialog' #ontAialog' 9pen#ileAialog' 5ave#ileAialog' and #olderBrowserAialog components. Figure D.2. 1 ,ample 7sage of the ColorDialog Component

Figure D. . 1 ,ample 7sage of the FontDialog Component

Figure D.#. 1 ,ample 7sage of the *penFileDialog Component

Figure D.'. 1 ,ample 7sage of the ,a&eFileDialog Component

Figure D... 1 ,ample 7sage of the Folder"rowserDialog Component

Generally' you%re most concerned with the +olor property of +olorAialog' the #ont property of #ontAialog' and the #ile!ame property of 9pen#ileAialog and 5ave#ileAialog. 6owever' all the dialogs provide other properties you%ll want to e(amine' including the Aefault"(t and #ilter properties of 9pen#ileAialog and 5ave#ileAialog. 6ere%s an e(ample of the standard dialog components assuming that each of the components was created by dropping it onto a design surface:
.. <se the Color9ialo* $oi chooseColor%Clic4(o&'ect sen er, ($ent)r*s e) " .. Set initial color this.color9ialo*5.Color 1 this.+ac4Color; .. )s4 the /ser to #ic4 a color i,( this.color9ialo*5.Sho!9ialo*() 11 9ialo*2es/lt.DL ) " .. G/ll o/t the /serFs choice this.+ac4Color 1 this.color9ialo*5.Color; .. <se the Font9ialo* $oi chooseFont%Clic4(o&'ect sen er, ($ent)r*s e) " .. Set initial ,ont this.,ont9ialo*5.Font 1 this.Font; .. )s4 the /ser to #ic4 a ,ont i,( this.,ont9ialo*5.Sho!9ialo*() 11 9ialo*2es/lt.DL ) " .. G/ll o/t the /serFs choice this.Font 1 this.,ont9ialo*5.Font; .. <se the D#enFile9ialo* (/se '/st li4e the Sa$eFile9ialo*) $oi chooseFile0oD#en%Clic4(o&'ect sen er, ($ent)r*s e) "

.. Set initial ,ile name this.o#enFile9ialo*5.FileBame 1 this.,ileBame0ext+ox.0ext; .. )s4 the /ser to #ic4 a ,ile i,( this.o#enFile9ialo*5.Sho!9ialo*() 11 9ialo*2es/lt.DL ) " .. G/ll o/t the /serFs choice this.,ileBame0ext+ox.0ext 1 this.o#enFile9ialo*5.FileBame; -

0ll the standard dialogs' including the print/related dialogs 3e(cept for 8rint8reviewAialog4' are limited to only modal operation via 5howAialog because that%s what the shell dialogs support. 9nly 8rint8reviewAialog' which is provided in Win#orms but not provided in the shell' supports the modeless 5how method.

(otif! +cons
0nother service provided by the Windows shell is the notification tray' where applications can put their own icons to interact with the user' as shown in #igure A.I. The !otify*con component has the following interesting properties and events:

Lisi!le property: ;con property:

whether or not the icon is shown on the tray the icon to show Te*t property: the tooltip over the icon Conte*t$enu property: the menu to show when the user right/clic$s on the icon Clic6 e#ent: when the user clic$s on the icon or selects from the conte(t menu 0ou!le?Clic6 e#ent: when the user double/clic$s on the icon $ouse0own- $ouse$o#e- and $ouseIp e#ents: custom interaction with the user Figure D.0. 1 ,ample 7sage of the (otif!+con Component

#igure A.I shows a notify icon implemented with the following code. *t sets the tooltip te(t and icon at run time based on the user clic$ing and choosing menu items from a conte(t menu:
.. 6cons 6con north6con 1 ne! 6con(V"C:\...\)2W?@<G.6CD"); 6con east6con 1 ne! 6con(V"C:\...\)2W?@20.6CD"); 6con so/th6con 1 ne! 6con(V"C:\...\)2W?@9B.6CD"); 6con !est6con 1 ne! 6con(V"C:\...\)2W?@L0.6CD"); .. Hel#er $oi Set9irection(strin* irection) "

// Set the tooltip compassNotify;con.Te*t 1 direction/ // Set the icon

s!itch( irection ) " case "Borth": compassNotify;con.;con 1 north;con/ &rea4; case "(ast": compassNotify;con.;con 1 east;con/ &rea4; case "So/th": compassNotify;con.;con 1 south;con/ &rea4; case "West": compassNotify;con.;con 1 west;con/ &rea4; // Conte*t menu item3 North #oid north$enu;tem8Clic6+o!9ect sender- E#ent'rgs e. 4 5

Set9irection("Borth");

// Conte*t menu item3 East- South- and West elided // Clic6 handler #oid compassNotify;con8Clic6+o!9ect sender- E#ent'rgs e. 4

s!itch( com#assBoti,y6con.0ext ) " case "Borth": Set9irection("(ast"); case "(ast": Set9irection("So/th"); case "So/th": Set9irection("West"); case "West": Set9irection("Borth"); -

&rea4; &rea4; &rea4; &rea4;

Timer
The e(ample in #igure A.I uses mouse clic$s to do some primitive animation of the notify icon' but a more fun implementation would do animation itself. Because animation is simply a matter of changing an image after a certain amount of time has elapsed' our code to change the image is halfway there. 0ll that%s needed is a way to be notified when a certain amount of time has elapsed' a perfect <ob for the Timer component. The Timer component notifies a listener via the Tic$ event based on two properties. The "nabled property must be set to true for the Tic$ event to fire. The *nterval property is the number of milliseconds to wait between Tic$ events. The following e(ample uses a timer to animate the notify icon:
.. Boti,y icon clic4 han ler $oi com#assBoti,y6con%Clic4(o&'ect sen er, ($ent)r*s e) "
// Toggle animation timerB.Ena!led 1 2timerB.Ena!led/ timerB.;nter#al 1 BAAA/ // 'nimate once/second

// Timer Tic6 e#ent handler #oid timerB8Tic6+o!9ect sender- E#ent'rgs e. 4

s!itch( com#assBoti,y6con.0ext ) " case "Borth": Set9irection("(ast"); case "(ast": Set9irection("So/th"); case "So/th": Set9irection("West"); case "West": Set9irection("Borth"); -

&rea4; &rea4; &rea4; &rea4;

+mage $ist

0ll this animation brings to mind the tas$ of managing images' and that%s what the *mage7ist component is for. 0n image list is a collection of images of the same si=e' color depth' and transparency color 3as determined by the 5i=e' +olorAepth' and Transparency+olor properties4. The images themselves are in the *mages collection and can contain any number of *mage ob<ects. Jou can edit the *mages collection directly using *mage +ollection "ditor' as shown in #igure A.8. Figure D.2. +mage Collection %ditor

To use the *mage7ist after the images have been populated in the collection editor' you pull them by inde( from the *mages collection property:
$oi Set9irection(strin* irection) " int in ex 1 :5; s!itch( irection ) " case "Borth": in ex 1 ?; &rea4; case "(ast": in ex 1 5; &rea4; case "So/th": in ex 1 @; &rea4; case "West": in ex 1 N; &rea4; .. Set &ac4*ro/n ,rom the ima*e list
this.)ac6ground;mage 1 !ac6ground;mageCist.;mages%inde*(/

What%s nice about this code is that all the related images come from a single place. 6owever' the *mage7ist component has some limitations:

Jou can%t edit an image after it%s been addedP you must remove the old image and add the edited image.

Jou can have only a fi(ed si=e of up to >FS pi(els in either dimension. *mage +ollection "ditor is difficult to use for images larger than 1S pi(els in either direction. Jou must access images by inde(P you can%t access them by name. *mages are available only as type *mage and not directly as type *con' so if you need the *con type you must convert it from *mage.

These limitations' however' don%t stop the image list from being useful. +hapter 8: +ontrols e(plains how to use image lists to set the images for toolbars and other controls that need small' fi(ed/si=e' related images.

8ain 8enus and Conte3t 8enus


The ain enu component shows the menu at the top of a form and provides events when the user selects an item. The +onte(t enu component' shown in #igure A.9' provides a menu to be associated with a control and invo$ed using the conte(t mouse button 3most often the right mouse button4. Both the ain enu and the +onte(t enu components are covered in +hapter >: #orms. Figure D.4. 1 ,ample 7sage of the Conte3t8enu Component

%rror Pro&ider, Help Pro&ider, and Tooltips The "rror8rovider component gives you a way to notify users that a control has invalid data currently entered' as shown in #igure A.10. Figure D.16. 1 ,ample 7sage of the %rrorPro&ider Component

6elp8rovider supports the handling of the #1 $ey and ? button on a dialog. #igure A.11 shows an e(ample of the pop/up help supported by the help provider. Figure D.11. 1 ,ample 7sage of the HelpPro&ider Component

0s yet another way to show pop/up information' #igure A.1> shows the ToolTip component' which pops up when a user hovers the mouse over a control. Figure D.12. 1 ,ample 7sage of the ToolTip Component

The error provider' the help provider' and the tooltip component are especially useful when you%re building forms to be used as dialogs' so these components are discussed in detail in +hapter 3: Aialogs.

Standard Controls
The basic unit of the user interface in Win#orms is the control. "verything that interacts directly with the user in a region defined by a container is an instance of an ob<ect that derives' directly or indirectly' from the 5ystem.Windows.#orms.+ontrol class.

(on)Container Controls

!on/container controls are those that don%t contain other controls. $a9el The 7abel control' shown in #igure A.13' holds literal te(t that is meant to be informative to the user. #or e(ample' in a typical application' labels are displayed near te(t bo(es to inform the user what the te(t bo( contains. Te(t inside a label wraps to the width of the label. The label te(t can be aligned to any side or corner of the control.
.. La&el la&el5.Te*t 1 "0his is some test text..."; la&el5.Te*t'lign 1 Content)li*nment.0o#Center;

Figure D.1 . 1 $a9el Control in 1ction

$in5$a9el 7in$7abel ob<ects' shown in #igure A.1H' are <ust li$e labels but allow for one or more lin$s to be embedded into the label. These lin$s are clic$able elements that trigger events. Figure D.1#. 1 $in5$a9el Control in 1ction

This control is commonly used to allow users to clic$ on lin$s to Web sites from Windows #orms applications. Jou can add te(t to the lin$ label in the same way as any other label:
.. Will a/tomatically #arse common <2Ls lin4La&el5.Te*t 1 "htt#:..sells&rothers.com";

To ma$e the lin$ wor$' you must handle the 7in$+lic$ed event:
$oi lin4La&el5%Lin4Clic4e ( o&'ect sen er, Lin4La&elLin4Clic4e ($ent)r*s e) " .. Start 6( !ith the <2L System.9ia*nostics.Grocess.Start(e.Lin4.Lin49ata as strin*); -

Te3t"o3 Te(tBo( ob<ects' shown in #igure A.1F' are used to display user/editable te(t. The te(t bo( allows for either single or multiline display and editing of te(t. The most common thing you%ll do with a te(t bo( is retrieve the te(t within it:

Messa*e+ox.Sho!(te*t)o*B.Te*t);

Figure D.1'. 1 Te3t"o3 Control in 1ction

"utton Button ob<ects' shown in #igure A.1S' are used to trigger actions on forms. When a button is pressed' the +lic$ event is triggered:
$oi &/tton5%Clic4(o&'ect sen er, System.($ent)r*s e) " Messa*e+ox.Sho!("D/ch3"); -

Figure D.1.. 1 "utton Control in 1ction

*n addition' buttons can be designated as a form%s 0cceptButton or +ancelButton. These designations specify that the button is automatically clic$ed when the user presses the "nter $ey 30cceptButton4 or the "5+ $ey 3+ancelButton4. Chec5"o3 +hec$Bo( ob<ects' shown in #igure A.1I' are most often used to indicate the answer to a yesCno )uestion. +hec$ bo(es normally have two states: chec$ed or unchec$ed. Testing the state of the chec$ bo( is as simple as retrieving the value of the +hec$ed property:
i,( chec4+ox5.Chec4e ) Messa*e+ox.Sho!("Chec4 &ox chec4e 3");

Figure D.10. 1 Chec5"o3 Control in 1ction

+hec$ bo(es also support a mode in which they have three states: chec$ed' unchec$ed' and indeterminate. When this mode is enabled' a chec$ bo( starts in an indeterminate state and reacts to user input to toggle between chec$ed and unchec$ed. /adio"utton @adioButton controls' shown in #igure A.18' are similar to chec$ bo(es in that they can have a chec$ed and an unchec$ed state' but @adioButton controls are normally used in a series to indicate one of a range of options. When more than one radio button is placed in a container 3a form or one of the container controls listed later4' the radio buttons allow only

one button at a time to be selected. Jou can test radio buttons in the same way you chec$ chec$ bo(es:
i,( ra io+/tton5.Chec4e ) Messa*e+ox.Sho!("2a io &/tton chec4e ");

Figure D.12. 1 /adio"utton Control in 1ction

Picture"o3 The 8ictureBo( control%s one and only function is to display images to the user' as shown in #igure A.19. The picture bo( supports most bitmap formats 3.bmp' .<pg' .gif' and so on4 and some vector formats 3.emf and wmf4. 6ere is an e(ample of setting an image into a 8ictureBo( control:
#ict/re+ox5.6ma*e 1 ne! +itma#(V"c:\!in o!s\7a#otec.&m#");

Figure D.14. 1 Picture"o3 Control in 1ction

$ist"o3 7istBo(' shown in #igure A.>0' holds multiple items that can be selected by a user. Jou manipulate items in a 7istBo( using the *tems collection property. 0 list bo( supports selection of one or more items in the list by the traditional +trl/clic$ing of items. Jou can find out the selected item by using the 5elected*tem property:
Messa*e+ox.Sho!("Selecte 6tem is: " ; list)o*B.Selected;tem.0oStrin*()));

Figure D.26. 1 $ist"o3 Control in 1ction

*n addition' you can handle the 5elected*nde(+hange event whenever the selection changes:
$oi list+ox5%Selecte 6n exChan*e (o&'ect sen er, ($ent)r*s e) " .. 6tem chan*e , so let the /ser 4no! !hich one is selecte Messa*e+ox.Sho!("Selecte 6tem is: " ; list)o*B.Selected;tem.0oStrin*())); -

Chec5ed$ist"o3 0 chec$ed list bo(' shown in #igure A.>1' is an e(tension of the list bo( that allows selection of multiple items in the list by chec$ing bo(es. *n all other ways the chec$ed list bo( is identical to the standard list bo(. Figure D.21. 1 Chec5ed$ist"o3 Control in 1ction

Com9o"o3 The +omboBo( control' shown in #igure A.>>' is a hybrid of a list bo( and a te(t bo(. The te(t bo( part of the control allows you to enter data directly into the control. When the user clic$s on the down button' a list of items is shown' and users can pic$ items from this list. 7i$e a te(t bo(' a combo bo( can be configured to allow free/form entry of information or to allow users to select only items that are in the list of items within the control. Because the control is part te(t bo( and part list bo(' it%s not surprising that it can do a little of both. 0s with the te(t control' the most common tas$ is usually retrieving the te(t:
Messa*e+ox.Sho!(com!o)o*B.Te*t);

Figure D.22. 1 Com9o"o3 Control in 1ction

0s with the list bo(' you can handle the event when the selected inde( changes:

$oi com&o+ox5%Selecte 6n exChan*e (o&'ect sen er, ($ent)r*s e) " .. 6tem chan*e , so let the /ser 4no! !hich one is selecte Messa*e+ox.Sho!("Selecte 6tem is: " ; com!o)o*B.Selected;tem.0oStrin*())); -

$ist=iew The 7ist-iew control' shown in #igure A.>3' is similar to the list bo( in that it shows multiple items that can be selected either individually or as multiple selections. The chief difference is that the list view supports views much li$e Windows "(plorer%s view of files. 7ist-iew supports a large icon view' a small icon view' a list view' or a details view. The details view supports more than one piece of information per item and allows you to define columns to show for the list of items. Jou can change the view by changing the -iew property:
listCie!5.Liew 1 Cie!.Small6con;

Figure D.2 . 1 $ist=iew Control in 1ction

0s with the list bo(' you can trap the change in the selected inde(:
$oi listCie!5%Selecte 6n exChan*e (o&'ect sen er, ($ent)r*s e) " .. Sho! the ,irst o, the selecte items Messa*e+ox.Sho!("Selecte 6tem is: " ; listLiewB.Selected;tems%A( .0oStrin*())); -

Tree=iew The Tree-iew control' shown in #igure A.>H' is used to show hierarchies. The tree is made up of nodes. "ach node can contain a nested list as e(posed via the !ode property collection' which is what provides the hierarchy. To create nodes in the tree view' you use code such as this:
.. Create 0ree 6tems 0reeBo e to#Bo e 1 treeCie!5.Bo es.) ("0o# 6tem");

.. ) chil no es in the to# no e to#Bo e.Bo es.) ("Chil Bo e"); to#Bo e.Bo es.) (")nother Chil Bo e");

Figure D.2#. 1 Tree=iew Control in 1ction

*n addition' the control supports events for e(panding nodes' something that allows you to la=ily load the tree view as the user loo$s down the hierarchy. Data:rid The primary function of the AataGrid control' shown in #igure A.>F' is to allow binding to data sets and other multidimensional data sources. *t allows you to store three dimensions of data. 0lthough grids are often thought of as containing two/dimensional data 3rows and columns4' the data grid also allows the display of multiple tables' and that provides the third dimension. +hapter 13: Aata Binding and Aata Grids e(plains data grids and data binding in detail. Figure D.2'. 1 Data:rid Control in 1ction

8onthCalendar The onth+alendar control' shown in #igure A.>S' is used to show or select specific dates. Jou can retrieve the selected date this way:
.. Eet all the 9ates chosen .. SelectionStart is &e*innin* 9ate .. Selection(n is last ate .. Selection2an*e !ill ret/rn all the ates Messa*e+ox.Sho!(strin*.Format("9ate(s): "?- : "5-", monthCalen ar5.SelectionStart.0oShort9ateStrin*(), monthCalen ar5.Selection(n .0oShort9ateStrin*()));

Figure D.2.. 1 8onthCalendar Control in 1ction

The loo$ and feel of the calendar can be changed to blend in with your applications. *n addition' you can show multiple months simultaneously by specifying the +alendarAimensions of the control. Jou can also add boldface to an array of specific dates or yearly dates on the calendar. This is especially useful for creating holiday and vacation calendar applications. The user can select multiple dates or a range of dates' although the ma(imum number of days selected is limited by the a(5election+ount property. DateTimePic5er The purpose of the AateTime8ic$er control' shown in #igure A.>I' is to display a user/ editable date or time or both. To help control the dates and times that are displayed' the control allows for specifying a minimum and ma(imum date and time. To specify whether you want a date or a time' you choose a format for the te(t in the control. 5hort and long specify different date formats' and time specifies a time format. The drop/down arrow on the control shows a calendar control to let users pic$ specific dates. 1sually' if you are using the control for times' you will want to enable the up and down buttons by specifying true for 5how1pAown' as shown in #igure A.>8. Figure D.20. 1 DateTimePic5er Control in 1ction

Figure D.22. 1 DateTimePic5er with ,how7pDown %na9led

To retrieve the date or time from the control' you get the -alue of the control:
.. Sho! the 9ate (or time) #ic4e Messa*e+ox.Sho!( ate0imeGic4er5.Cal/e.0oShort9ateStrin*());

H,croll"ar The 65crollBar control' shown in #igure A.>9' is a hori=ontal scrollbar. 0lthough most controls that use a scrollbar do so automatically' you can use this control to specify a scrollbar for subtle uses such as specifying a range of large values. Jou can specify the minimum and ma(imum range using the inimum and a(imum properties:
hScroll+ar5.Minim/m 1 ?; hScroll+ar5.Maxim/m 1 5?;

Figure D.24. 1n H,croll"ar Control in 1ction

The -alue+hanged event communicates when the value has changed' and the -alue property e(poses the current scroll value:
$oi hScroll+ar5%Cal/eChan*e (o&'ect sen er, ($ent)r*s e) " Messa*e+ox.Sho!("C/rrent scroll $al/e: " ; hScroll+ar5.Cal/e.0oStrin*()); -

=,croll"ar The -5crollBar control' shown in #igure A.30' is a vertical scrollbar. *t is <ust li$e the 65crollBar but is drawn vertically instead of hori=ontally. Figure D. 6. 1 =,croll"ar Control in 1ction

Domain7pDown The Aomain1pAown control' shown in #igure A.31' allows you to specify a list of items that the arrow buttons will switch between. The functionality is much li$e that of the combo bo(' but this control does not support showing the entire list at once. This control is

ultimately a te(t bo( with the upCdown control added so that the user can still type any desired te(t. @etrieving data from the control is identical to retrieving data from a te(t bo(:
Messa*e+ox.Sho!( omain<#9o!n5.0ext);

Figure D. 1. 1 Domain7pDown Control in 1ction

(umeric7pDown #unctionally the !umeric1pAown control is much li$e the Aomain1pAown control' but the intention of this control is to allow the user to specify a numeric value. The control' shown in #igure A.3>' supports minimum value' ma(imum value' and a step value to allow you to control which number can be selected. Jou can select the numeric value of the control using the -alue property:
Messa*e+ox.Sho!(numericIp0ownB.Lalue.0oStrin*());

Figure D. 2. 1 (umeric7pDown Control in 1ction

Trac5"ar The trac$ bar' shown in #igure A.33' allows the user to specify a numeric value with a ma(imum and a minimum value. The control captures the arrow' 8age 1p' and 8age Aown $eys to control how the values are moved on the trac$ bar. Jou can specify the number of positions in the bar' the number of values between each visible tic$' and the number of tic$s to move on an arrow $ey move or on the 8age 1p and 8age Aown $ey moves. Jou can catch the changed event of the trac$ bar this way:
$oi trac4+ar5%Cal/eChan*e (o&'ect sen er, System.($ent)r*s e) " Messa*e+ox.Sho!(trac4+ar5.Cal/e.0oStrin*()); -

Figure D.

. 1 Trac5"ar Control in 1ction

Progress"ar The progress bar' shown in #igure A.3H' is simply a user feedbac$ control that displays a level of completion. The control allows you to specify the minimum and ma(imum values' although the control continues to show the bloc$s shown here. Jou call the increment

method with a number for the amount to move the progress bar. There is no decrement method' but incrementing with a negative value will cause the progress bar to bac$ up:
.. ) $ance the Gro*ress &ar #ro*ress+ar5.6ncrement(5); .. 9ecrement the Gro*ress &ar #ro*ress+ar5.6ncrement(:5);

Figure D. #. 1 Progress"ar Control in 1ction

/ichTe3t"o3 The @ichTe(tBo( control' shown in #igure A.3F' is used for input and display of te(t formatted in the rich te(t format. The control lets you set ranges of te(t with various fonts' colors' and si=es. Jou can save the document in the rich te(t edit control using the 5ave#ile method:
.. Sa$e the ,ile rich0ext+ox5.Sa$eFile("my,ile.rt,", 2ich0ext+oxStream0y#e.2ich0ext);

Figure D. '. 1 /ichTe3t"o3 Control in 1ction

PrintPre&iewControl The 8rint8review+ontrol' shown in #igure A.3S' is used in creating a print preview window' as discussed in +hapter I: 8rinting. Figure D. .. 1 PrintPre&iewControl Control in 1ction

,plitter The 5plitter control' shown in #igure A.3I' is used to allow dynamic resi=ing of a doc$ed control within a form. Aoc$ing and splitting are discussed in detail in +hapter >: #orms. Figure D. 0. 1 ,plitter Control in 1ction

Tool"ar

0 ToolBar' shown in #igure A.38' is similar to a main menu e(cept that toolbars usually are used to specify buttons to press for )uic$er access to specific functionality. The toolbar is made up of a collection of buttons e(posed by the Buttons property. The supported button styles are standard' toggle' separator' and drop/down. The drop/down button allows you to specify a menu to show when the down button is pushed. Jou can handle toolbar button clic$s by handling the Button+lic$ event on the toolbar itself and chec$ing the sender to see which button was clic$ed:
$oi tool+ar5%+/ttonClic4( o&'ect sen er, 0ool+ar+/ttonClic4($ent)r*s e) " i,( sen er 11 tool+ar+/tton5 ) " ... else i,( sen er 11 tool+ar+/tton@ ) " ... -

Figure D. 2. 1 Tool"ar Control in 1ction

,tatus"ar The 5tatusBar control' shown in #igure A.39' is used to show the standard status bar on the bottom of a form. The status bar can either show a simple piece of te(t or show a series of panels' each of which can be either a te(t panel or an owner/drawn panel. Jou can change the te(t in a panel on a status bar li$e so:
.. Set the text in one o, the #anels stat/s+ar5.GanelsI?J.0ext 1 "Wor4in*...";

Figure D. 4. 1 ,tatus"ar Control in 1ction

Container Controls
+ontainer controls are used to hold other controls. These are commonly used to brea$ complicated forms into manageable si=es or for creating logical groups. Panel The 8anel control' shown in #igure A.H0' is a flat container for other controls to be placed within. The panel can have its frame style changed to suit the design of a particular form. Figure D.#6. 1 Panel Control in 1ction

:roup"o3 0 GroupBo(' shown in #igure A.H1' is a 8anel control that has a label and a frame. Figure D.#1. 1 :roup"o3 Control in 1ction

Ta9Control The Tab+ontrol control' shown in #igure A.H>' is a hybrid of a container and a control. The tabs on the top of the control are buttons that switch between pages. "ach of the pages is a separate container for controls. When using a tab control' you design each page%s content by dragging and dropping controls onto the tab control%s surface as you would if each tab page were a separate dialog. Jou can programmatically switch tabs by setting the 5elected*nde( or 5electedTab property:
.. Chan*e the in ex to the thir #a*e (@ 1 Nr #a*e) .. +oth lines o the same thin*, select the #a*e &y in ex .. or #a*e control name ta&Control5.Selecte 6n ex 1 @; ta&Control5.Selecte 0a& 1 ta&Ga*eN; .. Bame o, #a*e control

Figure D.#2. 1 Ta9Control Control in 1ction

"i9liograph!
The following resources either were used to prepare this boo$ or are good resources for more information. Ballinger' Deith. .NET Web Services- Arc"itect&re and ,mplementation it" .NET' Boston' 0: 0ddison/Wesley' >003. Bo(' Aon' with +hris 5ells. Essential .NET* +ol&me 3- T"e )ommon .ang&age R&ntime' Boston' 0: 0ddison/Wesley' >003. +el$o' Eoe. ,nstant S4. Programming' Birmingham L"ng.M: Wro( 8ress' 199F. +hiu' 8eter. ntcopyres.e(e' http:CCwww.codeguru.comCcppQmfcCrsrc/simple.html' 9ctober >001. &#inding and #i(ing 5lammer -ulnerabilities'& http:CCwww.microsoft.comCsecurityCslammer.asp' #ebruary >003. icrosoft Aeveloper !etwor$' http:CCmsdn.microsoft.com !ewcomer' Eoseph. &0voiding ultiple *nstances of an 0pplication'& http:CCflounder.comCnomultiples.htm' arch >003. 9nion' #rit=. Essential ASP.NET' Boston' 0: 0ddison/Wesley' >003.

@ammer' *ngo. Advanced .NET Remoting' Ber$eley' +0: 08ress' >00>. @ichter' Eeffrey. Applied Microsoft .NET Frame or! Programming' @edmond' W0: icrosoft 8ress' >00>. 5ells' +hris. &.!"T Aelegates: 0 +, Bedtime 5tory'& http:CCwww.sellsbrothers.comCwritingCdelegates.htm' >001. 5ells' +hris. &.!"T *mage @e/+oloring'& Windo s Developer Maga$ine Online' http:CCwww.sellsbrothers.comCwritingCAot!et*mage@e+oloring.htm' !ovember >00>. 5ells' +hris. &.!"T Kero Aeployment: 5ecurity and -ersioning odels in the Windows #orms "ngine 6elp Jou +reate and Aeploy 5mart +lients'& MSDN Maga$ine' Euly >00>. 5ells' +hris. &0 5econd 7oo$ at Windows #orms 5eptember >00>. ultithreading'& MSDN Online'

5ells' +hris. &+omponents 0re !ot Eust #or G1*s'& Windo s Developer Maga$ine Online' http:CCwww.windevnet.comCdocumentsCsRIH81Cwin10>I981809FH3C' Euly >00>. 5ells' +hris. &+reating !on/@ectangular Windows'& Windo s Developer Maga$ine Online' http:CCwww.windevnet.comCdocumentsCsRIF3FCwin103H118H8HFI>C1003cso.html' 9ctober >00>. 5ells' +hris. &*ncreasing 8ermissions for Web/Aeployed Win#orms 0pplications'& MSDN Online' !ovember >00>. 5ells' +hris. &7aunching !o/Touch Aeployment 0pplications with +ommand 7ine 0rguments'& MSDN Online' Eune >' >003. 5ells' +hris. & icrosoft .!"T #ramewor$ @esource Basics'& MSDN Online' #ebruary >003. 5ells' +hris. &8rinter argins' 8art 1'& Windo s Developer Maga$ine Online' http:CCwww.windevnet.comCdocumentsCsRIH81Cwin10H809H898I>HC' arch >003. 5ells' +hris. &8rinter argins' 8art >'& Windo s Developer Maga$ine Online' http:CCwww.windevnet.comCdocumentsCsRIH81Cwin10H939SFIII03C' 0pril >003. 5ells' +hris. &@esources and Win#orms'& Windo s Developer Maga$ine Online' http:CCwww.sellsbrothers.comCwritingC@esources0ndWin#orms.htm' 5eptember >00>. 5ells' +hris. &5afe' 5imple Eanuary >003. 5ells' +hris. &5afe' 5imple ultithreading in Windows #orms' 8art 3'& MSDN Online' ultithreading in Windows #orms'& MSDN Online' Eune >00>.

5ells' +hris. &5eriali=ation Basics' 8art 1'& Windo s Developer Maga$ine Online' http:CCwww.windevnet.comCdocumentsCsRIH81Cwin10HHFI1I8S90HC' #ebruary >003. 5ells' +hris. &5eriali=ation Basics' 8art >'& Windo s Developer Maga$ine Online' http:CCwww.windevnet.comCdocumentsCsRIH81Cwin10HF0933HH1S>C' #ebruary >003. 5ells' +hris. &5eriali=ation Basics' 8art 3'& Windo s Developer Maga$ine Online' http:CCwww.windevnet.comCdocumentsCsRIH81Cwin10HS80193110SC' arch >003. 5ells' +hris. &Windows #orms 7ayout'& MSDN Online' Aecember >00>. 5ells' +hris. &Windows #orms: .!"T #ramewor$ 1.1 8rovides "(panded !amespace' 5ecurity' and 7anguage 5upport for Jour 8ro<ects'& MSDN Maga$ine' arch >003. 5ells' +hris. &Win#orms 0uto/5caling'& Windo s Developer Maga$ine Online' http:CCwww.sellsbrothers.comCwritingCwinforms0uto5caling.htm' !ovember >00>.

5ells' +hris. &Win#orms Aata -alidation'& Windo s Developer Maga$ine Online' http:CCwww.sellsbrothers.comCwritingCwinformsAata-alidation.htm' !ovember >00>. 5ells' +hris' et al. Genghis class library' http:CCwww.genghisgroup.com. 5$inner' organ. Aebug*"6ost @egistry setting' http:CCdiscuss.develop.comCarchivesCwa.e(e?0>Rind01090[7RA9T!"T[8R@9>FS[*R/ 3' 5eptember >001. Weinhardt' ichael' and +hris 5ells. &Building Windows #orms +ontrols and +omponents with @ich Aesign/Time #eatures' 8art 1'& MSDN Maga$ine' 0pril >003. Weinhardt' ichael' and +hris 5ells. &Building Windows #orms +ontrols and +omponents with @ich Aesign/Time #eatures' 8art >'& MSDN Maga$ine' ay >003. Weinhardt' ichael' and +hris 5ells. &@egular "(pressions in .!"T'& Windo s Developer Maga$ine' http:CCwww.wd/mag.comCdocumentsCsRIFHICwin0>1>dC' !ovember >00>. Wildermuth' 5hawn. Pragmatic ADO.NET' Boston' 0: 0ddison/Wesley' >003.

The material from the following MSDN Maga$ine articles served as the base for +hapter 9: Aesign/Time *ntegration' and +hapter 1F: Web Aeployment: 5ells' +hris. &.!"T Kero Aeployment: 5ecurity and -ersioning odels in the Windows #orms "ngine 6elp Jou +reate and Aeploy 5mart +lients'& MSDN Maga$ine' Euly >00>. Weinhardt' ichael' and +hris 5ells. &Building Windows #orms +ontrols and +omponents with @ich Aesign/Time #eatures' 8art 1'& MSDN Maga$ine' 0pril >003. Weinhardt' ichael' and +hris 5ells. &Building Windows #orms +ontrols and +omponents with @ich Aesign/Time #eatures' 8art >'& MSDN Maga$ine' ay >003.

You might also like