Dotnet Windows Forms Custom Controls
Dotnet Windows Forms Custom Controls
-
The Designer
Adding a Toolbox Bitmap
Summary
Chapter 4. GDI+
The - Class
Brushes
Pens
Fonts
Working with Images
The Class
Summary
Chapter 5. Advanced Control Development
Control Base Classes
Designer Base Classes
Designer Services
Designer Attributes
Design-Time UI Clues
Attributes
Properties
Basic Control Debugging
Summary
Chapter 6. Control Design Concepts
Control Design
Control Internals
A Preview of the Control
Summary
Chapter 7. Component
Detailed Design
Implementation
Summary
Chapter 8. Control
Control Design
Control Implementation
Summary
Chapter 9. - Control
Design
Implementation
Testing the Control
Summary
Chapter 10. Control Deployment
Licensing
Hosting Controls in Internet Explorer
GAC (Global Assembly Cache) Versus Local Assemblies
Summary
appendix A. Extender Providers
Creating Extender Providers
Control-Based Extender Providers
Summary
Index
- -
All rights reserved. No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any
means, electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher.
No patent liability is assumed with respect to the use of the information contained herein. Although every precaution
has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or
omissions. Nor is any liability assumed for damages resulting from the use of the information contained herein.
Library of Congress Catalog Card Number: 2001118745
Printed in the United States of America
First Printing: February 2002
05 04 03 02 4 3 2 1
All terms mentioned in this book that are known to be trademarks or service marks have been appropriately
capitalized. Sams Publishing cannot attest to the accuracy of this information. Use of a term in this book should not be
regarded as affecting the validity of any trademark or service mark.
-
Every effort has been made to make this book as complete and as accurate as possible, but no warranty or fitness is
implied. The information provided is on an "as is" basis. The author and the publisher shall have neither liability nor
responsibility to any person or entity with respect to any loss or damages arising from the information contained in
this book.
Associate Publisher
Linda Engelman
Acquisitions Editor
Neil Rowe
Development Editor
Ginny Bess
Managing Editor
Charlotte Clapp
Project Editor
Carol Bowers
Copy Editor
Cheri Clark
Indexer
Sandy Henselmeier
Proofreader
Andrea Dugan
Technical Editor
Chris Crane
Team Coordinator
Lynne Williams
Media Developer
Dan Scherf
Interior Designer
Anne Jones
Cover Designer
Aren Howell
Page Layout
Mark Walchle
This book is dedicated to my grandfather, Howard R. Henry, a two-time survivor of cancer now in his third battle. His
positive can-do attitude has been an inspiration to everyone in my family and especially to me. I love you, Granddad.
Richard Weeks has been writing software for the better part of 10 years, specializing in C++, MFC, COM, ATL, and
now C#. His major areas of interest include custom control development, compiler theory, distributed applications
development, and design patterns. During the past 10 years, he managed to make time to earn his B.S. degree in
Computer Science from Western Carolina University; it proudly hangs next to his G.E.D. When not working, he enjoys
video games, go-cart racing, and good beer. Richard Weeks can be reached at [email protected] .
Writing a book is not a one-person task, and without the guidance from the editors and technical reviewers at Sams,
there is no way I could have written this book. I would like to thank Neil Rowe, my acquisitions editor for the second
time and survivor of driving with me through the Georgia Dome in a utility cart.
I would also like to thank my friends and family who have supported my efforts and have been most understanding of
the countless hours required to write such a book. And last but not least is my friend Bob Powell, who asked me to coauthor with him in my first writing venture. Oh, be sure to buy his books too.
-
As the reader of this book, you are our most important critic and commentator. We value your opinion and want to
know what we're doing right, what we could do better, what areas you'd like to see us publish in, and any other words
of wisdom you're willing to pass our way.
As an Associate Publisher for Sams Publishing, I welcome your comments. You can fax, email, or write me directly to
let me know what you did or didn't like about this book as well as what we can do to make our books stronger.
Please note that I cannot help you with technical problems related to the topic of this book, and that due to the high
volume of mail I receive, I might not be able to reply to every message.
When you write, please be sure to include this book's title and author, as well as your name and phone or fax number.
I will carefully review your comments and share them with the author and editors who worked on the book.
Fax:
Email:
Mail:
317-581-4770
[email protected]
Linda Engelman
Associate Publisher
Sams Publishing
201 West 103rd Street
Indianapolis, IN 46290 USA
Why write a book on custom controls? Well, there's an adage that says "sex sells." The premise of that adage is the
same for developing custom controls and application development. Consumers tend to buy the pretty box, or the
application with the slickest, most modern user interface. Prettiness also sells. Often, consumers make purchase
decisions regardless of functionality. Custom control development serves two purposes: The first is to mimic user
interface elements found in leading commercial products, and the second is to provide controls not found in the
standard set of Windows common controls.
Examples of useful custom controls include spreadsheet-style controls, such as Spread from FarPoint Technologies,
and the Outlook-style shortcut bar provided by various Toolkit vendors. These custom controls provide elements not
found in the standard set of common controls and allow for application developers to concentrate on building the core
logic of their application without having to spend time developing UI components. Custom controls often fill a specific
need, as in the case of Spread, and other times merely enhance common UI elements such as customizable toolbars
and menus.
In general, Microsoft has set the standard for application user interfaces. In fact, an entire industry has been created
whose sole purpose is providing developers with UI Toolkits that emulate the same UI elements found in most
Microsoft applications. Consider MS Office and VS .NET (Visual Studio .NET). Both of these applications have defined
the expected UI elements in other commercial Windows applications such as Act, Work Force Vision by Peopleclick,
and Visio. Companies that make their living selling custom controls are already releasing updated Toolkits to provide
the UI elements found in VS .NET.
Developing custom controls has often been considered a poorly documented, Windows guru-only task. This doesn't
have to be the case. Although poor documentation does exist, anyone with the proper documentation can create
stunning, modern UI custom controls like those found in MS Office and VS .NET. Such a journey begins with this book.
Before you start this journey, it might be helpful to know how custom controls are defined. A custom control is just a
common control that has been altered in either appearance or functionality to produce a desired effect. A custom
control can also be a new control that doesn't have a counterpart within the Windows common controls. There's no
hard-and-fast definition for what constitutes a custom control. Basically, just keep in mind that a custom contro l is not
part of the Windows common controls.
The process of developing controls is not a single-sided effort. In fact, the .NET base class library provides an
overwhelming set of base classes and interfaces targeted at control development and support. The base class library
provides not only several control base classes, but also several designer base classes that can be extended to provide
the design-time behavior for the control.
For those of you who've had the pleasure (read "torment") of developing ActiveX controls with Microsoft Foundation
Class (MFC) or even ActiveX Template Library (ATL), you'll appreciate the amount of reusable code provided by the
base class libraries. In fact, for simple controls it's possible to merely use one of the existing designers to sup port your
new control.
The .NET Windows Forms framework has the capability to host both classic ActiveX controls and .NET controls, thus
extending the life of existing ActiveX controls. I would expect that third-party UI control vendors will eventually port
all of their existing ActiveX controls over to native .NET controls to benefit from the added support afforded by the
base class libraries.
-
To create custom controls it is assumed that you, the reader, have some knowledge of C# and .NET. In addition, you
should be familiar with Windows Forms development. No prior knowledge of control development is required. The goal
of this book is to teach control development.
It is also helpful if you have some knowledge of advanced C# topics such as attributes, custom events, and reflection.
However, I have provided the needed information about these topics during the course of each control's development
and as necessary. If you are new to programming, I suggest that you choose another avenue for learning, because
this book is not geared toward teaching C# or .NET; rather, it shows you how to use C# and .NET to create your own
controls.
- -
To gain an understanding of what it takes to build controls for Windows Forms, this book takes a sample-based
approach. The first control presented is a simple control. The control acts as a standard
Windows Forms button, and in addition it allows for an icon image to be displayed on the control (see Figure 1).
Figure 1. The control developed in Chapters 2 and 3.
The serves as our first foray into custom control development. Although not very complicated, it shows
how to get started with building controls and using the various services offered by VS .NET for control development.
With the basics of control development covered, we'll move on to advanced control development topics, including
designer services, design-time verses runtime behavior, serialization, and licensing. To explore these topics, we'll
develop an control, which is similar to the custom control found in MS Outlook. Figure 2 shows the
control we will develop as the advanced sample for this book.
Figure 2. An advanced control, the .
After you understand the concepts and tasks related to developing the control and the
control, you will be well equipped to create other custom controls. You'll learn that the necessary requirements for
creating custom controls are the development of a sense of flair and style, and, of course, knowledge of the
requirements for building controls. This book arms you with that knowledge, and hopefully you will confidently develop
your own sense of style with the knowledge and experience you gain from the samples in this book.
Again, the main goal of this book is to teach by example. I've packed as much code as possible into each section,
including the appendix, which deals with Extender Providers. The code provided serves as a stepping- stone from w hich
to extend the provided controls or to use as the building blocks for new controls.
Each chapter builds on the concepts and code examples of the preceding chapter. The controls built over the course of
this book represent a starting point on which you should expand by adding your own features and support. The goal is
to cover key concepts rather than extraneous features that don't contribute to understanding the process of custom
control development.
- IN THIS CHAPTER
l
Control Concepts
Composite Controls
One of the early premises of the Windows operating system was to define a common set of user interface elements to
be shared by all applications. The idea was that a user could learn one application and apply that knowledge to other
applications. Each application shared common user interface elements such as menus, toolbars, and dialog boxes,
thereby creating a sense of unity between all Windows-based applications.
Over the years, the Windows common controls set has been expanded to include more and more user interface
elements that have become commonplace in applications. Of course, these common controls stem from the Windows
operating system itself and the advances of its own user interface.
The development of the Windows UI (User Interface) controls has not been limited to just advances in the Microsoft
Windows environment; rather, many third-party companies have built their entire livelihood off of creating custom
controls for Windows developers. Often, these custom controls mimic the various UI elements found within the latest
Microsoft products, such as Visual Studio and the popular Microsoft Office line of products.
The reason for the thriving market is customer driven. Customers expect that applications have the latest UI elements
found in typical Microsoft products. Somehow a slick up-to-date UI translates to a more powerful application. This
assumption is not always accurate, but a modern UI goes a long way in selling software
just ask any commercial
software developer.
I have often been asked, "Why doesn't Microsoft release its UI components for use by other developers?" Well, the
answer is not very straightforward. Each development team at Microsoft typically creates its own UI controls, and
devoting a single group to creating UI components is not cost-efficient or profitable for a company the size of
Microsoft. A top Toolkit company, as custom control development companies are called, can expect to see revenues of
$5 to $7 million annually. For Microsoft, this would probably cover the landscaping for the main campus.
Developing custom controls is a rewarding experience that allows for a deeper understanding of Windows development
and the development environment, such as Visual Studio. Gaining a solid understanding of the Windows subsystem,
GDI+ (Graphics Device Interface+), and user interaction, such as a mouse and keyboard, tends to push custom
control developers to create more powerful and feature-packed controls and applications.
The remainder of this chapter discusses basic control concepts such as runtime verses design-time support. In
addition, the basic anatomy of controls is covered.
Regardless of the type of control being developed, its use should be immediately obvious to the user of the control.
After all, if the user doesn't know what the control does, then the control serves no valid use. In addition, users
expect some basic concepts to be universal among all controls. For example, users expect a left mouse click to result
in an action.
Consider a menu, a toolbar, and a command button. Although each control looks different, users expect that when
they left-click the control, some action will take place within the application. The action is, of course, application
specific; however, the control's behavior is common among all applications. This common functionality is the
cornerstone of Windows development because it allows users to learn one application and apply that knowledge to
other Windows-based applications.
All Windows-based controls, both common and custom, share several common traits. These traits include the various
properties and events used to define the control's appearance and behavior. Common properties include ,
, , , and , to name a few. Standard events include , -,
-, -, and basic key board events. These events allow for an application to respond to a user's
current action. Properties and events are discussed later in this chapter.
Note
The subject of properties and events as defined within .NET development should be already familiar to you;
therefore, only a brief discussion of their use is covered here.
Controls all have the same basic anatomy. Just as human anatomy is mostly the same and differs only slightly based
on function and form, so is a control's anatomy. Humans share basic properties, such as eye color, hair color, height,
and weight. These properties can be used to describe an individual and help form a visual image of the individual. In
the same way, various properties of a control define its outward appearance.
A control has one physical part and two logical parts. The physical part is the control window. The control window is
the physical space the control occupies on the screen. Within the physical boundary of the control are two logical
regions: client and non-client areas. Figure 1.1 shows a basic form showing these areas.
Figure 1.1. Basic Windows Forms control anatomy.
The form itself represents the control window. The title bar containing the control box represents the non-client area
of the control. The control box contains the , , and - buttons in the upper-right corner of the
window. The remaining area of the control window is considered the client area. The client area is where child controls
would be located and all painting logic, such as drawing a pie chart, is clipped to the client area. The control window,
the client area, and the non-client area are discussed in more detail in the following sections.
The main window of a control defines the physical boundary of the control. All messages, such as mouse and
keyboard, are sent to the main control window for dispatching. These messages are generated based on user
interaction with the current window in focus. The main control window may or may not contain a non-client, or NC,
area. Normally, only top-level windows and dialogs contain NC areas. The NC area is generally reserved for the
window's main caption and the control box. The remainder of the control area is known as the client area. This is
where all other child controls and custom painting logic typically take place.
Often it is not necessary to manage the NC area of a control because the framework handles the painting and
management of this area. The underlying windowing framework takes care of providing the system menu, painting the
title bar, and placing the control box. In addition, the windowing framework handles the dispatching of commands for
the various items, such as system menu items and the minimize/maximize controls.
In recent years, applications have begun to take over the painting and logic of the NC area by providing for gradientfilled title bars or bitmap images within the title-bar area. Such custom painting of the NC area is in the realm of
skinning or applying a theme to an application's appearance. Examples of this custom painting logic include WinAmp
and Microsoft Media Player. Custom painting makes the application look more individual rather than appearing as part
of the collective.
As stated previously, the client area of a control is where most of the work, such as child controls and painting, takes
place. The client area represents a logic boundary within the main control. This logic boundary can be the same size
as the control itself. In the case of a common button, the control consists entirely of a client area. The logical
boundary for the client area is not restricted to the size of the control container, but rather the logical client area can
be any size. Only the client area visible within the boundaries of the parent control will be rendered on the screen, but
the logical size of the client area could be 100 square miles. Of course, I don't suggest a client area that large.
During development of a custom control, the main goal is to produce a control that is concise in its intended use. After
all, if the control's use eludes the end user, the control's development is really pointless. The user might as well have
chosen a better-known control or one of the common controls. Make sure that your users understand the intende d use
of your control.
When you are developing a control that is useful, it is also important to know your targeted audience. When you are
developing custom controls, there are two target audiences. The first is the developer who will use your control when
building an application, and the second is the end user of the application. Although the developer may understand the
use of the control, you must ensure that the end user of the application will also understand how to use the control.
Custom control developers sometimes forget about end users and focus solely on the developers who will be
purchasing the controls and using them to build applications. However, when end users complain that the application
is difficult to use, don't expect the developers to come back and purchase any more custom controls from you.
Custom control development consists of four major areas: properties, events, runtime, and design- time. Each of these
is discussed in the following sections and throughout this book.
Properties are the main mechanism for controlling the appearance of a control. Aspects from color and size to te xt and
images are defined using properties.
Using properties to define characteristics of a control is part of the Rapid Application Development, or RAD, style of
application development. Coupled with RAD is the capability to create small reusable modules of code such as custom
controls. This capability spawned a huge success for Microsoft Visual Basic.
The basic premise of properties was to replace the method call style invocation with a dot style notation. Under the
covers, the dot style notation is actually a method call, but to the developer the dot style notation provides a layer of
abstraction. Listing 1.1 shows a method call and a property reference or dot notation that sets a person's name.
Listing 1.1 Method Call Versus Dot Notation Properties
- - --
-
-
- -
-
-
-
The code snippet in Listing 1.1 shows the stylistic difference between using method calls, lines 3 and 4, and using the
dot notation of properties, lines 8 and 9. The result is the same in both cases. The first and last name of the -
object is set. The debate over which is better or preferred is often discussed; however, know that properties and the
dot notation are a requirement for developing well-behaved and well-received custom controls.
Events provide a basic mechanism for notification between various components. When the left mouse button is clicked
on a control, the control in turn fires off a click event to be handled by the application. These events allow for an
application to respond to user- or system-generated actions. Controls fire events to notify the application that some
action has taken place and the application should in turn do something. That "something" is, of course, application
specific.
The runtime appearance and behavior of a control are what the end user of an application experiences. Thus, whe n an
application is being used, the controls within the application have a defined appearance and a set of behaviors that are
controlled by the application itself. Certain aspects of a control can be modified during runtime, such as the color and
font used. The properties of a control can be modified in the application code, and may even be exposed to the user of
the application. End users of the application do not typically modify the appearance of a control; rather, modifications
-
With the RAD style of development, application developers work with a design-time tool such as Visual Studio to
create applications. During the design of an application, the developer will assign values to various properties of a
control and write custom code to handle any events fired by the control. The design-time cycle of a project is
interactive between the developer and the controls being used within the application. To a custom control develope r, it
is of paramount importance to create a control with solid design-time support. Figure 1.2 shows the Outlook-style
control. You are shown an Outlook-style control as it is developed at design-time in Chapters 7, 8 and 9.
Figure 1.2. Design-time development using the Outlook-style control.
It is during the design-time development of an application that the developers (one of your target audiences) are
addressed. If the custom control is hard for the developer to use to build applications, chances are the developer will
give up and purchase a different custom control to get the job done.
Pleasing developers is a difficult task. It is important to get feedback from the developers using your custom control
and to incorporate their comments into the control's design and usability. It is also helpful to look at the current set of
controls and their design-time support. Perfecting the design-time support of a control takes time and several
iterations of the controls designer. Designers are covered in Chapter 3, "Designer Basics."
- The discussion of custom controls to this point has assumed a single control or single component. As with most
development projects, the end result is a composition of smaller components that in turn create the final product. The
same case holds for custom control development. Rather than create a single monolithic control, custom controls are
often created by using one or more smaller controls.
Simple controls, such as a command button, require only a single control to provide the necessary functionality. A
combo box is a composite control that uses smaller controls to implement the larger control. This design pattern is
known as Composition. Composition is a useful design pattern you should explore when creating any custom control
that contains several aspects found in simpler controls. The - developed in Chapter 2, "Control Basics," is
based on the idea of Composition.
In the days before 128MB of RAM was standard, developers had to save as many system resources as they could. This
is when soft controls came into play. The idea behind soft controls is that the control is not a real control. The control
does not have an HWND, or handle to a physical window, but rather is a logical area on the screen. A soft control
looks and acts just like a standard control as far as the user is concerned. Only the developer knows that it's n ot a real
control.
Soft controls are often contained within a larger control. The host for the soft control handles the processing of user
input and passes along information as needed to the soft control. Such information might include mouse events such
as click events. Soft controls provide a lightweight alternative to full- blown controls and are rather easy to implement.
Soft controls generally provide painting logic and hit-testing. Hit-testing is used to determine whether a point is
located within the boundaries of the soft control. In Chapter 9, "ImageListView Control," a small soft-control scroll
button, similar to those found on scrollbars, is developed to demonstrate this useful technique.
Before we dive into control development in the next chapter, a tour of the VS .NET IDE is in order. Understanding the
development environment is essential when developing custom controls. After all, custom controls must interact with
the environment during design-time to support code-serialization and design-time feedback for the developer using
the control. Figure 1.3 shows the IDE during the design of a Windows application.
Figure 1.3. The parts of the VS .NET IDE.
The VS .NET IDE is geared toward RAD-style development and makes heavy use of the Toolbox and Property Browser
for quickly creating Windows-based applications. During the development of custom controls, it is important to
understand how to properly coexist and make use of the services exposed via the IDE. These interactions and services
are discussed in later chapters as they become important.
When it comes to custom control development, understanding how the Toolbox, Icon Tray, and Property Browser work
is very important. Each of these items reacts to controls and offers different services to be used by a custom control
developer.
The Toolbox, for instance, displays the control name and associated bitmap for the control. In addition, the Toolbox
exposes a service for querying the actively selected control within the Toolbox. This service is needed for providing
drag-and-drop support during design-time. In addition, it is possible to exclude controls from the Toolbox by
specifying that a control should not be visible in the Toolbox. This is accomplished through the use of a custom
attribute. These topics are discussed in later chapters.
The Property Browser is the most widely used of the IDE components. The Property Browser offers several areas of
extensibility, such as custom editors for property values, and the capability to hide and dynamically add properties.
Each of these topics is covered in later chapters. In addition, the Property Browser, known as the
control, can be used in your own applications. By default, the control does not appear within the
Toolbox. Adding the is just a simple matter of customizing the Toolbox and adding the control. This
too is covered in later chapters.
The Icon Tray is used to hold components that require design-time support but are not necessary controls. For
instance, the Menu component is not a control; however, it requires extensive design-time support. In addition to the
Menu, a Tooltip is also a component that requires design-time support. The Tooltip, like the Menu, is not a control but
rather a component. The Icon Tray serves as a bucket to hold these components and allow for a user to select the
component and access its properties via the Property Browser.
Developing custom controls is not a difficult task, and by the end of this book you will have all the knowledge
necessary to create advanced custom controls. The intent of this chapter was to familiarize you with the necessary
terms and to give a general overview of control development. The key requirement for developing custom co ntrols is a
desire to create cool components for other developers to drool over.
Properties
Events
Controls provide a means for user interaction with an application. Every control defines a set of properties, events,
and a user interface that represents the control and the intended use the control has. Control development consists of
two parts: runtime and design-time. Runtime has to do with how the control looks and acts during the execution of
the application, whereas design-time refers to the WYSIWYG editor used by VS .NET to create an application. In this
chapter the runtime aspect of control development is the focus; Chapter 3, "Designer Basics," covers the design-time
aspects of control development.
This chapter covers the basics for creating a simple button control the control. The
control allows an image to be displayed along with the standard text found on the Windows Form s button control
(see Figure 2.1).
Figure 2.1. The control.
The control is a custom control. To build it, you need to understand the following topics:
l
Properties
Events
The process of developing custom controls is the same as that of developing a set of classes. The process begins by
determining the required functionality and the necessary classes that will be responsible for implementing the
functionality. In addition, it is necessary to determine the proper base class for the custom control. Each class can
inherit from a base class and implements zero or more interfaces. Classes also provide properties and even events. As
you'll see in the following sections, custom controls behave much like classes do.
- --The .NET base Class Library provides base classes that provide various levels of support for building controls.
The .NET base Class Library is at the heart of the .NET platform. You can think of the .NET base Class Library as
similar to the C runtime or VB runtime on steroids. In addition to providing common types such as and
-, the base Class Library provides for database access, collections, windows forms, and Web forms
development, to name a few.
Each derived control base class extends the functionality provided by the base class from which it inherits. Figure 2.2
shows the control classes provided by the base Class Library.
Figure 2.2. Control base classes.
The control hierarchy shown in Figure 2.2 should be used to determine where to begin when a new custom Windows
Forms control is being built. Each derived class extends and in some cases modifies the behavior of its parent class.
Every common control available with VS .NET derives from one of the classes in the control hierarchy.
At the very root of all control base classes is the class or the fully qualified name
-. The base class serves as the base class for all classes within the
--- namespace. Any class that inherits from the base class basically states that the
class will free up any resources it uses through the invocation of its -- method. In essence, any class that
inherits from the base class can be told when to clean up without waiting for the object instance to be
collected by the .NET Garbage Collector.
The class represents the single most significant base class in that it provides all the necessary plumbing for
control development. The base class provides for message routing, both keyboard and mouse, security, size
and position, and the creation of the underlying window handle or , for instance. Although the base
class does not provide any default painting, it does provide for all the basic services of a .NET control, including the
implementation of ActiveX control interfaces. By providing all the ActiveX control interfaces, custom controls
developed in .NET can be hosted within Internet Explorer and other ActiveX control hosts.
According to the documentation, classes do not typically inherit from the base class directly, but rather from
the - class. The developers for the Windows Forms controls apparently didn't read that documentation,
because the controls found in the Windows Forms namespace typically inherit directly from the base class.
The --- class serves as the base class for the , , ,
, -, and so on. Actually, the intention is that most developers will be developing simple
- derived controls and do not require the low-level control afforded by twiddling with the underpinnings
of the control framework.
As the name suggests, the base class provides the capability for a control to scroll its contents.
Setting the property to provides the scrolling. The control inherits from this control base
class.
The base class provides the necessary wiring for hosting other controls such as buttons, labels,
and the like, and it serves as the base class for the -, the , and the class. The main
benefit derived from using the is focus management and mnemonic handling for child controls
contained within the . Focus management deals with handling the Tab key and setting focus to
the next control based on the tab order of the child controls. Mnemonic handling is the processing of shortcut or
accelerator keys to set focus to the corresponding control.
Using the object provides processing of events such as the Tab key and focus information about
child controls. It is important to note that a will not receive focus but rather focus will be set to
the first child control of the container.
-
The .NET - is very much the same as the - control concept found in earlier versions of Visual Basic.
Essentially, a - is a simple method for creating a composite control consisting of one or more controls.
Because the - class derives from the base class, it inherits all the focus
management and control management implemented by the . Control management entails hosting
child controls and managing the events of the child controls. In essence, a - is a fully self-contained
control that also generally includes some amount of business logic such as data entry validation and even database
access if necessary.
The - base class provides a solid foundation for creating reusable controls consisting of presentation and
data validation to be reused in an application. Consider building an application in which it is necessary to obtain a
customer's address, suppliers' addresses, and other various addresses. Each of these addresses will be subject to
similar data validation, such as verifying the zip code for a city and the city within a state.
When a - is created to handle this common task, the - can then be used anywhere within
the application that requires validating the user-entered address.
-- -
To gain an understanding of basic control development, designing and creating a simple - provides an
easy starting point. Taking the address validation scenario presented previously, designing and implementing a basic
- to fulfill this purpose requires only the following few simple steps:
1.
2.
3.
4.
5.
Select the new - from the Toolbox and drag it onto the main form (see Figure 2.4).
Figure 2.4. The -- placed on .
Beginning with step 1, create a new Windows application project with the name " Address UserControl." This project
will serve to contain both the custom - and the Windows Form that will be used to host the
--. After the project is created, it's time to add a new - to the project. You can do this by
selecting Add New Item from the File menu or by right-clicking on the project in the Solution explorer and selecting
New UserControl from the Add menu item. Name the new - --.
If the -- is not currently in design mode, double-click the --- file in the Solution
explorer to bring up the - in design mode. Next, construct the -- UI so that it matches
the control shown in Figure 2.3.
After the UI for the control has been constructed, double-click the button to bring up the code window for
the control. Listing 2.1 provides the implementation for the event handler and a small helper method for the
control.
Listing 2.1 Logic for the -- Validate Button
- --
-
--
---
--
---
-- - - -
The code for the -- is fairly simple in all respects. Each on the control is tested to ensure
that the user in fact entered something.
With the code in place, the next step is to build the project. The project must be compiled before the new
-- can be used and placed on the main form. Attempting to use the -- without
building the project results in a rather cryptic message from the IDE about not being able to create the control.
After the project has been compiled, switch back to the main form, which should be -. Now available on the
bottom of the Toolbox in the Windows Forms tab is the newly created --. As with any other control
within the Toolbox, this new control can be dragged onto the form and sized appropriately (see Figure 2.4).
Custom - style controls are handy when building applications in which the UI and business logic are
seemingly tied together, such as in the case of data validation. Also, because -s are by far the simplest
style of control to construct, they allow for implementing quick and simple solutions rather than constructing complex
controls from scratch.
Properties are syntactic shorthand for accessor and mutator methods. In place of coding methods that retrieve or
modify a particular member variable, properties provide both a get and a set submethod. Support for property style
methods can be found in Visual Basic, C#, C++ (using -) and COM, to name a few. Consider the code
snippet in Listing 2.2.
Listing 2.2 Get/Set Methods
-- -
-
-
-
- -
-
The code in Listing 2.2 shows two methods whose sole purpose is to expose the underlying private data member:
. Before the notion of properties, this style of set/get methods for allowing access to data members was common
practice among Object-Oriented developers.
Without support for properties, accessing member fields requires the explicit invocation of a method. The property
syntax merely shortens the code; however, the syntax offers a more natural expression. Listing 2.3 shows the same
- class using a property instead of methods to access the name member field.
Listing 2.3 Properties
-- -
-
-
-
- -
-
The property syntax is shorthand notation for calling the accessor/mutator method. In fact, line 12 of Listing 2.3 is
represented as - in Intermediate Language and is the actual call made to the -
object.
Controls have several properties already defined by the base class. These properties include , ,
, , and a host of others. Table 2.1 shows some of the common properties of the base
class.
Table 2.1. Control Properties
Property
Description
Text to be displayed in the control.
Foreground color of text and graphics in the control.
Background color of the control.
Get/set edge of parent to dock the control to.
Font to use for text within the control.
Note
At last count, the base class provides 59 properties of its own; for a complete listing of Control
properties, see the MSDN help topic for Control Members.
When creating new controls, you will also be defining additional properties that relate to the expected functionality of
the control. The control developed in this chapter adds a property for the to be drawn on the
button. Properties are in many respects the attributes of a control. As such, when deciding what properties a control
should have, try to think in terms of "What are the attributes of this control?" With respect to the control
to be developed, the property defines the associated icon image of the control.
Events are one of the more interesting additions to VB .NET and are an integral part of the C# language and the .NET
platform. The mechanism provides a method of communication or notification between objects. Consider the
Windows Forms control. When a mouse-click event occurs, the button notifies any subscriber that a
event has occurred. The subscriber can then perform processing based on the event received from the publisher of the
event.
-
The event paradigm is an implementation of the - pattern. In the - model,
also known as --, the notifies registered -- when the state of the has
changed. Figure 2.5 shows a UML, or Unified Modeling Language, diagram of the - pattern classes.
Figure 2.5. The - pattern.
The - is a very useful design pattern and can be easily implemented in both C# and VB .NET. In
addition, it is often helpful to understand the fundamental concepts when dealing with a high- leve l abstraction such as
events. The event model used in .NET reduces the dependency on having known methods for a subject and observer
as in the - pattern. Rather than requiring an - to provide a known method such as
, the .NET event model allows for loosely coupled events and event handlers.
To provide a loosely coupled event system, .NET introduces the concept of delegates. For C/C++ programmers,
delegates are like function pointers on steroids. In VB parlance, delegates are similar to passing the address of a
function to another function using the -- operator. For an object to subscribe to an event, it need only
implement a member method with the required delegate signature, the signature being the return type and
parameters for the method.
As an example, consider the method on a control. To handle the event, a method must exist that
accepts two parameters: - and - . As long as the member method conforms to this
method signature, that method can be used to attach to the published event. Events and delegates go hand in hand
as delegates are used by events to invoke the specified method.
To expose an , a class needs to define the event within the class declaration. Listing 2.4 shows an example of
defining an event.
Listing 2.4 Declaring a Public Event
--
-
-
The member of the class defines an event with a delegate type of - and,
as recommended by the coding standards, implements a method to handle
dispatching the event. The reason for the method is so that derived classes can have a first
crack at the event and can decide whether the event should be propagated to all registered listeners.
The class uses the method internally to raise the event. Anytime it is necessary to invoke the
event, code within the class would merely make a call to the protected method, passing it a
new instance of the - type. In turn, the member is tested to determine whether it is null, and if
not, all observers of this event are notified.
In addition to using predefined types, you can create your own delegate signatures. Listing 2.5 provides for
a custom signature and - derived class.
Listing 2.5 Custom Delegate/Event
-- - -
-
- -
--
-
-
--
-
- -
Listing 2.5 creates a custom - derived class used by the custom delegate that is then
consumed by a client declaring a method whose signature matches that of the delegate.
Okay, time for something interesting. With the basics covered, it's time to dive in and create a simple control based on
the information provided so far. The example we'll use creates a custom pushbutton that displays an icon to the le ft of
the text. In addition to providing the C# implementation, the has also been implemented in VB .NET to
show that it is possible to create the same types of controls regardless of language choice.
Choosing the base class for a custom control requires evaluating the functionality needed by the control. Because the
control doesn't scroll and is not going to host other controls, the ---
base class provides all the required functionality needed to serve as a starting point from which to build. Figure 2.6
shows the being used in an application.
Figure 2.6. The .
2.
3.
To create the , start by creating a new Class Library project in VS .NET, as shown in Figure 2.7, with the
name .
Figure 2.7. The project.
Note
The reason for creating a Class Library instead of a Windows Control Library is that a Windows Control
Library assumes you are going to build a -. As such, unnecessary references and project files
are created.
As always, VS .NET provides a default --- file and opens it within the editor. You can delete this file, or
rename it to - as you chose. Next, add references to - and
--- to the project. Right-clicking the project name and choosing the Add Reference menu
option can be used to add the references.
With the project workspace set up and the necessary references in place, all that remains is to copy the source from
Listing 2.6 for C# or Listing 2.7 for VB .NET, depending on the project language type you chose.
Listing 2.6 - C# Source
- - - --
-
- -
- ---
- -
- -
-
- -
--
-- ---
---
-- -
---
-
- -
--
-- -
---
-
-
-
-
-
-
-
- -
-
-
-
-
- --
--
-
-
---
-
--
-
- --
--- --
-
--
---
-
-
-
--
--
- -
- -
- -
---
-----
-
-
-
-
-- -
- -
- -
- --
- -
-
-
--
Even a small control can quickly reach a couple hundred lines of code. The C# version of the control,
from Listing 2.6, is designed to mimic the standard Windows button in terms of look and feel while also providing the
capability to associate an icon to be drawn on the button.
The drawing code for the button is broken down into three tasks. The first task is to draw the button itself. The
method on line 176 handles drawing the basic button control. After the button has been rendered, the
next step is to draw the associated icon if there is one. The method on line 214 handles this task.
Rather than simply drawing the icon image as is, the method scales the icon as necessary to fit within the
button. The final width of the renderend icon must also be tracked because the text for the button will need this
information to align itself properly. The final basic drawing step is to render the button text using the
method on line 188.
The method determines the alignment for the text depending on factors such as the icon draw width and
the current state of the button. If the button is in a pushed state, the text is offset by one pixel. This offsetting makes
it appear as if the button is being pushed down and gives a sense of movement to the user.
The next major area of the is dealing with events. The most common events deal with the mouse. The
overrides both the - and the - events of the control base class. The mouse
events are used to change the state of the button from normal to pushed and back to normal, depending on the
actions of the mouse. Other events include the paint and size events, along with responding to the
event.
Listing 2.7 is the VB .NET equivalent of the C# source found in Listing 2.6. In every way, these two code listings
provide for the same functionality.
Listing 2.7 VB .NET Source
- -- - -
- -
--
--
- ---
- -
- -
- ---
- -
--- -
-- -
- - -----
--
---
--
- - -----
--- --
--- -
--
- ----
- - --
- - --
- -- - --
- - - --
- --
--
--
- --
- --
----- -
-
- -
- --
- - -
---
-- - --
- --
- -
--
-
-
The code for both C# and VB .NET is remarkably similar in structure and syntax. In fact, all the topics covered in this
book can be implemented in C# or VB .NET with ease. The .NET base class libraries have finally leveled the playing
field such that you may use the language of your choice to accomplish the task at hand.
Most of the code for the is fairly simple in nature and makes use of GDI+, Graphics Device Interface, to
draw the associated icon for the button. The topic of GDI+ is covered in Chapter 4, "GDI+." In addition, the
uses the class to draw the button itself. Controls have two basic parts: presentation and
logic.
The logic of a control depends on the purpose it's supposed to serve, and as such, developing two complete controls,
both presented in this book, will help give you an understanding on that front. The presentation of a control consists
of runtime and design-time behavior and the control's look and feel. Chapter 4, "GDI+," covers the use of GDI+ for
dealing with the drawing aspects of a control, and the remaining chapters cover VS .NET support for control
development.
-
Testing the is as simple as creating a new Windows Forms project and customizing the Toolbox to
display the . Create a new Windows Forms project and then right-click on the Toolbox to bring up the
context menu shown in Figure 2.8, and select Customize Toolbox. This brings up the Customize Toolbox dialog, which
you can use to browse for the compiled DLL created for the (see Figure 2.9).
Figure 2.8. The Toolbox context menu.
Click OK and a Toolbox item for the appears at the bottom of the Windows Forms tab. As with any other
controls, select the and drag it onto the form. With the selected, use the property browser
to assign an icon to the button. Notice that a dialog appears and when an icon file is selected, the property
browser is updated and the control renders the icon. How does this happen? Well, this is the subject of the next
chapter.
By now you should realize that there is nothing difficult about developing custom controls. All that is really necessary
is to be armed with the information about control base classes and a desire to create your own custom control. This
chapter covered the basics of control classes and what it takes to get up and running with building controls. The next
chapter deals with the design-time of custom controls, whereas this chapter focused on the runtime behavior. To build
professional-quality controls, equal effort must be spent on runtime and design-time. And now, on to the design-time
experience.
DesignerAttribute
A control designer defines the behavior and UI presentation of a control during design-time. In VS .NET, forms can be
created using the Forms Designer. This Forms Designer allows controls, such as buttons, menus, and toolbars, to be
drawn on the form rather than being created pragmatically. Each control has an associated designer that defines the
behavior of the control during the visual design-time process of building a Windows Forms application.
During design-time, a control's properties can be manipulated using the Property Grid. The control should appear very
similar to the runtime look while being designed, with a few exceptions. These exceptions include the handling of a
control's - property and any designer clues such as the placement grid. The placement grid is the series of
dots used for aligning controls during design-time that are not shown at runtime. In general, however, a control's
appearance during design-time should give an accurate representation of the control's appearance during runtime.
Note
Certainly, setting the control's - property to - during design-time should not make the control
invisible. If this were the case, there would be no way to select the control and continue to visually design
it.
Designers are an important part of the equation in developing custom controls. By providing a rich design-time
experience, application developers can visually build applications using the custom control you've created. The UI
presentation is not a designer's sole responsibility; designers must also provide for proper serialization of the code
generated for the control, in order for the control to work properly at runtime. The code for the construction of the
control, along with the necessary property settings, is serialized within the method of the
form hosting the control. This is the reason for the all-too-familiar comment on the method stating, "This code should
not be modified as the designer will OVER WRITE this method when serializing the form's design state."
- - -Just as .NET provides base classes for developing controls, there also exists a set of base classes for implementing
designers. In Chapter 5, "Advanced Control Development," the designer base classes are covered in more detail; for
now, the - base class is the focus.
The - base class provides the bare-bones functionality for designing a control. Figure 3.1 shows a
UML diagram of the - inheritance chain and supported interfaces. UML, or Unified Modeling
Language, diagrams are helpful tools for visualizing the various components and classes of any software project.
Figure 3.1. The - hierarchy.
-
To specify the designer for a control, the --- is used to
decorate the control class. The word decorate is used to denote the fact that the - provides extra
information about the control class. The control class itself does not use the specified designer class; however,
VS .NET uses this information to locate and create the specified designer. In the case of the , the
- would be declared as shown here:
---
--
-- - --
The declaration for the designer assumes that the - resides in the namespace
- and uses the fully qualified name as the argument for the -. In C# it's
not necessary to include the part when declaring and using an attribute; the class -
can be referenced as -.
-
It's time to create a simple designer for the control. The designer will be built in two stages. The first
stage of the designer will filter properties of the control to remove the and properties.
The next stage of development will introduce the concept of verbs; verbs are actions that can be associated with a
control.
As with any project, the first step involves setting up the development environment. After the VS .NET Solution is
created, the process of creating and testing the - can begin.
Before we venture into designer development, now would be a good time to set up a VS .NET Solution that will be
used throughout the remainder of the book. In VS .NET a Solution is used to contain one or more related projects. For
those of you familiar with Visual Studio 6, a Solution is orthogonal to a workspace.
Start by creating a new C# class library with the name . This will create a new VS .NET Solution, and
the output when compiling the Solution will be . In addition, the default namespace will also be
.
With the Solution in place, create two folders:
l
The new Solution should look similar to what's shown in Figure 3.2.
Figure 3.2. The Solution.
As with any .NET project, the familiar - folder and the --- source file are automatically
created. The folders within the Solution allow for a convenient way to organize code within the project. In addition,
any new classes that are created within the folders will have the folder name added to the default namespace.
The - folder will need to contain the - file that was created in the preceding chapter. Right-
click the - folder, select Add Existing Item from the Add menu, and locate the - source file. It
is important to note that this operation will copy the source file to the new destination rather than referencing it. This
means that there will be two copies of the source and changes to the new source will not be reflected in the original
source. Open the - source file and change the namespace to -.
During development of a new custom control, it is sometimes necessary to remove any unwanted or unneeded
properties inherited from the base class from which the new custom control derives. The process of adding or
removing properties and events is known as filtering. The reason behind filtering, in this case filtering properties, is to
alter the available options during the design of the control rather than to provide unnecessary or unused
properties/events.
The first designer will be used to remove or filter out two properties from the : and
. These properties are inherited from the base class and serve no purpose for the
control because neither of these properties has any effect on the control.
The capability to filter properties, events, and attributes comes from implementing the - interface.
Table 3.1 lists the - interface methods.
Table 3.1. The - Interface Methods
Method
----
Description
Allows a designer to change or remove attributes.
Allows a designer to change or remove events.
Allows a designer to change or remove properties.
Allows a designer to add attributes.
Allows a designer to add events.
Allows a designer to add properties.
Advanced uses of the - interface are covered in Chapter 5, "Advanced Control Development."
As the first venture into developing a designer, the first pass of the designer will remove the unused
properties and . Currently, the provides both of these properties as they
are implemented by the base class. The default properties are supplied when the control is created and can
be seen in the property grid when the control is selected on the form (see Figure 3.3).
Figure 3.3. The default properties.
Note
The property has the value of . This means that currently there is no image
associated with this property. One of the responsibilities of a - class is to provide such feedback to
the developer and to the property grid.
Notice the and properties displayed in Figure 3.3. To remove these properties, the
- class will implement the method -- and remove the unwanted
properties from the properties collection. Because the - base class implements the
- interface, the - class needs to override the implementation of the
-- method. Listing 3.1 contains the C# source for the -.
Listing 3.1 Designer Stage One
- - - --
-
-
-
-
-
-
-
-
--
--
-
- -
-
-
-
-- - -----
-
- - - -
-
-
-- -
-
-
----
The class should now look similar to what's shown in Listing 3.2.
Listing 3.2 Updated Attributes for the IconButton
--
-- --
-- ---
Rebuild the Solution to produce the new control library. To test the results of the designer, start a
new Windows Forms Solution and add the to the form. Notice that the and
properties are no longer displayed in the property grid, as shown in Figure 3.4.
Figure 3.4. The first phase of the -.
- Verbs are best described as actions that can be applied to the control being designed. Verbs for a control are linked to
an event handler and are added to the context menu for the control, as well as the property window. The best way to
understand the role of verbs is to implement them, and that's exactly what the second phase of the
- is about.
To support adding verbs for a control, the designer needs to implement the - property. The - property
returns a -- of -- that the control designer supports. The
- will be extended to provide verbs for changing the property of the control to ,
, or . The event handler for custom verbs uses the following signature:
- -
Listing 3.3 shows the updated - with the - property implemented.
Listing 3.3 Designer Stage Two
- - - --
-
-
-
-
-
-
-
-
--
--
-
- -
-
-
-
-- - -----
- -
- - -
- - -
- -
-
- -
-
- -
- - - -
-- -
- -
- -
- -
- -
- -
- -
Line 29 of Listing 3.3 implements the - property. Each verb defines a text string for the menu and an
to be invoked when the menu handler is selected. Figure 3.5 shows the context menu and property
grid of the using the revised - class.
Figure 3.5. Verbs support.
VS .NET handles the context menu and property grid support for displaying the supported verbs or commands th at the
current control designer supports. When one of the supported verbs is selected, the designated is
invoked so that the verb can be executed. In the case of the -, the property of the
being designed is updated accordingly.
Designer verbs also allow for user feedback such as providing a check mark for the current selected. To
provide this feedback, the property of the - item needs to be set. The current implementation
of the - merely creates the supported designer verbs within the context of the - property
rather than as an implementation member. Listing 3.4 updates the - to support providing the
-- as members and makes use of the property.
Listing 3.4 The Updated - Class
- - - --
-
-
-
-
-
-
-
-
--
--
-
- -
-
-
-
-- - -----
--
-- -
- - -
-- -
-
-- -
-
-- -
-
- -
- --
- - - -
-- -
- -
- -
- -
- -
- -
- -
- --
--
As a result of the updated -, the custom verbs on the context menu will show a check mark
next to the currently selected foreground color corresponding to the selected verb (see Figure 3.6).
Figure 3.6. Using the designer verb property.
With the addition of verbs, the - class is beginning to take shape. In Chapter 5, "Advanced
Control Development," the designer will be extended to provide even more features. By now you should have the
basic idea of what is involved in developing a designer.
Before this chapter ends, I'd like to add a Toolbox bitmap for the . A Toolbox bitmap is the 16x16 image
that appears in the Toolbox tab for the . Adding a bitmap for a control is a simple task, to say the least.
All that needs to be done is to add a bitmap with the same name as the control to the project. Figure 3.7 shows the
Solution with the file added in the - folder of the Solution.
Figure 3.7. The file.
The bitmap must reside in the same namespace as the control and be compiled as an embedded resource. The
Toolbox uses this as the default search for locating the associated Toolbox image to associate with the control. In the
case of the , this means adding the bitmap to the - folder of the Solution. To enable the bitmap
as an embedded resource, right-click the bitmap file and select the Properties menu item. From the Properties page,
set the Build Action to Embedded Resource, as shown in Figure 3.8.
Figure 3.8. properties.
The bitmap that will act as the Toolbox bitmap for the control must also have the following properties:
l
Height of 16
Width of 16
16 colors
With these conditions satisfied, the new bitmap image will appear next to the name of the control when
loaded in the Toolbox. Figure 3.9 shows the Toolbox with the bitmap enabled.
Figure 3.9. The Toolbox bitmap.
Again, the Toolbox will search the assembly manifest for an embedded bitmap with the same qualified name as the
control. If found, the bitmap will be used as a visual representation of the control within the Toolbox.
The intent of this chapter was to produce a simple designer for the that was developed in the preceding
chapter. In addition, I wanted to point out that there is no voodoo or black art to developing custom controls and the
designers for those controls. All that is needed is an understanding of what is expected of a designer and the support
provided by VS .NET. In addition, all the C# code presented in this chapter can easily be directly ported to VB .NET or
any other .NET language. By now you should have a sense of the basic requirements for developing controls and their
designers.
IN THIS CHAPTER
l
Brushes
Pens
Fonts
Before this chapter dives into the details of control development, a brief tour of GDI+ is in order. GDI stands for
Graphics Device Interface, an abstraction for drawing to a graphics- capable device such as the screen or printer. GDI+
is the next stage of evolution in the development of the Win32 GDI API.
One of the major tasks associated with Windows development is the task of rendering or painting on the screen or
printer. Humans are visual creatures, and as such the capability to provide information and feedback through visual
means is important. Consider data within a spreadsheet. Although the actual figures and formulas detail the exact
data and necessary information, providing different styles of graphs of the data often helps us visualize the
information. This presentation of data is just as important when developing various user interface elements such as
custom buttons, menus, and toolbars.
GDI+ represents the latest Graphics Device Interface API provided by the .NET framework. GDI+ is a major
improvement over the somewhat archaic Win32 GDI calls of yesteryear, and it provides a simple object model for a
graphics interface.
One of the major portions of control development is the user interface it presents to the user. In fact, a lot of tim e and
effort will be spent on the painting code necessary to create just the right look and feel you're after for the control.
The goal of this chapter is to explore some of the objects associated with GDI programming. Issues such as different
styles of brushes, pens, fonts, and image formats are covered because they are likely to be a common theme when
developing custom controls and Windows applications in general.
- -At the root of GDI+ is the -- class. This class encompasses methods for drawing text,
icons, images, rectangles, ellipses, curves, and everything in between. The - class is the heart and sole of
GDI+, and you'd do well to familiarize yourself with the services it offers, as well as its limitations.
Although the - class provides the means to render basic shapes and images to a window, it requires a
supporting cast. This supporting cast comprises brushes, pens, fonts, and even images. All of these objects work
together to produce the final result displayed on the screen or printer.
An important note about GDI+ is that the programming model is a static model, whereas previous versions of GDI
were stateful models. This means that for GDI+ to work, it requires resources such as brushes and pens to be
managed by the developer rather than the GDI API. For seasoned Win32 GDI developers, this new model requires
some getting used to; however, the new GDI+ offers increased performance and ease of use.
-Brushes are used to fill basic geometric shapes such as rectangles, ellipses, and polygons. The
-- class is an abstract base class and cannot be instantiated directly. The - class serves
as a base class for various brush styles. Figure 4.1 shows four of the common brush styles in action.
Figure 4.1. Basic brushes.
-
A - is just as its name implies: it creates a solid color that fills a graphics primitive such as a triangle,
circle, or rectangle. All graphics primitives are constructed from the most basic primitive, the point. Points can be used
to construct lines, circles, and polygons. From these basic shapes, more complex shapes can be constructed. Creating
a - is a simple matter of knowing the color of the brush to create and passing the color to the constructor
of the - class. In the case of Figure 4.1, the - was created with the following code:
- -- - -
The - object is created by specifying the color of the brush. The color can be specified either by using a
predefined color found in the class or by using the method of the class.
With the brush created, a call to the method of the graphics object creates a solid blue rectangle.
Listing 4.1 shows the code snippet from the Brush Demo used to create and render the -.
Listing 4.1 Creating a -
-
- -- - -
- -- -
----
Notice the call to the -- method of the -- object. GDI+ objects tend to be resource intensive, and
it's recommended that the -- method should be invoked to free the associated resources when the GDI+ object
is no longer needed.
-
A - defines a brush with a foreground and background color, along with a enum or pattern
for the brush. The - displayed in Figure 4.2 uses a foreground color of , a background color of
, and the -- hatch style. At last count 56 hatch styles were defined within the enum. If you're
interested in seeing all of them, use the code snippet in Listing 4.2 and change the accordingly. A
complete listing of the various hatch styles can be found by searching the online help for
- enum value.
Figure 4.2. The -.
-
- - - -- -
-
- - -
---
Providing a sample of every style of available would consume far too much space, but you should
experiment with different - to see the result. The following code snippet shows how to create a
hatch style with a blue foreground color and a gray background:
- - -
-
A linear gradient is a transition from one color to another. The - class defines a starting and
ending color and creates a smooth transition from one color to another. In addition, it is also possible to define an
angle for the transition. Figure 4.3 shows a gradient from to with a 45-degree slope for the transition.
Figure 4.3. The -.
As with the other brushes, creating a gradient style brush is a simple matter of providing the desired parameters
during construction. Listing 4.3 shows the code used to create the image shown in Figure 4.3.
-
-
- - -
- -
--
The last parameter for the - constructor is the angle or rotation for the transition. In C# it is
necessary to post-fix a float constant with the lowercase letter f in order for proper type safety. Such post-fixing of
constants is found in languages such as C, C++, and even Visual Basic.
Also, the angle parameter is based on a left- handed coordinate system, meaning that the rotation is counterclockwise.
To produce a downward slope, you can use a negative value or provide angle values greater than 90 degrees. Only
extermination will help to make this clear.
GDI+ uses both 2D and 3D graphics concepts to accomplish rotation, scaling, and translation. Before panic sets in, be
aware that the most 2D or 3D graphics fundamentals require nothing more than basic geometry, trigonometry, and
matrix manipulation. Books solely devoted to 2D and 3D graphics are widely available in just about every bookstore.
-
The last brush covered is the -. The - allows for an image to be used as a repeating
pattern for the brush. The image can be any image format such as a bitmap, jpeg, or gif, to name a few. Figure 4.4
shows the - using a jpeg as the repeating image.
Figure 4.4. The -.
As with all the brushes presented so far, no real skill is needed to create the -. Listing 4.4 shows the
code to create the - shown in Figure 4.4.
Listing 4.4 Creating a -
-
- -- --
- - --
-
----
--
Listing 4.4 starts by loading a jpg image from a disk and then uses the newly created image to create a
-. This - is then used to fill a rectangle much in the same manner as a solid brush or any
other GDI+ brush.
This covers the basics of brushes. Listing 4.5 shows the complete source for the Brush Demo from Figure 4.1.
Listing 4.5 Brush Demo Source
- -
- -
- - - - -
- --
- -
- ---
- -
- -
-- ---
- -
- - -
- -
- -
- -
-
----
- -- - -
- -- -
----
-- -
- - -
- -
- - -
---
-- -
- - -
- -
--
-- -
- -- --
- - --
- -
----
--
-
-- -
-
-- --
--
---
--- --
- -
-
- -
- -
-
-- -
- ---
- -
-
- -
-
-
Listing 4.5 reprents a fairly typical Windows Forms application as developed using the IDE. Note the comments on
lines 91 and 92 which specifiy that the code within the method should not be modified. This
is due to the fact that the IDE and the Forms Designer use this method to persist code for creating controls placed on
the form during design-time. This interaction between the designer and generated code is covered in Chapter 3,
"Designer Basics."
All the painting code takes place within the method. Most of the code is derived from the
previous code listings for creating and using GDI+ brushes. As with any new programming endeavor, you should
modify the sample and experiment with the various brush properties and styles.
Pens are used for all drawing methods of the - object with the exception of the method. A pen
can be used to draw solid lines, dash patterns, and even brush-style lines such as gradient or hatch. To create brushstyle pens, use the property constructor for a new pen, or assign the existing brush to the pen's - property.
Figure 4.5 shows the interface for the Pen Demo. The property grid allows for changing the properties of the current
pen to see the result.
Figure 4.5. The Pen Demo.
Unfortunately, pens are not very exciting and a lengthy explanation of their use is not necessary. However, the Pen
Demo is interesting with regard to the use of the property grid. To attach the property grid to the current pen, the Pen
Demo application creates a small class named -. The - class provides some of the
properties of a pen and is used to hook up the property grid to the underlying pen. The best part of this is the minimal
amount of effort needed to use the property grid. Listing 4.6 shows the source for the Pen Demo application.
Listing 4.6 Pen Demo Source
-
-
-
-
-
-
-
-
--
-
---
-
-
-- -
-
-- -
-- -
--
--
--
-- ---
-
-
---
- - -
- -
-
-
- -
-- -
-- --
--
---
--- --
- -
- -
- -
- ---
--
--
---
---
- ---
-- -
- ---
- -
- ---
-
---
-- -
- ---
- -
-- ---
---
As stated earlier, no work is necessary to make use of the property grid; just assign the property
to the object you want the property grid to work with. The demo allows you to see the result of changing the
properties of the underlying object and how those changes affect the output generated by using the pen.
One item that requires explanation is the event provided by the - class. This
event is used to notify the parent form that the properties of the pen have been changed and the screen needs to be
updated to reflect these changes. Events are an intrinsic part of .NET, C#, and Windows/Web Forms development.
When a property of the - class has been modified, the protected method is
invoked. Having a method for events is recommended by the current .NET coding standards
available on the MSDN Web site. This allows for derived classes to have the first crack at the event and determine
whether the event needs to be propagated to all event listeners.
The overall intent of the demo is to allow for a visual reference of the various effects the pen properties have when
the pen is used to draw to the screen or printer. In addition, the is a nice addition to the standard set
of controls provided with Visual Studio; understanding how it works will soon become necessary as you begin
developing custom controls. This is the same used by the IDE during design-time, and custom
controls are expected to work properly with the grid to provide manipulation of various design-time properties.
Interaction with the property grid is covered from time to time as it relates to the current project.
Working with various fonts has never been a trivial task. Each font has specific characteristics such as its height,
width, ascent, and decent. Fortunately, working with the class provided by the base class libraries makes
working with fonts a painless task.
Fonts, like brushes and pens, can be solid or textured and can be any size or color. Included with the .NET Framework
SDK is a wonderful font example that includes textured fonts, drop shadows, internalization, and font rotation. For the
sake of completeness, the Font Demo application will be shown to illustrate the following topics:
l
Drop shadows
Textured fonts
Background images
Graphics transformations
The Font Demo will be in the style of the Pen Demo presented earlier in this chapter, enabling the property grid to be
used to manipulate the various properties and options. Figure 4.6 shows the Font Demo application with the source
given in Listing 4.7.
Figure 4.6. The Font Demo.
-
-
-
-
-
-
-
-
-
-
--
-
---
-
-- -
-
-
-
-
-
-
--
- -
- - -
-
- - -
-
--
--
- -
-
- -
-
-
- -
-
- -
-
-
-
-
-
-
- -
- - -
-
--
-- -
- -
-
- -
-- -
- -
-
- -
-- -
- -
-
- -
-
-
-- ---
---
---
-
- -
- - -
- --
- -
-
- -
-- -
- -
-
-
- -
- --
- -- -
-
-- - -
---
-- -
-- --
- --
- -
--
- -
- -
--
-- -
-- -
-- --
--
---
--- --
- -
- -
- -
-- ---
- ---
--
-- -
-- -
-- -
--
-- -
--
--- --
--
---
---
- ---
-- -
- ---
- -
- ---
-
---
-- -
- ---
- -
-- ---
--
---
- -
----
The Font Demo application makes use of the -- attribute. This attribute is used
by the property grid; it determines whether the property will be available with the property grid. Expect to make use
of this attribute to specify properties that are not appropriate for design-time editing.
The Font Demo uses the same basic application shell as the Pen Demo. The property grid is used to manipulate the
various properties of the current font object, and those properties determine how the font will appear when rendered.
Working with image files has never been one of the easiest tasks to undertake. Acquiring proficiency with image
formats such as bitmap, jpeg, gif, tiff, and countless others requires a significant investment of time and effort.
Fortunately, the base class libraries provide support for the common image formats and allow for simple loading,
displaying, and conversion of various image formats. Table 4.1 lists the currently supported image formats.
Table 4.1. Supported Image Formats
Image Format
Bmp
Emf
Exif
Gif
Icon
Jpeg
MemoryBmp
Png
Tiff
Wmf
Description
Bitmap image format
EnhancedWindows metafile image format
Exchangeable image format
Graphics interchange format
Windows icon file format
Joint Photographic Experts Group (Jpeg) image format
Memory bitmap image format
WC3 Portable Network Graphics image format
Tag image file format
Windows metafile image format
Unless you have plans to create a custom imaging application such as PaintShop Pro or Adobe PhotoShop, the
class should serve the basic needs of loading and displaying images. Figure 4.7 shows the UI for the ImageStudio
Demo application.
Figure 4.7. ImageStudio.
The ImageStudio Demo allows for viewing various image formats and for saving loaded images into other supported
formats. The following listings describe the ImageStudio Demo source files.
ImageStudio will be developed in a pseudo-MVC-style framework. MVC refers to Model-View-Control and is a popular
framework for developing large applications. Visual C++ developers will see this as similar to the Doc/View
architecture employed by MFC. The basic premise of these two framework styles, MVC and MFC, is to separate the
data, in this case the image, from the onscreen representation and the parent form or controller that handles user
input. In addition, ImageStudio is an MDI application allowing for multiple images to be loaded and viewed at the
same time. Listing 4.8 presents the or class used by ImageStudio.
Listing 4.8 Class
- -
- -
- -
-
--
-
-
--- --
-
-
--- --
-
-
-
The class handles the loading and saving of images. Notice on line 22 of Listing 4.8 that the
static method requires only the filename of the image to load. The method uses the extension of
the file to determine the format of the file and as such does not require that the format be specified. The
method can also use the file extension to determine the format, or override to save to a new format based on the
argument.
Listing 4.9 is the class, which is derived from and supports scrolling of the image. As its name
suggests, the class represents the View component of MVC or Doc/View MFC framework-style
development.
Listing 4.9 Class
- -
- ---
- -
- -
-
-- ---
-
-
-
-
-
-
-
-
The sole purpose of the ImageView class is to render the loaded image and support scrolling when the window is
smaller than the image size.
The ImageStudio Demo is an MDI application. Listing 4.10 contains the source for the class. The
ties together a single instance of an and one or more classes. In the
ImageStudio Demo application, only a single class is hosted within the .
Listing 4.10 MDI Child Form
-
-
-
-
-
-
-
--
-
---
-
-
-
-- ---
-
-
-
- -
--
-- -
-- --
--
---
--- --
- -
- -
- -
-- -
- -
The ties a specific instance of an class to the class that is
responsible for displaying the loaded image. The goal of implementing all these classes, ,
, and , is to focus the responsibilities of the application where they make sense. Such a
design might be somewhat overkill for this demo; however, understanding such design patterns and their usage is
important when creating large-scale applications and even complex custom controls. The final component of the
application is the window or the Controller from the MVC architecture. The supporting cast is in place and
all that remains is a host to oversee the user interaction and to dispatch responsibilities to the various components of
the application.
Listing 4.11, the final listing for the ImageStudio Demo, is the MDI parent window. The
provides handling of menu items and manages the MDI children, class, for the demo application.
Listing 4.11 MDI Parent Form
- -
-
-
-
-
-
-
-
--
-
---
-
-
-- ---
---
---
--- -
--- -
---
---
---
---
---
---
--- -
-
-
-
- -
---
---
-
- - -
- -
-
-
-
-
-
-- --
--
---
--- --
- -
-
- -
- -
-
- ---
- ---
- ---
-- ---
- ---
- ---
- ---
-- ---
- ---
- ---
-- ---
- ---
- ---
--
-
-
-
--
- ---
--
--
-
-
--
--
-- -
--
---
--
--
--
--
--
-- -
--
---
--
-- -
- -
-- -
--
- -
---
- --
- - --
--
- - --
- -
-
-
- --
The class is certainly the largest piece of the application. However, it's important to notice that the vast
majority of the code revoles around setting up and managing the user interface such as menus and the File Open
dialog. The relies on each of the previous developed classes to provide the true functionality of the
application so that the can handle the tasks associated with user interaction. The vast majority of the
code within the is acutally created during desgin-time. The only code added was to the event handlers for
the various menus. As software projects grow in size and complexity, it is important to divide the responsibility
amoung various smaller components as was done within the ImageStudio Demo.
Note
The subject of design patterns is an important area within software development. I suggest Design
Patterns: Elements of Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John
Vlissides.
-Although not part of GDI+, the class offers several methods for drawing standard UI elements. In
fact, the developed in the preceding two chapters used the class to draw the button
itself. It is interesting to note that although this class exists, the base class library does not use it. The standard
class, found in the --- namespace, implements its own drawing logic. The class itself is
worth mentioning because it does provide a mechanism for drawing standard controls such as buttons, check boxes,
and even focus rectangles. Also because IL (Intermediate Language) is fairly readable, loading up
--- into and peeking into the methods helps give some
insight into GDI+ and basic 2D drawings.
The capability to draw the selected icon in a disabled state would enhance the . To accomplish this
disabled image, as shown in Figure 4.8, the method can be used.
Figure 4.8. The image disabled and enabled.
The will render the icon in a disabled state, as shown in Figure 4.8, when the property is set to
-. Listing 4.12 shows the changes to the method to implement rendering the
in a disabled state.
Listing 4.12 Update
----- -
- -
-
- -
- -
-
The changes to the method are found between lines 21 and 28 of Listing 4.12 . To use the
- method, the icon must be first converted to a bitmap. Then a thumbnail, or a
reduced image, needs to be created based on the size of the calculated. After this is done, it's a simple
matter of invoking the method to render the grayscale image of the icon.
This concludes the brief tour of GDI+. The .NET base class libraries provide a rich set of classes and APIs for dealing
with the graphical interface, fonts, and images. In addition, built-in support for translation, scaling, and rotation on
GDI calls makes tasks such as rotated fonts a snap. For more in-depth information regarding GDI+, I recommend C#
and the .NET Framework: The C# Perspective , by Robert Powell and Richard Weeks, also from Sams Publishing.
IN THIS CHAPTER
l
Designer Services
Designer Attributes
Design-Time UI Clues
Attributes
Properties
The process of building custom controls has been greatly simplified due to the richness of the base class libraries
available in .NET. Already you possess the knowledge and skill necessary to create a wide assortment of controls. All
that remains is learning about the services provided by the base class libraries and the VS .NET support for custom
control development. In this chapter, all control and designer base classes, interfaces, events, and attributes are
discussed at various levels of detail.
In this chapter, more details of the base class libraries, designers, and services are explored. Other than learning what
services are available for building controls and their associated designers, an understanding of where to begin or what
base class should be used is all that needs to be refined.
.NET also introduces the concept of attributes. Attributes are used to provide additional information about classes,
properties, methods, and parameters. Control development makes use of various attributes, covered in this chapter,
to associate controls with designers and properties with editors. Attributes provide an extremely flexible mechanism
for loosely coupling components and promoting reusability.
- --The Windows Forms library, like custom controls, uses various base classes for both controls and designers. Often it's
helpful to have a point of reference in order to proceed with a new project. A hierarchy of the class structure provides
an excellent reference advantage. Figure 5.1 shows the class hierarchy starting with the
--- class.
Figure 5.1. The control class hierarchy.
Regardless of the control you intend to build, there exists a base class from which to begin. Notice that a is in
fact derived from a control base class, the class to be precise. Each level in the hierarchy
provides slightly more features and modifies derived behavior to produce the desired result.
Picking the proper base class requires that the expected result and functionality of a new control is properly defined.
As a guide, use the following questions to help decide what base class to derive from:
l
Is it a simple control?
Derive from the base class
Control base classes are not the only classes that can serve as the starting point for the development of a ne w control.
Consider the control that inherits from the control class. In turn, the control derives from the
base class. When developing a new control, give proper consideration to the base class from
which to start. In addition, the same consideration should be given for the control's associated designer.
- - --Like controls, designers have several base classes to choose from. Each designer base class provides certain
functionality that builds on the support provided by the inherited base designer class. At the root of the designer
hierarchy is the - class.
Not every control in .NET is an actual control. Rather, certain items are components that derive from
-. A component has no UI of its own and is often used to hold information for
part of its containing control. The Windows Forms and - are two such components. When a
component is placed on a object, an icon for the component appears within the Component Tray of VS .NET, as
shown in Figure 5.2.
Figure 5.2. The VS .NET Component Tray.
In Chapter 7, "OutlookBarTab Component," the class serves as the base class for the
that is part of the control. -derived classes use the - base class to
provide the necessary designer implementation. The - class implements --,
-, and the - interfaces. As such, component designers are capable of filtering properties,
events, and attributes and have the capability to expose custom verbs. In addition, components designers can shadow
properties and interact with the root document designer, which is the topmost designer. The Form designer is a
document designer.
- Designer Services can be divided into three major sections: component designers, Windows Forms Designers, and drawing d esig
The component designers provide the core functionality of the .NET framework designer architecture, whereas the Window s For
Designers extend the design- time support for Windows Forms. The list of classes, interfaces, delegates, and enumeratio ns for thes
three designer sections is rather extensive. They are discussed in the following sections.
-- -
The -- namespace contains the core .NET framework designer archite cture classes and interfaces
that can be used to define designers and custom editors for components at design-time. Tables 5.1 through 5.4 list the classe
interfaces, delegates, and enumerations, respectively, along with a brief description of each.
Table 5.1. Classes
Class
--
Description
- data for the
- event.
at design-time.
Displays the content of byte arrays in
hexadecimal, ANSI, and Unicode format
provides for basic
collection editing.
Modal dialog for editing the collection
using a .
A unique command identifier consisting
of a numeric command ID and a GUID
menu group identifier.
- data for the
event.
--
--
----
-
-
--
--
Interface
-
-
-
--
-
Description
Interface to add and remove component event handlers
and provides methods for raising
events.
The base designer interface for building custom component
designers.
Event notifications for the addition and removal of
designers, selection changes, and provides the capability to
add designers.
Allows for and - filtering of events, properties, and
attributes for a component.
Interface for mapping designer transactions and
components.
Interface to access the designer options found on the
Options submenu under the Tools menu.
Dictionary interface for a designer to store user-defined
data.
Exposes events as - objects.
Provides enumeration of extender providers. A
component implements the
interface.
Interface to add and remove extender providers at design
time.
Used to provide the IDE with context help information
about the current task.
Used for identifying inherited components.
Interface to add and remove commands and verbs to and
from the VS .NET menu.
Interface to names of and references to objects within a
designer project.
Provides access to specific resources.
-
-
Support for view technologies.
events.
Retrieves an assembly or type by name.
Table 5.3. Delegates
Delegate
Description
Handles the - event.
Handles the event.
Handles the event.
Handles the ,
, , and
events.
Handles the event.
Handles the - and
--- events.
Enumeration
Description
Used by the to indicate the display type (Auto, Hex, ANSI, or
Unicode).
---- -
Tables 5.5 through 5.7 list the classes, interfaces, and enumerations provided by the Windows Forms Designer framework.
Table 5.5. Classes
Class
Description
for filenames.
for folders.
Interface
Description
Enumeration
Description
Selection rule for a component as it relates to moving and sizing.
-- -
The last major area of designers includes the services provided under the -- namespace. As you might
expect from the name, the designers and services provided deal with UI presentation and drawing logic. In addition, interactio n
the Windows Forms ToolBox is provided within this namespace. Tables 5.8 through 5.11 show the classes, interfaces, delegates, an
enumerations, respectively, that are provided by the -- namespace.
Table 5.8. Classes
Class
Description
Property Grid.
Collection of - used by the
.
Interface
Description
Allows management of the properties list of the properties
window.
Delegate
Description
Handles the events from the
Handles the
-
event.
Table 5.11. Enumerations
Enumeration
Description
Indicates the value editing style for a (
, ).
At this point, you are likely feeling overwhelmed by the number of services available through the -
namespace and the ---- namespace. Fortunately, the use of all these services is rarely needed
even for complex custom controls. It's also important to remember that each service provided is extremely granular in scop e and as
such its use is easier to understand. One last note: a lot of the services are already provided free; that is, you d on't have to do
anything to make use of them.
Note that the we developed has not made any explicit reference to most of the available designer service s, yet th
control and its associated designer are fully functional. There are times, however, when it is necessary to use the service s availabl
During the development of the control, many of the services will be used.
- Attributesplay a large role in every facet of .NET, from XML serialization to control development. The
relies heavily on custom attributes for object properties that it uses to determine the default value, determine whether
the property is browsable, and make many other choices. Attributes are used to control the and the
code serializer, and for defining the for a property, the designer for a component, and the category
for a designer, among other things. The common case for attributes deals with interaction with the
and specifying the category, description, browsable, and type converter attributes. Figure 5.3 shows how attributes
are used to control the .
Figure 5.3. The .
The use of attributes provides a powerful and expressive manner in which to decorate a component with additional
functionality and services without having to inherit or write custom code. As a general rule, look for an attribute first,
and then write code. Chances are there is an attribute that will accomplish that task without requiring you to w rite any
code. The subject of attributes will continue to come up throughout this book. With each new attribute used, an
explanation of its use will be covered.
- Now that the dry, tedious topics have been covered, it's time to get back to the fun stuff. The goal of any control
designer is to provide a proper view of how the control will appear during runtime. However, it is also necessary for
the designer to provide a set of hints or visual clues that facilitate the design of the control. The designer itself
provides these design-time hints or UI clues by overriding the - method. Figure 5.4 shows the
various UI clues provided by the designer.
Figure 5.4. The designer UI clues.
Each designer draws a focus rectangle and the control alignment grid only if the grid is turned on. These
subtle UI clues provide important information to the developer when he is designing the control. The focus rectangle
not only shows the active tab, but also defines the client area boundary for the current . In addition, the
designer draws the control alignment grid to assist the developer in aligning child controls within the
boundary of the .
To see how simple it is to provide UI clues, note how the - class is extended to provide an
annoying design-time adornment. The designer paints the term " DESIGN-TIME" across the top of the
, as shown in Figure 5.5.
Figure 5.5. Providing UI clues.
-
- - - --
-
-
-
-
-
-
-
-
-
--
--
-
---
- ----
- -
-- - -----
-
--
-- -
- - -
-- -
-
-- -
-
-- -
-
- -
- --
- - - -
-- -
- -
- -
- -
- -
- -
- -
- --
--
- - -
-
- - -
- -
The - method found on line 84 of Listing 5.1 simply creates an opaque brush and renders the
text "DESIGN-TIME" across the top of the . Obviously, this is not the best idea for a UI clue;
however, it does illustrate the ease with which UI clues or adornments can be added to a control.
New to the .NET framework is the concept of attribute-based development. Attributes provide an extensibility
mechanism for classes, properties, methods, parameters, and return values. By applying attributes to a particular
entity, the default behavior can be altered to produce a new desired result. For a complete description of attributes,
refer to the MSDN help.
The .NET designer architecture makes heavy use of attributes to simplify common development tasks. Examples
include defining the default value for a property, assigning a category and description to properties, and indicating the
default event for a control, to name a few. No discussion of the designer architecture would be complete without a list
of attributes commonly used for designers, controls, serialization, licensing, and the . Table 5.12 lists
the attributes found in the - namespace, along with descriptions.
Table 5.13 and Table 5.14 list the attributes found in the -- and
- namespaces, respectively.
Table 5.12. - Attributes
Attribute
Description
The value for this property
originates for another source,
generally the containing control.
Used by the to
determine whether the property
should be displayed.
Defines the category for the
property or event within the
of the
.
-
-
-
--
Specifies the -
for the class.
Specifies a list can be used for data
binding.
Localization for a property.
Specifies that a property can be
combined with properties of other
objects in the property window.
Specifies that the parent property
should be notified when the
property is modified.
Specifies that the property value
should appear within a set of
parentheses. Example:
appears next to
collection-based properties such as
the - property of the
control.
Identifies the property tab(s) to
display for the class or classes.
Defines the name of a property
provided by the class that
implements the
interface.
The class is an extender
provider.
Specifies that the property is readonly within the .
Specifies that the property can be
used as an application setting.
Defines how the designer refreshes
when the property is modified.
Specifies whether the component
should be loaded in the toolbox.
Specifies that the component will
accept only other s
belonging to the specified
namespace, or type.
Attribute
Description
- Indicates the serializer for the serialization
manager to use in order to serialize objects of
this type.
-
Indicates the base serializer for the root object
designer.
Attribute
Description
Associates an image with the specified component.
Support for small and large images.
Attributes are a prominent theme in .NET development, and custom control development is no exception
The .NET designer architecture provides a RAD (Rapid Application Development) style environment for applications
development. One of the keys to the RAD environment is the capability to modify the properties of components
visually at design-time. The Windows Forms Designer provides a that allows for modifying control
properties and instantly viewing the result of those modifications.
The .NET designer base classes provide extensive support for editing various types of properties, from intrinsic types
such as strings and integers to complex objects such as fonts, collections, and images. Because it is impossible to
provide a property editor for types yet unknown, the is used to define the that
the will use to support modification of the property. Figure 5.6 shows the for the
- collection of the control.
Figure 5.6. - property .
Providing a custom for a control or component is fairly simple, thanks in part to the existing designer
architecture. The base class provides four virtual methods that can be overridden to provide a custom
for a component. Table 5.15 lists these methods along with descriptions of each method's purpose.
Table 5.15. Virtual Methods
Method
Description
Invoked by the to edit the specified
property value.
Specifies the type of editor as , , or
.
Returns if implementing method;
otherwise, returns -.
All that is required in order to associate the with a particular property is the use of the
. The specifies the type of editor to be used when editing the property at
design-time. Figure 5.7 shows the that will be built for the 's property.
Figure 5.7. for the .
The typical coding convention for style editors is to have the class contained within the editor class as a
nested class. The drawback to this is that the designer will not allow for visual development of a nested class. When
developing the , you may want to create the class as you would any other and then, when
finished testing, move the class into the editor class. This, however, creates a small issue with the resource file,
the corresponding file with the - extension. To preserve the resource information, it is necessary to include the
resource file into the project. I suggest the following steps for creating nested classes:
1.
2.
When you are finished developing the form, copy the underlying - file somewhere safe.
3.
Copy the class code into the target parent class, in this case the class.
4.
5.
Add the saved - file to the project as a resource. Adding an existing item and choosing the - file can do
this.
6.
Double-click the resource file in the Solution explorer. This invokes the resource editor.
7.
In the data sheet of the resource editor, modify the value of - entry to be the qualified name of the
dialog (see Figure 5.8). In the case of the the qualified name is
.
Figure 5.8. The resource data sheet.
8.
Locate the line of code in the method of the form where the resource manager is
referenced. It is usually the first line. Modify the qualified name so that it matches that of the -
value in the resource file. The following snippet shows the line of code to modify:
---- --
----
9.
If it seems like a complex set of steps, it is. Unfortunately, the resource file support for VS .NET is not quite up to par
with the way it should be in terms of ease of use. In an attempt to simplify the development process, certain tasks
such as resource file management have been made overly complex.
Listing 5.2 contains the source for the , along with the nested class to act as a
custom .
Listing 5.2 Source
- -
- -
- --
- --
- -
- ---
- ----
- -
-- --
--
-- --
--
- -
---
- -
- -
-- ---
---
---
---
---
--- --
---
- -
-
-
-
-
-- -
-- --
--
---
--- --
---- --
----
- ---
- ---
- ---
- ---
--- ---
- ---
--
- -
- -
- -
-
--
-
---
- -
- -
-
---
- -
- -
- -
- --
--
---
--- --
--- -
--- --
--- -
---
---
---
--- -
- -
- -
- --
-- -
- -
-- ---
---
-
---
---
- --
-- - --
-
-
- --
- -
- --
- -
The code in Listing 5.2 consists of two major components: the dialog for selecting and previewing the icon image, and
the code necessary to paint the icon image within the .
The nested is a basic -derived class that is used to display an for
selecting an icon. In addition, the creates an instance of an to allow for a simple
preview mechanism. After an icon is selected, the is closed and the class is
responsible for providing the necessary code to update the .
The class provides the implementation necessary to interact with the . The first task is to
return the type of editor that will be provided. For the , the type of is returned, as a nested
will be used. Other types are and . When the method is invoked, the
creates and displays the nested to allow for the selection of an icon image. When the value of
the icon is modified, the will invoke the method to determine whether
the editor will handle displaying some representation of the property within the .
Next, the method is invoked if the method returns . The
method is responsible for painting within the for the associated property. The renders a
miniature image of the icon within0 the .
To associate the with the property, modify the source and add the
to the property as shown in Listing 5.3.
Listing 5.3 Applying the
-- -
-
-
- -
--
-
By now you may be wondering how to debug controls, designers, and editors. As with any class library project, there
needs to be an application that will use the library. This application can be set as the startup program for debugging
purposes. In the case of designers and editors, VS .NET provides the solution. In the project properties, set the Start
Application to , as shown in Figure 5.9.
Figure 5.9. Debugging setup.
Now when the project is executed, a new instance of VS .NET will be launched. This will allow for setting break points
within the control, designer, and editor.
This chapter certainly covered a lot of ground and provides a point of reference for the entire book. With the designer
architecture and services detailed in this chapter, you have all the basic information you'll need in order to create
advanced controls. I suggest reviewing the various base classes and interfaces presented here before moving on to
the remainder of the chapters.
The next four chapters are dedicated to building a fairly complex control, along with designers and custom editors.
Many of the designer services, classes, interfaces, and attributes listed in this chapter will be used during the
construction of the OutlookBar, aka the Shortcut Control.
IN THIS CHAPTER
l
Designer Services
Designer Attributes
Design-Time UI Clues
Attributes
Properties
The process of building custom controls has been greatly simplified due to the richness of the base class libraries
available in .NET. Already you possess the knowledge and skill necessary to create a wide assortment of controls. All
that remains is learning about the services provided by the base class libraries and the VS .NET support for custom
control development. In this chapter, all control and designer base classes, interfaces, events, and attributes are
discussed at various levels of detail.
In this chapter, more details of the base class libraries, designers, and services are explored. Other than learning what
services are available for building controls and their associated designers, an understanding of where to begin or what
base class should be used is all that needs to be refined.
.NET also introduces the concept of attributes. Attributes are used to provide additional information about classes,
properties, methods, and parameters. Control development makes use of various attributes, covered in this chapter,
to associate controls with designers and properties with editors. Attributes provide an extremely flexible mechanism
for loosely coupling components and promoting reusability.
- --The Windows Forms library, like custom controls, uses various base classes for both controls and designers. Often it's
helpful to have a point of reference in order to proceed with a new project. A hierarchy of the class structure provides
an excellent reference advantage. Figure 5.1 shows the class hierarchy starting with the
--- class.
Figure 5.1. The control class hierarchy.
Regardless of the control you intend to build, there exists a base class from which to begin. Notice that a is in
fact derived from a control base class, the class to be precise. Each level in the hierarchy
provides slightly more features and modifies derived behavior to produce the desired result.
Picking the proper base class requires that the expected result and functionality of a new control is properly defined.
As a guide, use the following questions to help decide what base class to derive from:
l
Is it a simple control?
Derive from the base class
Control base classes are not the only classes that can serve as the starting point for the development of a ne w control.
Consider the control that inherits from the control class. In turn, the control derives from the
base class. When developing a new control, give proper consideration to the base class from
which to start. In addition, the same consideration should be given for the control's associated designer.
- - --Like controls, designers have several base classes to choose from. Each designer base class provides certain
functionality that builds on the support provided by the inherited base designer class. At the root of the designer
hierarchy is the - class.
Not every control in .NET is an actual control. Rather, certain items are components that derive from
-. A component has no UI of its own and is often used to hold information for
part of its containing control. The Windows Forms and - are two such components. When a
component is placed on a object, an icon for the component appears within the Component Tray of VS .NET, as
shown in Figure 5.2.
Figure 5.2. The VS .NET Component Tray.
In Chapter 7, "OutlookBarTab Component," the class serves as the base class for the
that is part of the control. -derived classes use the - base class to
provide the necessary designer implementation. The - class implements --,
-, and the - interfaces. As such, component designers are capable of filtering properties,
events, and attributes and have the capability to expose custom verbs. In addition, components designers can shadow
properties and interact with the root document designer, which is the topmost designer. The Form designer is a
document designer.
- Designer Services can be divided into three major sections: component designers, Windows Forms Designers, and drawing d esig
The component designers provide the core functionality of the .NET framework designer architecture, whereas the Window s For
Designers extend the design- time support for Windows Forms. The list of classes, interfaces, delegates, and enumeratio ns for thes
three designer sections is rather extensive. They are discussed in the following sections.
-- -
The -- namespace contains the core .NET framework designer archite cture classes and interfaces
that can be used to define designers and custom editors for components at design-time. Tables 5.1 through 5.4 list the classe
interfaces, delegates, and enumerations, respectively, along with a brief description of each.
Table 5.1. Classes
Class
--
Description
- data for the
- event.
at design-time.
Displays the content of byte arrays in
hexadecimal, ANSI, and Unicode format
provides for basic
collection editing.
Modal dialog for editing the collection
using a .
A unique command identifier consisting
of a numeric command ID and a GUID
menu group identifier.
- data for the
event.
--
--
----
-
-
--
--
Interface
-
-
-
--
-
Description
Interface to add and remove component event handlers
and provides methods for raising
events.
The base designer interface for building custom component
designers.
Event notifications for the addition and removal of
designers, selection changes, and provides the capability to
add designers.
Allows for and - filtering of events, properties, and
attributes for a component.
Interface for mapping designer transactions and
components.
Interface to access the designer options found on the
Options submenu under the Tools menu.
Dictionary interface for a designer to store user-defined
data.
Exposes events as - objects.
Provides enumeration of extender providers. A
component implements the
interface.
Interface to add and remove extender providers at design
time.
Used to provide the IDE with context help information
about the current task.
Used for identifying inherited components.
Interface to add and remove commands and verbs to and
from the VS .NET menu.
Interface to names of and references to objects within a
designer project.
Provides access to specific resources.
-
-
Support for view technologies.
events.
Retrieves an assembly or type by name.
Table 5.3. Delegates
Delegate
Description
Handles the - event.
Handles the event.
Handles the event.
Handles the ,
, , and
events.
Handles the event.
Handles the - and
--- events.
Enumeration
Description
Used by the to indicate the display type (Auto, Hex, ANSI, or
Unicode).
---- -
Tables 5.5 through 5.7 list the classes, interfaces, and enumerations provided by the Windows Forms Designer framework.
Table 5.5. Classes
Class
Description
for filenames.
for folders.
Interface
Description
Enumeration
Description
Selection rule for a component as it relates to moving and sizing.
-- -
The last major area of designers includes the services provided under the -- namespace. As you might
expect from the name, the designers and services provided deal with UI presentation and drawing logic. In addition, interactio n
the Windows Forms ToolBox is provided within this namespace. Tables 5.8 through 5.11 show the classes, interfaces, delegates, an
enumerations, respectively, that are provided by the -- namespace.
Table 5.8. Classes
Class
Description
Property Grid.
Collection of - used by the
.
Interface
Description
Allows management of the properties list of the properties
window.
Delegate
Description
Handles the events from the
Handles the
-
event.
Table 5.11. Enumerations
Enumeration
Description
Indicates the value editing style for a (
, ).
At this point, you are likely feeling overwhelmed by the number of services available through the -
namespace and the ---- namespace. Fortunately, the use of all these services is rarely needed
even for complex custom controls. It's also important to remember that each service provided is extremely granular in scop e and as
such its use is easier to understand. One last note: a lot of the services are already provided free; that is, you d on't have to do
anything to make use of them.
Note that the we developed has not made any explicit reference to most of the available designer service s, yet th
control and its associated designer are fully functional. There are times, however, when it is necessary to use the service s availabl
During the development of the control, many of the services will be used.
- Attributesplay a large role in every facet of .NET, from XML serialization to control development. The
relies heavily on custom attributes for object properties that it uses to determine the default value, determine whether
the property is browsable, and make many other choices. Attributes are used to control the and the
code serializer, and for defining the for a property, the designer for a component, and the category
for a designer, among other things. The common case for attributes deals with interaction with the
and specifying the category, description, browsable, and type converter attributes. Figure 5.3 shows how attributes
are used to control the .
Figure 5.3. The .
The use of attributes provides a powerful and expressive manner in which to decorate a component with additional
functionality and services without having to inherit or write custom code. As a general rule, look for an attribute first,
and then write code. Chances are there is an attribute that will accomplish that task without requiring you to w rite any
code. The subject of attributes will continue to come up throughout this book. With each new attribute used, an
explanation of its use will be covered.
- Now that the dry, tedious topics have been covered, it's time to get back to the fun stuff. The goal of any control
designer is to provide a proper view of how the control will appear during runtime. However, it is also necessary for
the designer to provide a set of hints or visual clues that facilitate the design of the control. The designer itself
provides these design-time hints or UI clues by overriding the - method. Figure 5.4 shows the
various UI clues provided by the designer.
Figure 5.4. The designer UI clues.
Each designer draws a focus rectangle and the control alignment grid only if the grid is turned on. These
subtle UI clues provide important information to the developer when he is designing the control. The focus rectangle
not only shows the active tab, but also defines the client area boundary for the current . In addition, the
designer draws the control alignment grid to assist the developer in aligning child controls within the
boundary of the .
To see how simple it is to provide UI clues, note how the - class is extended to provide an
annoying design-time adornment. The designer paints the term " DESIGN-TIME" across the top of the
, as shown in Figure 5.5.
Figure 5.5. Providing UI clues.
-
- - - --
-
-
-
-
-
-
-
-
-
--
--
-
---
- ----
- -
-- - -----
-
--
-- -
- - -
-- -
-
-- -
-
-- -
-
- -
- --
- - - -
-- -
- -
- -
- -
- -
- -
- -
- --
--
- - -
-
- - -
- -
The - method found on line 84 of Listing 5.1 simply creates an opaque brush and renders the
text "DESIGN-TIME" across the top of the . Obviously, this is not the best idea for a UI clue;
however, it does illustrate the ease with which UI clues or adornments can be added to a control.
New to the .NET framework is the concept of attribute-based development. Attributes provide an extensibility
mechanism for classes, properties, methods, parameters, and return values. By applying attributes to a particular
entity, the default behavior can be altered to produce a new desired result. For a complete description of attributes,
refer to the MSDN help.
The .NET designer architecture makes heavy use of attributes to simplify common development tasks. Examples
include defining the default value for a property, assigning a category and description to properties, and indicating the
default event for a control, to name a few. No discussion of the designer architecture would be complete without a list
of attributes commonly used for designers, controls, serialization, licensing, and the . Table 5.12 lists
the attributes found in the - namespace, along with descriptions.
Table 5.13 and Table 5.14 list the attributes found in the -- and
- namespaces, respectively.
Table 5.12. - Attributes
Attribute
Description
The value for this property
originates for another source,
generally the containing control.
Used by the to
determine whether the property
should be displayed.
Defines the category for the
property or event within the
of the
.
-
-
-
--
Specifies the -
for the class.
Specifies a list can be used for data
binding.
Localization for a property.
Specifies that a property can be
combined with properties of other
objects in the property window.
Specifies that the parent property
should be notified when the
property is modified.
Specifies that the property value
should appear within a set of
parentheses. Example:
appears next to
collection-based properties such as
the - property of the
control.
Identifies the property tab(s) to
display for the class or classes.
Defines the name of a property
provided by the class that
implements the
interface.
The class is an extender
provider.
Specifies that the property is readonly within the .
Specifies that the property can be
used as an application setting.
Defines how the designer refreshes
when the property is modified.
Specifies whether the component
should be loaded in the toolbox.
Specifies that the component will
accept only other s
belonging to the specified
namespace, or type.
Attribute
Description
- Indicates the serializer for the serialization
manager to use in order to serialize objects of
this type.
-
Indicates the base serializer for the root object
designer.
Attribute
Description
Associates an image with the specified component.
Support for small and large images.
Attributes are a prominent theme in .NET development, and custom control development is no exception
The .NET designer architecture provides a RAD (Rapid Application Development) style environment for applications
development. One of the keys to the RAD environment is the capability to modify the properties of components
visually at design-time. The Windows Forms Designer provides a that allows for modifying control
properties and instantly viewing the result of those modifications.
The .NET designer base classes provide extensive support for editing various types of properties, from intrinsic types
such as strings and integers to complex objects such as fonts, collections, and images. Because it is impossible to
provide a property editor for types yet unknown, the is used to define the that
the will use to support modification of the property. Figure 5.6 shows the for the
- collection of the control.
Figure 5.6. - property .
Providing a custom for a control or component is fairly simple, thanks in part to the existing designer
architecture. The base class provides four virtual methods that can be overridden to provide a custom
for a component. Table 5.15 lists these methods along with descriptions of each method's purpose.
Table 5.15. Virtual Methods
Method
Description
Invoked by the to edit the specified
property value.
Specifies the type of editor as , , or
.
Returns if implementing method;
otherwise, returns -.
All that is required in order to associate the with a particular property is the use of the
. The specifies the type of editor to be used when editing the property at
design-time. Figure 5.7 shows the that will be built for the 's property.
Figure 5.7. for the .
The typical coding convention for style editors is to have the class contained within the editor class as a
nested class. The drawback to this is that the designer will not allow for visual development of a nested class. When
developing the , you may want to create the class as you would any other and then, when
finished testing, move the class into the editor class. This, however, creates a small issue with the resource file,
the corresponding file with the - extension. To preserve the resource information, it is necessary to include the
resource file into the project. I suggest the following steps for creating nested classes:
1.
2.
When you are finished developing the form, copy the underlying - file somewhere safe.
3.
Copy the class code into the target parent class, in this case the class.
4.
5.
Add the saved - file to the project as a resource. Adding an existing item and choosing the - file can do
this.
6.
Double-click the resource file in the Solution explorer. This invokes the resource editor.
7.
In the data sheet of the resource editor, modify the value of - entry to be the qualified name of the
dialog (see Figure 5.8). In the case of the the qualified name is
.
Figure 5.8. The resource data sheet.
8.
Locate the line of code in the method of the form where the resource manager is
referenced. It is usually the first line. Modify the qualified name so that it matches that of the -
value in the resource file. The following snippet shows the line of code to modify:
---- --
----
9.
If it seems like a complex set of steps, it is. Unfortunately, the resource file support for VS .NET is not quite up to par
with the way it should be in terms of ease of use. In an attempt to simplify the development process, certain tasks
such as resource file management have been made overly complex.
Listing 5.2 contains the source for the , along with the nested class to act as a
custom .
Listing 5.2 Source
- -
- -
- --
- --
- -
- ---
- ----
- -
-- --
--
-- --
--
- -
---
- -
- -
-- ---
---
---
---
---
--- --
---
- -
-
-
-
-
-- -
-- --
--
---
--- --
---- --
----
- ---
- ---
- ---
- ---
--- ---
- ---
--
- -
- -
- -
-
--
-
---
- -
- -
-
---
- -
- -
- -
- --
--
---
--- --
--- -
--- --
--- -
---
---
---
--- -
- -
- -
- --
-- -
- -
-- ---
---
-
---
---
- --
-- - --
-
-
- --
- -
- --
- -
The code in Listing 5.2 consists of two major components: the dialog for selecting and previewing the icon image, and
the code necessary to paint the icon image within the .
The nested is a basic -derived class that is used to display an for
selecting an icon. In addition, the creates an instance of an to allow for a simple
preview mechanism. After an icon is selected, the is closed and the class is
responsible for providing the necessary code to update the .
The class provides the implementation necessary to interact with the . The first task is to
return the type of editor that will be provided. For the , the type of is returned, as a nested
will be used. Other types are and . When the method is invoked, the
creates and displays the nested to allow for the selection of an icon image. When the value of
the icon is modified, the will invoke the method to determine whether
the editor will handle displaying some representation of the property within the .
Next, the method is invoked if the method returns . The
method is responsible for painting within the for the associated property. The renders a
miniature image of the icon within0 the .
To associate the with the property, modify the source and add the
to the property as shown in Listing 5.3.
Listing 5.3 Applying the
-- -
-
-
- -
--
-
By now you may be wondering how to debug controls, designers, and editors. As with any class library project, there
needs to be an application that will use the library. This application can be set as the startup program for debugging
purposes. In the case of designers and editors, VS .NET provides the solution. In the project properties, set the Start
Application to , as shown in Figure 5.9.
Figure 5.9. Debugging setup.
Now when the project is executed, a new instance of VS .NET will be launched. This will allow for setting break points
within the control, designer, and editor.
This chapter certainly covered a lot of ground and provides a point of reference for the entire book. With the designer
architecture and services detailed in this chapter, you have all the basic information you'll need in order to create
advanced controls. I suggest reviewing the various base classes and interfaces presented here before moving on to
the remainder of the chapters.
The next four chapters are dedicated to building a fairly complex control, along with designers and custom editors.
Many of the designer services, classes, interfaces, and attributes listed in this chapter will be used during the
construction of the OutlookBar, aka the Shortcut Control.
- IN THIS CHAPTER
l
Control Design
Control Internals
Building quality custom controls requires knowledge and practice. During the development process, there is a time of
discovery. This discovery process includes learning the underlying system, the framework, and the most practical
design for the control in order to provide a clear strategy for moving the project forward. To grow knowledge, there
must be a starting point from which to begin. The Windows Forms base class library can be used as such a starting
point.
By studying the current set of controls and their associated designers and s (see Chapter 5, "Advanced
Control Development," for more information on s), you can glean lessons and apply them to new
endeavors.
Before this chapter delves into designing and building the control, its subcomponents, and related
designers, a tour of the existing set of Windows Forms controls will provide some insight that can be applied to the
construction of the control to be built in chapters 7, 8 and 9.
It is necessary to understand how the common controls shipped with VS .NET are constructed so that the principles
behind their design and implementation can be applied to new custom controls. The remainder of this chapter is spent
exploring the concepts behind the implementation of the current common controls. In addition, we explore how to use
the current set of controls to gain a better understanding of how to create custom controls.
-
All Windows-based applications provide several well-known UI elements, such as menus, toolbars, and status bars.
Beyond these basic UI elements, most modern applications provide their own custom set of controls and/or extend
common UI elements to add some flair to the application. The leader and most-often-imitated UI elements are
Microsoft's. MS Office has been a constant source of new UI elements and ideas for third-party control vendors.
Advanced docking toolbars, custom menus, and windows are now available from several different vendors.
Users of Visual C++ and MFC (Microsoft Foundation Classes) have some basic UI elements afforded to them, such as
simple docking toolbars. However, advanced UI elements such as those found in the latest edition of MS Office and
VS .NET are not to be found within any provided framework or .NET classes. If you want your application to resemble
a Microsoft application, you will have to purchase a set of libraries from a third-party vendor or create your own set of
UI classes.
As with any type of project, certain criteria should be followed when creating custom UI elements. These are the
criteria:
l
Looks cool
Concise usage
Design-time support
Did I mention that it needs to look cool? After all, if you want your control to be noticed, it has to have a sexy
wrapper. Consider the menus in VS .NET verses the standard menus available as part of Windows Forms
development. Figure 6.1 shows a side-by-side comparison of these two menus.
Figure 6.1. A menu comparison
Both menus shown in Figure 6.1 provide the same functionality; however, users looking at the two will believe that
somehow, in some way, the cooler-looking menus translate into a more powerful application. When building a control,
make sure that it (a) looks cool and (b) looks really cool.
Of course, if the control only looks cool but does not offer any functionality, it is useless. Creating a nice-looking
control will get a developer's attention, and providing a useful control will get developers to actually use the control.
The .NET SDK ships with the tool ILDASM (IL Disassembler). ILis the Intermediate Language to which all .NET
languages are compiled. By now, most of you are probably familiar with this tool. ILDASM displays the contents of a
managed assembly, including the manifest, value types, interfaces, classes, enums, and delegates. In addition,
ILDASM displays the IL code for all class methods and properties. This IL code is fairly readable and can be translated
into C# with little effort. Producing the exact original C# code should not be expected because IL is not a one-to-one
mapping tool as far as code is concerned.
Examples are often the best way to learn a new task, and with ILDASM you essentially have a huge set of examples
just waiting to be explored. Every control, designer, and for the controls shipped with .NET is located
within one of three assemblies: ---, --, or
--. These three assemblies contain all the code you could ever need to use as
examples. However, there is one drawback: the only way to view the code is with ILDASM and the code shows as IL.
This does not represent an insurmountable obstacle because IL is not very hard to understand even if you don't know
how to program in it. You can figure out most of what you need to know by looking at the IL code itself.
-
The easiest path for translating IL code to C# is to do it by hand. Given the readability of IL, such a task is not
difficult. Consider the found in the -- namespace. The
object uses this editor during design-time as the . The class displays an Open File
dialog and draws the selected image within the property grid, as shown in Figure 6.2.
Figure 6.2. at work.
for the control? The method is a basic C# translation of the IL code for the
's method. Listing 6.1 shows the IL code from the method.
This IL code was copied from the code window of ILDASM.
Listing 6.1 IL Code for
- -
-- - --
- -- --
--
-
----
--
--
--
-
----
--- --
- --
-----
-
--
-
---
-
--
-
---
- -- ------ -
-- --
----
- --
--- --
--
- -- -- ---- -
- --
---- -
- --
--- --
--
The 45 lines of code in Listing 6.1 translate into about 12 lines of C# code. Listing 6.2 shows the approximate
translation from the original IL code to the C# equivalent.
Listing 6.2 C# Translation from the Original IL Method
---
- -
---
- -
- -
I translated the IL code to C# in just a few short minutes. Again, it is important to note that the translation is not
exact, but rather an approximation of the original C# code. The main reason for showing the IL is so that you can
compare the IL statements to the C# code.
IL is somewhat verbose when it comes to the code listing. This is due to the low-level nature of IL, and it serves as a
good example of the level of abstraction provided by languages such as C#. When you need to know how a control,
editor, type converter, or designer is functioning, make use of ILDASM to view its implementation.
-Every Windows Form control derives from the base class. This includes the class. Because controls are
capable of hosting or parenting other controls, it's possible to have a as the child of a control. Strange as
this may sound, Figure 6.3 shows a child being parented by a control.
Figure 6.3. A form within a panel.
Not only is the in Figure 6.3 a child of the , but also the , true to form, will clip child controls. This
means that dragging around the will cause it to be clipped to the bounding area of the . Figure 6.4 shows
the child being clipped by the .
Figure 6.4. An example of clipping.
Even stranger than hosting a within a is the capability to host a within a . Although there is
no practical reason to host a form in a button, it's important that you understand the relationship of controls and
windows and how that relationship affects behavior.
This begs the question of why you would want to use this design. Why allow a , which should be a
component, be the child of a or ? Everything is a window, regardless of what shape or functionality it
takes on. Because the class offers the basic framework, everything is now considered a control.
The base class implements all necessary ActiveX control interfaces, basic drag -and-drop, and the all
important interface. The interface provides access to the underlying window handle.
This window handle can be used to call Win32 functions that require a handle to the current window.
Most of the controls and classes provided with Windows Forms are available for your own use when developing
controls or applications. However, several classes are private to the -- and ---
namespace. One of the more interesting classes is the -- class. This is, of course, just one of
many private classes found within the .NET base class library. Even though the classes are private and cannot be used
to create new controls, nothing prevents your peeking at their implementation with ILDASM to see what functionality
they provide and how they are used.
- Designers represent one of the most critical aspects of custom control development; however, the documentation
surrounding their implementation is somewhat lacking, to say the least. Without a proper designer, a newly developed
custom control will not respond to the design-time actions of a developer or the development environment. Even
though the documentation for designers is rather light, the openness of .NET and the capability to peek inside the
internals of other designers provides some insight as to the design and development of control designers.
-
One of the most important things to note about a designer is the fact that there is only one instance of a designer.
Let's use the as an example. If there are three s on the form, there exists only a single
-. Figure 6.5 shows this basic relationship between a control and its designer.
Figure 6.5. The designer relationship.
Just before the designer is to be utilized, the context information for the designer is updated. When a different
is selected, the designer's property is set to the currently selected control.
Whenever an action such as a component being added or removed takes place, all active designers that have
subscribed to the are notified. Remember that a designer subscribes to the
by overriding the method. During initialization, the designer can choose to
subscribe to the events listed in Table 6.1.
Table 6.1. Events
Event
Description
A component has been added.
A component is about to be added.
A component has been changed.
A component is in the process of being changed.
A component has been removed.
Because there is only a single designer for the , each time an is added, the
-'s method is invoked. This means that the designer will receive one notification
for each control it is responsible for designing when a subscribed change event occurs. If there are three
- on the form, then three notifications will be sent to the designer. It is important to note that anytime a
control or component changes, all designers are notified of the change. This is true even if the designer does not
manage the control being changed.
The delegate parameters for a change event specify a parameter: -. The
- provides a property, , that specifies the component associated with the
event. This allows for the receiver of the event, in this case the designer, to determine the context of the event and to
determine whether the change event should be responded to. In Chapter 8, "OutlookBar Control," the designer uses
the to track which controls it is parenting within the individual tabs.
The control will be designed and implemented in a manner that encompasses much of the material
covered so far. In addition, some basic design patterns and guides will be offered during the development process.
Learning by example has always been an effective way to teach and learn new material. I also encourage you to
extend what you learn by enhancing the projects discussed in the following chapters.
One key point to understand about control development is that the presentation of the control that is, the rendering
of the look and feel is the most time-consuming aspect of control development. Creating the underlying logic and
designer is often very easy in comparison to the requirements of generating the proper UI and usability of a control.
Figure 6.6 shows the completed control.
Figure 6.6. The completed control.
The control project spans three chapters. The first step in building the consists of
designing and building the individual tabs found within the control. These tabs will be built as components and not as
actual controls. This approach provides designer support for the tabs and lessens the overall weight of the control.
This brings us back to the subject of soft controls. Remember that soft controls look and act like regular controls to an
end user but in fact are not controls. Soft controls have no corresponding HWND, window handle, and require some
parent control to pass messages to them for processing. This approach saves valuable system resources and is often a
more simple way to build complex controls.
As far as designer support for soft controls is concerned, deriving a soft control from the base class
enables a default designer to be assigned to the soft control. In addition, a custom designer can be created for the
soft control. The VS .NET IDE will also handle the basic process of code serialization for the soft control, thus removing
the burden of creating a custom code serializer for the control.
Note
Creating custom code serialization is not a task you'll likely encounter when developing custom controls and
as such is not covered here. However, information about creating custom code serialization can be found
within MSDN.
Chapter 7, "OutlookBarTab Component," shows how the tabs are built as components.
Chapter 8, "OutlookBar Control," develops the control to host one or more of the
components.
Chapter 9, "ImageListView Control," shows you how the - control is developed. This is a
standalone control that can be hosted by the control.
The next few chapters are geared toward applying the knowledge gained so far. Each successive chapter moves closer
to providing the final goal, a working -style control. The final result should be considered a starting point,
a control from which to expand its functionality, appearance, and design-time support.
IN THIS CHAPTER
l
Detailed Design
Implementation
All projects, no matter how small or large, must start somewhere. Ideally, you should build the core components of a
system and gradually expand outward until such time as the outer layers can be built in parallel. The
control will be developed using this same process. First, the is created. The
provides a simple starting point for the overall project. In Chapter 8, "OutlookBar Control," the is
used within the main control. The control is then further extended in Chapter 9,
"ImageListView Control," to provide for image support.
-
Before diving into the code for the , its design must be fully specified. The design details include the
following:
l
Base class
Interfaces
Properties
Events
Public methods
User interface
/ designer
After the design criteria have been established, it's merely a matter of realizing the design in code. The first question,
of course, is where to start.
- -Deciding on the base class for the requires determining what role the component plays, the nee ds of
the component, and its interaction with its eventual container. Because the represents a graphical
entity on the screen, it might seem necessary for it to derive from the base class. The base class
would allow for the to receive Windows messages, such as keystrokes and mouse events. However,
there is no requirement which states that only controls can draw on the screen. All that is required to draw on the
screen is a device context, such as the - object.
In fact, the mostly serves to hold information about a particular tab within the control.
The individual tabs are not full-blown controls, but rather a means for the user to choose which tab within the control
to activate. These types of controls are known as soft-controls. Remember that soft-controls do not have an
associated window handle or message loop and therefore do not require any real resources. This design allows for a
light and efficient control design.
In contrast, if the were to be derived from the base class, each individual tab would have
an associated window handle and the extra weight of the base class. The upside to this is that each
would be capable of processing Windows messages on its own.
In processing the messages, the control would then need to notify its parent container, in this case
the control, of relevant events such as activation via a mouse click. Exposing events from the control to
which the control would subscribe would develop this interaction.
Deciding on the base class for any control requires careful consideration of the intended use and the requirements of
the control. The reason for creating the as a soft-control is to provide an example of soft-control
creation and use. After all, the purpose of this book is to demonstrate various techniques for creating custom controls.
As seen in Chapter 5, "Advanced Control Development," the .NET BCL, base class library, provides several interfaces
geared toward control development. The various control base classes implement these interfaces to provide a default
implementation. During the development of a custom control, it may be necessary to override the implementation of
one or more interface methods or properties. In the next chapter, a custom control collection is constructed to
maintain a collection of s. This custom collection is constructed by implementing the necessary
collection interfaces: , -, and .
- Defining the properties is relatively easy. Consider what information the component needs to
contain, and chances are most of that information will need to be available to users of the component. In addition to
these properties, there needs to exist an event to notify observers of the component when a particular property has
changed. The event name should coincide with the name of the property. For example, the property should have
a corresponding event named . This property/event pair holds true for all properties.
Beginning with properties, the purpose of the is to hold a child control, such as a panel or list box.
The also provides a text caption and an associated icon. In addition, the text color and text
alignment of the tab can be changed during design-time and at runtime. The properties for the are
listed in Table 7.1.
Table 7.1. Basic Properties for the Component
Property
Description
The text or caption to be displayed
The text color for the component
The text alignment for the component
The associated icon image for the component
The associated child control to be contained within the
The properties listed in Table 7.1 are used to define the appearance of the . In addition, each
property has a corresponding event that is used to notify the control that a property has been modified.
The property/event pairing is suggested by the design guidelines provided by Microsoft for control development. Table
7.2 lists the matching events for the properties in Table 7.1.
Table 7.2. Property Changed Events
Event
Description
Raised when the property has been modified.
Raised
Raised
Raised
Raised
Each of the events from Table 7.2 has a corresponding protected member method associated with it. The
corresponding member methods have the method signature
- -
where the is replaced with the name of the corresponding event. Again, this is according to the
development guidelines proposed by Microsoft. This is done so that any derived classes have the first opportunity to
handle the various events. This is accomplished by overriding the protected event methods.
To keep the component simple, only two public methods are provided for the soft-control. Based on
previous discussions of soft-controls, you have probably guessed that the two public methods relate to drawing and
hit-testing. Table 7.3 lists the public methods for the component.
Table 7.3. Public Methods
Method Description
- Determines whether the point passed in lies within the bounding rectangle
of the component.
When you're building soft-controls, the methods listed in Table 7.3 are the usual starting point. Of the two methods,
the method is often subdivided into several smaller methods, with each method handling a different aspect of
the drawing logic.
-
Even though the is not a control, it will still be responsible for rendering its own UI within the
control. The reason for this is to keep the drawing code contained within the . This frees
the parent control from having to perform the drawing of the soft- control and helps segment the code. In addition, the
component could then serve as a base class, and the derived class could in turn provide its own
custom drawing logic.
Rendering of the tab component includes a normal look with and without an image, and a pushed look, such as when
a mouse has been clicked over the tab. In addition, the will trim its text with an ellipsis ( ) set of
characters when the text length exceeds the size of the tab. Figure 7.1 shows the various looks of the tab component.
Figure 7.1. The look and feel.
In addition, the will keep track of its own bounding rectangle and provide hit-testing, determining
whether a mouse click point is contained within its bounding region. The bounding rectangle is the physical size of the
component and is used when determining whether a mouse click has occurred within the component. This is kno wn as
hit-testing.
-
One of the most critical aspects of a control from a developer's point of view is the design-time experience. When end
users of an application see the runtime behavior, a developer requires that the control be rich in design-time
functionality. The need to provide an easy-to-design control with custom editing features requires careful attention to
be given to the designer for a control.
Sometimes the default designer provides the necessary support, as is the case with the component.
For a -derived class the - class represents the default designer. For classes derived
from the base class, the default designer is the - class.
The basic support derived from the - consists of placing an icon within the Icon Tray area of the
VS .NET IDE and property browser support. In addition, basic code serialization services are provided.
During the development of the control in Chapter 8, a custom designer will be required to provide
features such as drag-and-drop at design-time for child controls.
Now that the responsibilities and expected functionality of the have been defined, it's time to realize the design in cod
provides the complete listing for the component.
Listing 7.1
- -
- -
- -
- ---
--
-
-
-
- - -
- - -
- - -
- - -
- - -
- -
- -
- -
- - -
- -
- -
- -
- -
---
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- --
- -
--
-
-
- - -
---
The implementation of the follows the general layout of the topics discussed within this chapter. Each of the o
and public methods has been implemented. In addition, the method delegates various drawing tasks to specific protecte
component.
As expected, the drawing of the components represents most of the component's logic. The must determine ho
the properties, size, and state of the tab. Drawing of the tab is broken down into three areas: , , and
The method uses the familiar class to render a basic button-style control. Next, the me
version of the icon image associated with the tab component. Finally, the method is called to draw the text for the c
consideration the icon and text length.
It's important to note the use of the attribute used on the class. The attribute sp
should create a toolbox item. In addition, it can be used to specify the type of toolbox item to create. In the case of the
should not be available on the toolbar. This will prevent the component from being available for use as a toolb ox control. After all, t
really a subcomponent of the control.
-
Without having the control implemented, testing the requires creating a custom test- bed application. O
mind when building custom controls is that most of the time you are left to your own devices when it comes to testing the co nt
provides a rich infrastructure for control development, many times your own creativity and necessity will spawn new too ls for testing.
As with many of the applications built so far for testing, the property grid will serve as a means to interact with the prop erties p
component. Figure 7.2 shows the test-bed application.
Figure 7.2. The test-bed demo
Before diving into the source for the test bed, you should have noticed the property being displayed within the property
component has no property, yet a grid item appears. When the control is built, its own property will b
child tabs, and as such this functionality should be tested as well. To provide the property, the will serv
derived class will provide a single property: .
Listing 7.2 shows the source listing for the test-bed application.
Listing 7.2 Testing the Component
-
-
-
-
-
-
-
-
--
-
---
-
- -
-- -
-- ---
---
- -
- - -
- -
- -
-
-
- - -
- --
-- --
- - -
- --
- -
-
-- --
--
---
--- --
- -
- -
- -
- ---
--
---
- ---
-- -
- ---
- -
- ---
-
---
-- -
- -
-- ---
- -
---
By deriving the class from the component, a property can be added for the
same time, this technique does not disturb the component in any way that would alter its behavior.
The test-bed application is a standard Windows Forms based application. To test the component, the form w
managing the component. The major areas of the test bed revolve around mouse events and painting. When a mouse click occurs on the par
and y coordinates of the mouse click are passed to the for hit-testing. Again, hit- testing is used to determine wh
within the soft-control.
Depending on the result of the hit-testing for the , the appropriate parameters are passed to the metho
method handles all the drawing logic for the component.
In addition to mouse events and drawing, the main form subscribes or listens to the events of the component. These ev ents includ
, and so on. When a property of the component has changed, it is necessary for the parent form to provi d
can use to redraw itself to reflect the changed properties. All the necessary testing code is found between line
Using the control gives a pseudo design- time feeling to the component being tested. The VS .NET IDE provid e
controls such as the , and a simple test application such as that developed in Listing 7.2 could easily be extend
develop other soft-controls.
The journey toward a functional and popular control, the , is now underway. In the next two chapters
control development is the primary focus. The component serves as the basis for building the larger
control. Developing controls requires defining the various components and building small blocks from which the main
control will be constructed. In this chapter several ideas have been re-enforced, and some new ideas have been
introduced. The process of building controls is a fantastic journey, and it has just begun.
IN THIS CHAPTER
l
Control Design
Building on the knowledge gained thus far, it's time to begin developing a more advanced control. The
control is one of the most common custom controls used by applications. In fact, there are many Toolkits, provid ers of
custom controls, which are companies that offer the as part of the standard offering of controls. At the
end of this chapter, you will have created your own functional control to use in your projects.
The process of building the control is divided into three major sections: The control, a
custom collection for managing components, and the control's designer. As with the other controls
developed so far, the process will begin by first defining the requirements for the control, including the listing of
properties, events, and public methods. In addition, the topic of custom events and custom event parameters will also
be discussed.
As with the component developed in the preceding chapter, a test application will be created to aid
in the development of the control. Remember, you'll need to get creative to test controls during their
development rather than relying on the VS .NET IDE. The reasoning for this is that in the early stages of control
development, the control will more than likely lack full support for interacting with the IDE. As such, creating small
test applications will allow for basic testing and debugging until such time as the control provides the necessary
support for the VS .NET IDE.
-
The main functional requirement of the control is that the control will host other controls within the
components. Each new child control will be associated with a particular tab within the ,
and that child control will be activated when its parent tab is selected. Figure 8.1 shows the control
hosting a control and a control.
Figure 8.1. The control in action.
Because the control will act as a container control for other .NET controls, the emphasis of this chapte r is
to demonstrate the implementation of this particular requirement. Extending the controls feature set will
be left to you as an exercise.
Before the discussion dives into the code for the control, the subject of defining custom events needs to be discussed.
The following section discusses custom events.
- The control defines a custom event and an associated event handler. In Windows Forms development,
controls raise events to notify the observer either of some change in the state of the control or that an action has
occurred that should be responded to. Generally, event handlers for such events take the following form:
-- - -
The - parameter can be the -- class or a class that derives from -, such as
- or --.
Defining custom events and event handlers, known as delegates, provides a clear message to the user of the control
as to the event type and the expected event arguments that pertain to the event. Listing 8.1 shows the proper
semantic for defining custom events.
Listing 8.1 Custom Events
- -
- -
-- -- --
-
-
- --
-- - --
- --
- - --
--
-
- - --
- -- --
- --
-
- -
-- -
- - - -
- - -
--
- -
--
- -
The process of creating custom events generally begins by defining the event and the event parameters. According to
the development guidelines provided by Microsoft, event handlers should define two parameters. The first parameter
for an event is the sender or originator of the event. The second parameter is the necessary information about the
event, and it should be an - derived class.
Note
Delegates are the basic mechanism for handling events and notifications within the .NET framework.
Extending the basic set of events and defining custom events is a programming task that all .NET
developers should become proficient in.
Notice the method - on line 33 of Listing 8.1. Defining a protected virtual event handler within the
class allows for derived classes to override the event handler to perform any custom processing before allowing the
event to propagate to the registered observers of the event.
The implementation for the control is divided into three parts:
l
The control
Custom collection
The control provides a custom event for notification when the selected changes. In
addition, some basic properties are provided, such as the active tab, the index of the active tab, and a collection of
objects.
The collection provides a means to add new tabs to the at design-time or runtime. This
collection is discussed later in the chapter.
The - adds support for drag-and-drop during design-time, as well as the capability to activate
the hosted tabs within the control similar to the Windows Forms . This designer is discussed later in the
chapter.
To keep the code to a minimum while still providing a useful control, the control will provide only a bare
minimum set of properties and only a single event. Table 8.1 lists the properties exposed by the control.
Table 8.1. Control Properties and Descriptions
Property
Description
Exposes a custom collection of components.
Property
Description
The currently active component.
The formerly active component.
-
-
Most of the control code, excluding the custom nested collection, deals with drawing and child control
management. Listing 8.2 provides the core implementation of the control. The implementation is
discussed in more detail following this code listing.
Listing 8.2 Control
-
-
-
-
-
-
-
-
-
---
- --
-- - -
-
-
- --
-- ---
- - -
- - -
- -
-- --
- -
-- --
- -
-- --
-- --
-
--
- -
-
- - -
- -
- -
- --
-
-
- --
-
-- --
--
-
---
--- --
-
-
-
-
- --
--
--
-
-
-
--
-
--
-
--
- --
--
- -
--
-
-
--
-
-
-
- -
- --
-
- -
- -
--
- -
--
- - -
- -
--
- - -
- -
- -
--
- -
--
-- -
-
- -
-
--
-
-
-
-
-
- - -
- - -
--
-
- -
-
-
-
-
-
--
--
--
-
-
-
-
--
-
-
-
-
-
-
-
--
-
- -
-
--
-
--
-
-
--
-
-
-
- - --
-
--- --
--- -
The code listing in 8.2 represents all the code for the control, including the implementation of the
custom collection. The custom collection is covered later in the chapter.
The purpose of the control is to host other controls. Most of the code for the control deals
with the management of the child controls. When an tab becomes active, the control must
calculate the area that represents the child control's size and position. The purpose behind this calculation is so that
the child control can be properly positioned within the active tab. When this is done, it appears that the child control is
connected to the tab. When a different tab is activated, a new child control rectangle is calculated and the current
tab's child control is repositioned and made visible.
Calculating the active child control's position requires knowing the number of tabs, the height of a tab, and the active
tab index. The active tab represents the top of the child control's position, and the height of the child control is
determined by subtracting the height of any tabs below the child control. The method , on line
261 of Listing 8.2, provides the logic necessary to determine the position of an active child control.
Again, the main job or function of the control is the management of s and the
associated child controls. Managing the various s requires tracking mouse events, just as in the test
application from the preceding chapter, and providing a custom collection to track the s. Tracking
mouse events occurs in the methods - and -. Each of these methods uses the -
method provided by the component to determine whether the mouse click has occurred on a tab
component.
In addition, the base class - property required an attribute that prevents the from
being persisted in generated code. By default, any exposed collection automatically has its contents saved, or
generated, by the code generator during design-time. The reason for not wanting this collection to be generated is
that child controls are added to this collection when new objects are added to the - collection. If
the control persisted both the - collection and the - collection, this would cause double the
references to controls contained with the control.
The design of the control was driven by the functional requirements and expected runtime behavior to
be provided. Determining the base class from which they derived came down to two possible choices:
or -. Each of these base classes provides the necessary functionality needed by the
control. Such functionality includes the capability to host child controls and provide focus management.
The decision to use as the base class was the result of realizing that the - class
provides no extra functionality required by the control than does the . Therefore,
the provided the proper level of support without any additional overhead, as would be introduced
by the - base class with its extra level of inheritance.
Among the more interesting implementation parts of the control are the methods
-- and --. Using the API makes it possible to
subscribe to all available events without having to know all the events being exposed by the
component. Whenever the raises an event, the control will invalidate itself to reflect
the changes in its child components.
- -
Following the design of the Windows Forms control, the provides a custom collection for the
objects to be managed by the control. This custom collection provides a means to add new tabs to
the at both design-time and runtime. The is then serialized during design-time by the
code generated. Providing a custom collection requires implementing the , , and -
interfaces. Tables 8.3 through 8.7 describe the properties and methods for these interfaces.
Table 8.3. Properties of
Property
Description
Returns the number of objects within the collection.
Method Description
Copies the current contents of the collection to an array starting at the
supplied index.
Table 8.5. Method of
Method
Description
Returns an enumerator that can be used to iterate through the
items within a collection.
Table 8.6. Properties of -
Property
Description
- Returns if the collection size is static or fixed.
Method
Description
Adds a new item to the collection.
Removes all items from the collection.
Determines whether the collection contains the specified item.
Retrieves the index of a specified item.
Inserts an item into the collection at a specified index.
Removes the specified item from the collection.
Removes the item at the specified index.
In addition to the methods required by the implemented interfaces, the also implements the method
. The code generator, when adding child components to a parent container, uses the method.
Also, the is a nested class within the control itself, because it is dependant on the
services provided by the control. As such, the class cannot stand on its own because
its implementation requires access to protected methods within the control. Listing 8.3 provides the
source for the implementation.
Listing 8.3
-- -
-
-
- -
--
- - -
- - -
--
- -
--
--
--
--
--
- -
--
--
--
- - --
--- --
--- -
-
-
The represents a fairly standard collection implementation. All items for the collection are stored in
an internal -. The reason behind developing a custom collection is that there's a need to interact with the
control. When a new component is added to the collection, it is necessary to add the
- child control to the - collection of the control. Again, the reason for using a
nested class to provide the collection is that it allows the collection to access protected methods of the parent class.
-
With the collection in place, testing the control is now possible. To test the control, comment out the
- attribute on the class. Otherwise, VS .NET will not be able to support the control at designtime due to the incapability to locate the designer for it. After all, the designer has not yet been implemented. At this
point, create a new Windows Forms application project and add the control to the toolbox. Draw an
control on the main form and use the - property to add three objects to the
control, as shown in Figure 8.2.
Figure 8.2. Testing the control.
With the three tabs in place, compile and run the test application. The activation of tabs is fully working at this point.
Of course, the goal of the control is to host other controls and associate those child controls with the
various tabs within the control. Although child controls can be added by hand-coding the relationships between the
controls and the tab that will host them, a custom designer is required to support this behavior during design-time.
The next section discusses the design and implementation of a custom designer for the control.
-
The goal of a control designer is to allow developers to customize the appearance of a control, add child controls, and
link event code to the events of a control. The rule of thumb when creating designers is to keep the designer simple
and to the point. If you complicate control designs, developers won't want to use your controls. The
designer adds support for drag-and-drop during design-time, as well as the capability to activate the hosted tabs
within the control similar to the Windows Forms .
The - requires the use of four services provided by the VS .NET IDE. These services include
--, , , and . Each of these
interfaces was covered in Chapter 5, "Advanced Control Development"; only the relevant methods for these services
are covered here.
The -- interface is a gateway, so to speak. This interface provides access to the underlying services
provided by the root designer. Again, the root designer is the main designer hosted by the VS .NET IDE. For the most
part, the -- interface is used to obtain services from the root designer. These services include
and .
As with other RAD environments, the toolbox is a palette of controls that can be drawn or dragged onto a form. The
provides methods for deserializing and creating the controls within the toolbox.
Changing the active selection the control currently selected in design mode requires the use of the
interface. This service not only can be used to determine which control or controls are currently
selected, but also provides the capability to change the current selection.
The designer will be responsible for responding to drag-and-drop notifications, allowing activation of the
contained s, and removing unused and unwanted properties from the control. Listing
8.4 provides the source for the designer.
Listing 8.4 Implementing the Designer
- -
- --
- -
- --
- -
- --
- ---
- ----
- -
- -
-- -
-----
--
--
---
- -
--
----
-- --
--
-- -- --
--
---
-- -
--- -
- -
- -
--
---
- -
---
- -
----
- --
-- -
- - - - -
-
--
- - -
- -
---
- -
--
- - -- -
-- --
- - -
-- -
- -
--
-
- -
-
- -
--
---
- -
--- -
- -
--
--
---
Designers represent a critical element of the control development process. Not only do designers provide for the
design-time experience, but they also are part of the serialization process for the code generated for the control.
Dissecting the designer requires looking at the various interfaces and services that the designer uses. Each of the next
sections covers a particular aspect of the designers' implementation and services used.
When a control changes during design- time, the code generated, as well as the root designer, needs to be informe d of
the changes to the control. This allows the designer to make any necessary modifications to the control it is
responsible for. In the case of the -, when a child control is added or removed, the designer
needs to know if the control belonged to the control, and if so, it updates the control. This
is also the case when an component is removed. When an is removed, the
designer needs to remove the child control of the from both the root designer and the
control itself.
The facilitates this notification process. Table 8.8 shows the events provided by the
interface.
Table 8.8. Events
Event
Description
Raised when a component is added to the designer during
design-time.
Raised when a component is in the process of being added.
Support for drag-and-drop functionality is a snap, thanks to the services provided by the .NET base class library. Line
74 of Listing 8.4 provides the overridden method . The - provides all the necessary
information to determine the location of the drop and the currently selected toolbox item. Using this information, the
toolbox item can be created using the interface and the control added to the control.
During the processing of the drop notification, the of the control is modified, and it is
necessary to inform the root-level designer that a property of the currently selected component has been modified.
Notification is necessary to ensure that the control will be properly serialized in code during code generation.
The - method is used to notify the root-level designer and code generator about the
changes taking place.
To allow for design-time activation of the hosted tabs, the method is overridden so that processing of the
mouse messages can take place. Notice the use of the constant value for testing the Windows message.
This constant value is the raw value for the message. The class - found in the
--- namespace provides a static member name for this message; however, the class is private!
Not to worry, however; using , these constant values can be retrieved. Figure 8.3 shows with the
- class selected and the value for being displayed.
Figure 8.3. Spying with ILDASM.
With the , custom tab collection, and designer in place, the final product is almost complete . This chapter
has covered a lot of new ground pertaining to control development and the implementation of a much more
sophisticated designer. At this point, extending the current control to add additional features merely requires some
imagination and extending the use of the services provided by the .NET base class library covered so far. The goal has
been to build on knowledge gained in previous chapters and to apply that knowledge in incremental steps toward the
final goal, which is to gain a solid understanding of custom control development.
-
IN THIS CHAPTER
l
Design
Implementation
The control is nearly complete. All that remains is building the - control that can be
hosted inside the control or any other derived class such as a or .
The - control is built in four stages. The first stage involves creating a soft-control .
This is used for scrolling the contents of the -. The next stage involves the creation of
a small component used to track the individual items within the - control. This component,
-, is used to store and manage information about an individual item within the -
control. Such information includes the text and image index for the item.
After the and - components have been created, work will begin on the
- control. The - control uses both the and -
components to create a graphical shortcut-style control. Each image, or -, will act as a button
allowing the user to select an item in a similar fashion to clicking a button control.
The final stage of the project involves the creation of a simple designer for the - control. The designer
will only provide the necessary support for managing the control and tracking the removal of -s
from the root designer.
-
Designing and building the - control appears to be harder than it actually is. The goal of the
- control is to mimic the basic look and feel of the list of images hosted within the Outlook shortcut
bar. These images include the Inbox, Today, Calendar, and Notes images, to name a few. Figure 9.1 shows our
- control at both design-time (on the left) and runtime (on the right).
Figure 9.1. The - control.
Scrolling
Hover effect
Scrolling support is provided by "faking" a set of scroll-like buttons in the upper- and lower-right corners of the
control. The other features are discussed throughout the remainder of this chapter.
Although the buttons appear to look and act as a standard Windows button, they are in fact just simple classes that
provide basic drawing and hit testing. One of the key concepts when building controls is to make them as light as
possible. This means that you should not waste unnecessary resources, such as window handles or GDI resources like
brushes and pens.
Implementation of the - control requires constructing the following components:
l
component
- component
- control
--
Like other soft-controls developed thus far, the has two jobs: painting and hit-testing. The first job,
painting, involves determining the orientation of the button. The can be used to represent either an
Up or a Down orientation. The orientation of the button is used to determine the style of arrow to render on the
button. Rather than using two bitmaps, or using one bitmap and rotating it, to represent the arrow, the
uses a - primitive to create the polygon for the arrow.
Note
The - object allows for a polygon, or path, to be constructed from a set of points or lines.
This path can then be filled in the same manner as a rectangle or an arc using standard graphics calls.
Developers of custom controls would do well to learn the ins and outs of GDI+.
The decision to create a soft-control rather than a full-blown -derived component is based on one simple
fact: a full control is not needed. For the to serve its intended purpose, it does not need any of the
services or implementation afforded by the base class. Sometimes figuring out what's not needed can help
to determine the proper starting point for a new component or control.
Note, however, that the could just as easily be developed as a -derived or event derived control. The only drawback to this approach is the extra resources consumed by creating an additional window
handle and any unseen overhead within the base class implementations. For die-hard ATL/C++ developers, the idea
of "don't pay for what you don't use" carries over into C# and .NET. Because the does not use or need
any features from the base class, why pay the price of using it? Implementation inheritance is like
consuming alcoholic beverages. Do it responsibly.
Because the class represents the smallest component, its implementation is shown here first. Listing
9.1 contains the implementation for .
Listing 9.1 Implementation
- -
- -
- ---
-
--
-
-
-
-
-
- -
-
-
- -
-
-
- -
-
-
-
- -
-
- - -
- - -
- - -
- - -
- - -
- - -
- -
- -
-
- - -
- - -
- - -
- - -
- - -
-
- -
--
--
- -
- - --
---
By now the basic structure of soft-controls should be ingrained in your mind. The introduces a new
- primitive: the -. To draw the arrow or triangle that represents the arrow, a -
object is constructed based on three points. These three points represent the path of the polygon to be later filled.
The orientation of the arrow is determined by the specified during the
construction of the . The methods and , found in Listing 9.1 on lines
53 and 66, respectively, handle the process of creating the points used to represent the arrow to be drawn. In
addition, the arrow is offset when the is in a pushed state. This offsetting gives the appearance of the
button control being pushed into the screen.
After the points for the arrow have been calculated, the method uses these points to construct the
- object. Notice on line 83 the - method invocation. The - method call
completes the polygon so that when the - is filled, a solid triangle will be the output.
Now that the is in place, the next step in building the - control is the construction of
the - component. The - component is used to manage information
reguarding an item within the - control.
-
Similar to the component, an - is a component that tracks various information
about the item it represents within the - control. This approach is a common theme when building
composite controls that contain items such as the control, control, or control. Each
- is capable of rendering itself in either large or small image mode and provides hit testing and
hover state feedback. When the mouse pointer is held over an image, the image is drawn in a popped-up style state
to indicate the hover state mode, as shown in Figure 9.2.
Figure 9.2. Hover state feedback.
As with other controls and components developed so far, a determination of the base class from which to begin needs
to be addressed. First, the - needs to be defined. Is it a control? No. What is the
-? An - is an object that is to be used to manage the display of text and an
associated icon or image. The - depends on its parent, the - control, for location
and mouse event processing. In addition, the - needs its parent in order to access the proper
-, large or small, to obtain the associated image to draw.
The - represents a subcomponent of the - control. This basic definition and the
requirements of the - help to establish the base class for it. Listing 9.2 contains the
implementation of the - component. A vast majority of the code deals with draw ing both the text
and the associated image.
Listing 9.2 - Component
-
-
-
-
-
-
-
---
-
-
-
-- - -
-
-
-
-
- -
- - - --
- -
- -
- -
- -
- - -
- -
--
--
--
-
--
- - -
- -
--
- - -
- -
--
- - -
-
- -
- -
- --
- -
-
-
As noted previously, most of the code from Listing 9.2 deals with drawing both the text and the image for the item . All
drawing boils down to determining the size, position, and color of what is going to be drawn. Determining the size of
the image, whether large or small, and the size of the text serve to then derive the calculations for size and placement
of the text and image.
Implementing various effects, such as a hovering effect when the mouse is over an item or a pushed effect when a
mouse click occurs, requires subtle drawing hints. In the case of hovering, a border is added to the image, and the
image itself is offset to give the appearance of movement.
Stepping through the drawing code, beginning on line 115 of Listing 9.2, the basic logic is as follows:
1.
2.
3.
4.
Calculate image rectangle. Calculate text rectangle to sit below image rectangle. Draw text. Go to 6.
5.
Calculate image rectangle. Calculate text rectangle to sit adjacent to image. Draw text. Go to 6.
6.
Draw image based on state Hover, Pushed, Normal. See on line 158.
These six steps outline the drawing logic for the - component. There is no hard-and-fast rule for
drawing other than that it should "look right." Defining that look, of course, is left to the developer of the control.
With the - component complete, the next step is to create the - control itself.
The next piece of the puzzle for the - control is implementing the container for all the components
listed so far. As with the control, the - has one primary function: managing the
subcomponents it contains. And also like the control, the - control contains a custom
collection in order to maintain and manage the set of -s associated with the control.
The - control manages one or more -s. Although the -s are
capable of drawing themselves, the - control is responsible for managing their position, scrolling, and
handling events such as mouse events.
A custom event is provided to notify any observers, such as the parent form, when a particular -
has been clicked. This custom event provides the index for the item that has been clicked on.
Again, rather than providing extraneous features, the - control features are kept to a minimum to
demonstrate the processing of building a custom control. Table 9.1 lists the properties exposed by the
- control.
Table 9.1. - Control Properties and Description
Property
Description
- - component containing a set of 16x16 images.
Before the source listing for the - control is shown, it should be noted that the listing contains the
code for a custom collection. Providing custom collections is a common theme and a task you should become
comfortable with. The relevant sections of the custom collection will be discussed following the source listing.
Without further ado, Listing 9.3 contains the source code for the - control. Following the listing is a
discussion of its implementation.
Listing 9.3 - Control
- -
- -
- --
- -
- ---
- -
-- -- -
--
-
--
- -
- ---
-- -
-
-
-
--
-
-
- - -
- -
- -
- -
- -
- -
--
- --
- -
-- --
- -
- -
- - -
- - -
--
--
---
- - - -
- - -
- -
--
- -
- -
- - -
-
- -
- -
- -
- -
- --
--
- -
- - - -
- -
-
- -
--
---
- --
- -
-
- -
--
---
- --
--
- -
- -
--
--
--
--
-
-
- -
- - - -
-
- --
---
--
--
- --
--
- -
- -
--
--
--
--
- -
- - - -
-
- -
---
--
- --
--
- -
-
-
- - - -
- -
- - -
- -
- -
- -
--
--
-- - -
---
-
- -
- -
-
--
-
-
--
-
--
--
-
- - -
-
- -
-
-
-
--
--
-- -
- -
- -
- -
--
-
-
- -
--
Understanding the code in Listing 9.3 requires breaking it down into the various responsibilities: managing
-s, handling mouse events, and providing scrolling.
- The management of items within the control is twofold. First is the custom collection that interacts with the
- control whenever an item is added or removed. Second is managing the location of the items within
the drawing area of the control itself.
The custom collection is fairly similar to the custom collection created from the control. The differences
pertain to the interaction with its parent. In the case of the - control, when an item is added to the
collection, the new item must calculate its size and the parent needs to be invalidated so that the new item will
appear. The - method, on line 398 of Listing 9.3, contains this insertion logic.
The next major task of managing -s is to determine their position within the client area of the
- control. During the drawing of each item, its calculated height is used to determine the location of
the next -. In the event that there are more items to be drawn than space is available, scrolling
buttons need to be displayed. Scrolling support is discussed later.
Drawing logic is contained within the method, on line 107 of Listing 9.3. The drawing logic is as follows:
1.
2.
Draw item.
3.
4.
5.
This set of steps is repeated until there are no more items to draw or step 5 is reached.
- Mouse events are the main means of interaction with the - control. In fact, the handling of mouse
events represents a significant portion of the code for the - control. The mouse events used are
-, -, and -.
Starting with the - event, on line 153 of Listing 9.3, the logic is as follows:
1.
2.
Is the mouse over the item in hover mode? If yes, nothing to do and return from -; otherwise,
draw the item in normal mode.
3.
Loop through visible items and hit-test. If the mouse is over an item, draw the item in hover mode, save the
item's index, and exit -.
The - event is used to provide visual feedback to the user. This visual feedback is accomplished by drawing
the item under the mouse in hover mode. That is, the item appears to be raised up from the control.
The next mouse event, -, on line 190, is used to determine whether an item or has been
hit. The - method logic is as follows:
1.
2.
Has the scroll-up button been hit? If yes, draw the in a pressed state and exit -,
else step 3.
3.
4.
Has the scroll-down button been hit? If yes, draw the in a pressed state and exit
-, else step 5.
5.
For each visible item, hit-test the item. If an item has been hit, draw the item in a pressed state and exit
-.
The final mouse event, -, on line 227 of Listing 9.3, is used to determine whether an item or
has been clicked. If an item has been clicked, the method is invoked for the current
item. The method in turn raises the event. If a has been hit, the
member is adjusted up or down depending on the that was clicked.
The scrolling functionality for the control is provided through the adjustment of the member . The
member determines which - will be drawn at the top of the control. Adjusting which
item is drawn at the top enables the illusion of scrolling to be obtained. As seen by the implementation, there is no
scrolling of the actual window contents. Rather, the item to be drawn first is adjusted.
At this point, the control is functional but not well behaved within the designer, because it will not clean up any items
it contains when the control itself is deleted. To realize all the work done so far, there is only one final step: creating a
simple designer. This final step is discussed in the following section.
--
For the - control to be considered a well-behaved control, it must clean up after itself during designtime. Without a designer, the - control will orphan -s when deleted from a form.
Orphaning of the -s happens when the - control is deleted and there is no
designer logic to destroy subcomponents associated with the - control.
The -- will take on the responsibility of destroying all -s when the
- control is deleted. Subscribing to the event of the
allows for this cleanup to take place. When the component being removed is the
- control, all the -s are destroyed. Listing 9.4 provides the implementation of the
designer.
Listing 9.4 --
-
-
-
-
-
-
-
-
-
-
--
-
--
-
--
---
----
-
- -
-- -- -
- -
-- -- --
--
- -
-
- -
--
---
--
--
--
---
--
Again, the only function of the designer is to remove orphaned -s during the removal of the
- control. The event handler, located on line 18 of Listing 9.4, accomplishes
this task. To properly clean up during the removal of the - control, the designer iterates through the
- collection of the - control and has the -- service destroy each component. It is
necessary to use the -- service to destroy these components in order for the components to be
removed from the Icon Tray area as well as remove all generated code from the method that
relates to the now deleted components.
With the completion of the designer, the process of building a basic control is finally finished.
-
Testing the final control is a victory that should be savored. The various controls, components, and designers for the
control and its related controls demonstrate the basic requirements for creating custom controls for
Windows Forms. Figure 9.3 shows the test application displaying a -- indicating which item was clicked on.
Figure 9.3. The client.
To test the control, compile the solution containing all the code developed thus far. This will result in the dll
being created. This dll houses all the components, controls, and designers developed to this point.
Creating the client application requires starting a new Windows Forms project and adding the and
- controls to the Toolbox. Adding the controls to the Toolbox requires customizing the Toolbox and
browsing to the location of the newly compiled dll.
To create the test application, drag the control onto the main form. Next, drag an - control
onto the control. A new tab is automatically created.
Associate an - component with the - control containing a set of large images. The images can
be of anything. The images shown in Figure 9.3 were borrowed from Microsoft Outlook.
After the - has been associated with the - control, use the - property editor to add
-s and set the text and image index for them. Listing 9.5 shows the result of the test client code.
Listing 9.5 Client
-
-
-
-
-
-
-
-
--
-
---
-
- -
-
-
-
-- ---
-
---- -
-- -
-- -
-- -
-- -
-- -
-- -
-- -
-- -
- -
- - -
-- -
-- --
--
---
--- --
- -
- -
- -
-- -
---- --
----
- -
-- ------
-- --
- -
-- --
-- --
-- --
-- --
-- --
-- --
-- --
--
--
- -
- -
-- -
-- ---
-- -
--
-------
--- --
-- ---
-- ---
---
--
--
--
--
--
--
--
--
---
--- --
-- -
-- -
-- -
---
--
--
---
- -
- --
- ---
- -
--
--
--
--
--
-- -
--
--
--
-- --
--
-- -
--
-- -
-- -
- -
-- ---
---
---
- -
---
- - -
---
-- - -
Listing 9.5 is 99% generated during the design process of the test application. The only hand-coded method is the
- event handler on line 163, and even the stub for this was genereated by doubleclicking on the - control.
The - event handler uses the -- parameter to get the text of
the - item and displays a message box stating which item was clicked on. Of 168 lines of code, only 2
lines have to be typed. Isn't code generation wonderful?
These last four chapters have covered a lot of ground. The only magic to creating custom controls is imagination,
creativity, and desire. At this point, the nuts and bolts of control development have been explored, and all that
remains is to add features to both the controls and the designers developed so far. Using Chapter 5, "Advanced
Control Development," as a reference, you should be able to extend the to add new features or create a
new custom control from scratch. The best way to learn to develop controls is to write them. Look at the various UI
elements found in VS .NET and start creating controls.
IN THIS CHAPTER
l
Licensing
The last major area of control development has to do with distributing controls to customers. Control deployment
consists of three basic topics:
l
Licensing
Each of these topics requires assessing how, when, and where controls will be used.
-
Almost every type of software application includes some type of licensing agreement that governs the rights and uses
of the software. In addition to the licensing agreement, a product key may also be required during th e installation and
setup for the software package. Licensing is used to govern the rights of the user with respect to the use of a product
and how that product is deployed, used, and even distributed.
.NET provides a licensing system for components such as the custom controls developed in this book. The licensing
system is based on the COM version of licensing providing support for both design-time and runtime licensing for a
component. By licensing components, a vendor can govern the use of the component and help ensure that the user of
the component has the proper licensing for development and distribution of the component.
Note
For those of you not familiar with COM, don't worry it's not important anymore. COM is mentioned only
because the current licensing system is based on COM licensing. There are plenty of texts covering every
grueling aspect of COM if you feel inclined to investigate. Personally, after many years of COM-based
development, I'm glad to see it go.
- -
The type of license depends on the expected use of a component. Demo versions of a component, such as the
, may be distributed free and require no license for use. Generally, the demo version has only a sub set of
the functionality available for use, along with the restriction that the component cannot be used in a commercial
application without purchase of the component.
Another option for licensing is a one-time fee for the product such as when buying a software package. The software
generally includes an EULA, or End User License Agreement. The terms and conditions of the EULA will define the
rights of the user with regard to the software.
Specialized server-based products often require the purchase of a Per- CPU license, in which the license fee is base d on
the number of CPUs within the server hosting the software or component. This style of licensing is generally found in
server applications such as databases, email systems, and Web servers.
When considering a licensing system, try to keep in mind that the licensing should be unobtrusive and effective for
both parties. Creating a complicated and intrusive licensing system often turns away potential customers who vie w the
licensing schema as an unnecessary burden.
- The .NET framework provides for both simple file-based licensing and custom licensing that can be implemented as
needed to fulfill the licensing requirements of the component author. In the simplest form of licensing, a small te xt file
is used to contain a single line of text regarding the license of the component. This scheme provides no real security
or means of enforcement for the license. With custom licensing, the enforcement and use of the component and
licensing are governed by the implementation of the custom - derived class. Custom licensing is
covered in the "Providing Custom Licensing section" of this chapter.
To support licensing, the .NET Framework provides the classes listed in Table 10.1
Table 10.1. Licensing Classes
Class
Description
Abstract base class of all licenses.
-
-
Specifies when a licensed component can be used.
-- Specifies a design-time licensing context.
-
Abstract base class for all license provider classes.
- Attribute used to specify the - for
-
-
-
Implementation of a - that is
consistent with the .NET Framework licensing system.
Exception thrown when an error is encountered when
trying to validate or grant a component's license.
The .NET licensing classes provide for simple licensing or serve as a starting point for developing custom licensing
schemas. The next two sections cover simple licensing using the - class and creating a
custom-licensing schema by inheriting from - to implement the licensing validation.
- -
Creating a simple licensed component requires very little effort and entails only the four following steps:
1.
2.
3.
4.
The LIC, or license file, is a simple text file containing a single text string in the following format:
- -
Here, is the fully qualified name of the licensed component. In addition, the LIC file should have the same
name as the component. Using the control, the LIC file would be as follows:
Filename: -
Contents: - - - .
The LIC file must be located in the directory containing the assembly of the licensed control. Figure 10.1 shows the
LIC file for the loaded in VS .NET.
Figure 10.1. The LIC file.
To implement the simple licensing for the , the source for the control needs to be updated to add the
- and the creation and disposal of the license. Listing 10.1 shows the modifications to
the source necessary to support basic LIC file based licensing.
Listing 10.1 Licensed
-
-
-
-
-
---
-
-
- -
-
- -
--
-- --
--
--
-- ---
-
-
- -
- -
-- --
---
--- --
-
Rather than relisting the entire source, Listing 10.1 shows only the modifications to the
needed in order to implement licensing. The first modification is the addition of the - on
line 14. This attribute is used to specify the type of license provider to create when validating the license for the
component. The .NET Framework supports simple file-based licensing by providing an implementation of the
- abstract base class in the form of -.
With the attribute in place, the next step is to declare a private member of type -. The declaration of this
member is found on line 18 of Listing 10.1. Remember that the - class is an abstract base class for all license
types. To obtain a valid license, the - is used to validate the license for the control. This validation
can be found within the constructor for the starting on line 20. If the validation fails, the
- throws a -. When this exception is not caught, an instance of the control will
not be created.
In fact, using VS .NET to create a Windows application and hosting the on a form can demonstrate this
point. To make the license validation fail, remove the LIC file from the directory containing the
assembly. Next, drag the from the Toolbox onto the form. VS .NET generates a message box stating
that a valid license for the control could not be created, as shown in Figure 10.2.
Figure 10.2. License validation failure.
Licenses created by a component should be disposed of when the component itself is disposed of. Line 28 of Listing
10.1 shows the -- method of the disposing of the contained -.
To see the results of the licensing implementation, create a new Windows application and add the to the
Toolbox and then to the form. If the LIC file is in place, the will be created without any noticeable
difference with respect to other test applications created to this point. However, there is a difference, and that
difference is in how the licensing is enforced and used.
Every Windows application contains a - file, located in the project directory. This license file contains
the necessary licensing information for all licensed controls used by the application. This licx file is compiled as a
binary resource and embedded within the output assembly. This allows the application to validate all licenses and does
not require the various LIC files for licensed components to be distributed along with the application. Figure 10.3
shows the - file for the test Windows application.
Figure 10.3. The - file.
The simple file-based licensing schema does not provide much in the way of security or the enforcement of prop er use
of the control based on an agreed license. To provide a more robust licensing schema, it's necessary to create a
custom licensing solution.
- -
To provide a custom licensing implementation, at least two classes need to be created. These two classes will
represent the license and the license provider implementation. The licensing scheme is implemented by deriving from
the - base class and implementing the - method. In addition, a custom license class
needs to be created by deriving from the abstract - class and implementing the - property and the
-- method.
Custom Licensing implementations vary based on need. For the purpose of demonstration, a file-based custom
provider will be developed. Other implementations might make use of a Web Service or some other verification
means, such as registry entries or a product key entered by the developer. In development of a custom provider,
there is no steadfast rule for "how" only that the licensing system makes sense for the product.
To demonstrate a custom license provider, the will be licensed by using an xml file that contains an
entry for the control along with a key value to be used as the license key. This xml file will be named
--- and located within the same directory as the assembly. Listing 10.2
shows the xml file to be used as the license file.
Listing 10.2 Custom License XML File
-
--
- -
--
The next step in providing a custom licensing schema is to provide the implementation for a license and license
provider. Listing 10.3 shows the implementation for the -- and -- classes.
Listing 10.3 Custom License Provider Source
-
-
-
-
-
-
--
-
-
-
-
-
-
-
-
---
- -
-- -- --
-
-
--
-- -- -
-
--
- -
-
--
-- -- -
- -
-
- - -
-
-
--
-
- --
- -
- -
-
- -- -
-
---
- -
- -
- --- --
- -
---
--
-
-
- -- -
-
--
- -
- -- -
-
- --
-
- - -
The verification of the license takes place in the - method of the -- class; this
method begins on line 37. The location of the license key depends on the current --. The
- can be either or -. During runtime, the license key should be located within the
current - object because the application's file is part of the assemblies manifest. Remember
that the file will contain the necessary license information for validation during execution so that the
component license file does not need to be distributed along with the application.
During design-time, the -- attempts to locate the custom xml-based license file located in
the same directory as the control assembly. If the license key is valid, the license information is stored within the
current - object, and an instance of the -- is returned. Otherwise, the
-- throws an exception of type -, if allowed; otherwise, it returns null.
Note
For the purpose of the example, a non-empty string will suffice for the licensing requirements. In addition,
the license key is extracted using an expression into the xml file. The subject of XML and are
beyond the scope of this text. However, information about expressions and their usage is available
from www.w3c.org.
To use the new custom licensing, the - attribute of the should be updated as shown in
Listing 10.4.
Listing 10.4 Adding Licensing Support to the
--
---
-- ---
No other changes to the class are necessary to make use of the new custom licensing implementation.
The licensing support provided by the .NET Framework allows for custom licensing schemas to be implemented with
little effort, thus allowing vendors to enforce proper use of their intellectual property.
- -
Internet Explorer (IE) supports the tag for hosting classic ActiveX controls. In .NET, the tag can be
used to also host Windows Forms controls and provide accesses to the properties of the hosted control. For those of
you familiar with hosting ActiveX controls, the syntax for the tag will seem very familiar. The only difference
is the attribute used to identify the control to be created. The general form of the tag is as follows:
--
IE places the following constraints when hosting .NET controls within the browser:
l
The assembly must be in the current virtual directory or in the Global Assembly Cache.
Permissions for the virtual directory must be set to "Scripts only." Setting permissions to "Scripts &
Executables" will not allow the control to be hosted.
As long as these conditions are met, hosting a control is a fairly simple process. Listing 10.5 shows a basic aspx page
for hosting the developed earlier.
Listing 10.5 Hosting the
-- -
-
-
- -
- ---
--
- -
--
-
The output produced from Listing 10.5 is shown in Figure 10.4.
Figure 10.4. Hosting the in Internet Explorer.
-- -- --Deploying any .NET application, or any .NET assembly for that matter, requires determining where the physical
assemblies will be installed. One of the major advantages of the .NET platform is the capability for xcopy-style
deployment of applications and components.
Note
For those of you who don't remember the days of DOS, xcopy-style deployment means to simply copy the
application and its dependencies to a target destination. No registration of components is required, except
changes to the operating system itself. In Microsoft's words, it just works.
With classic COM, all COM servers require registration before the COM object can be used by client applications. This
registration process involves creating entries within the registry that identify the location of the COM server along with
the various co-classes, Prog-Id's, and version information. The various entries are used by the COM runtime to locate
and create an instance of a particular COM server co-class.
With .NET, there is no need to register assemblies for use because the runtime searches specific locations to locate
the required assemblies for any given .NET application. The runtime first searches the directory containing the client
application and then, if it's not found, continues the search using the system environment variable and the
Global Assembly Cache.
The question still remains, should the assemblies be located within the application directory or within the Global
Assembly Cache? The answer to this question depends on the intended use of a given assembly. Application-specific
assemblies should be stored within the application's home directory, whereas system-wide assemblies, or assemblies
that will be used by multiple applications, might be better located within the GAC. In addition, to provide side-by-side
versioning of assemblies, GAC deployment is necessary. The next section explores component versioning and
describes side-by-side versioning.
-- -
Versioning ofcomponents, or DLL hell as it is often referred to, has always proven to be somewhat troublesome in
nature. One of the design goals of .NET was to enable assemblies to run side-by-side based on version. This
versioning system allows assemblies to be updated without breaking existing clients who depend on an earlier version
of the assembly.
The only caveat about supporting versioning is that the versioned assembly must be signed. That is, a public-private
key pair that identifies the assembly must be provided. The process of signing an assembly is covered in the .NET SDK
documentation, as well as other books, including C# and the .NET Framework , by Sams Publishing.
This chapter brings to a conclusion the process of designing, creating, and deploying custom controls. There's a saying
that imitation is the sincerest form of flattery. With the number of vendors providing custom UI elements to mimic
those found in the latest Microsoft products, there must be something to that old saying. To expand your own
knowledge of building custom controls, it's often helpful to pick a particular control and try implementing your own
control that looks and feels like the original. Examples for controls can be found on codeguru.com, on
codeproject.com, and in any application that offers some UI element you find interesting. As with all endeavors in life,
practice makes perfect.
This is the last chapter, but the following appendix, " Extender Providers," offers an interesting look at dynamically
extending controls. Extenders can be used with your newly created custom controls or to extend common controls.
IN THIS CHAPTER
l
Although not a control, Extender Providers allow for properties to be added to .NET components. The ToolTip
component is an example of an Extender Provider. The ToolTip component allows a ToolTip window to be associated
within one or more controls on a form. The ToolTip component adds the property to each control on the
form. This property is then displayed in the Property Browser when a control is selected.
To see how to implement and use Extender Providers, two separate components will be created. The first component
will be used to highlight a control when the mouse passes over it. This Extender Provider will enable the highlight to
be specified during design-time. In addition to this first extender, a second extender will be implemented to provide
feedback for menu options. This second extender will demonstrate how to apply extenders to classes such as the
component that are not derived from the base class.
Extender Providers are created by implementing the interface. This interface has only a single
method: . This method is used to determine whether the provider can extend
the target object. Generally, Extender Providers are designed to work only with specific types of components as in the
case of the ToolTip Extender Provider. The ToolTip will only add the property to -derived objects
and will not extend s.
Adding properties to objects being extended is twofold. First the Extender Provider must use the
attribute. This attribute is applied to the Extender Provider class. Listing A.1 shows an example based on the ToolTip
extender provider.
Listing A.1 Defining an Extender Provider
--
The attribute takes two arguments for its constructor. The first argument is the name of the
property to provide. In the case of the ToolTip component, the property name happens to be . Note that the
name of the property being provided does not have to be the same as the name of the Extender Provider. Listing A.2
shows the start of the component that will be developed as our first foray into Extender
Providers.
Listing A.2 The Class Description
--
- -
-
The will only extend controls hosted on a and will not provide a border to a derived control. This brings up the second argument to the attribute. In addition to the need to
specify the name of the property to be provided, the base class or type of target being extended needs to be specified.
In the case of the ToolTip and , the target object must be of type .
With the basics of the in place, you now need to understand how the property is added to
the targets. The properties are provided via a set of method calls. These method calls allow for obtaining and setting
the property value. The syntax for these methods is as follows:
is replaced with the name specified within the . Notice that the
properties are provided via method calls and not the property syntax. This is because the Extender Provider requires
an additional parameter: the object associated with the property. When a ToolTip extender is placed on a , that
single ToolTip extender provides the ToolTip property to every control on the . Hence only a single instance of
the ToolTip is used to extend all the controls on the .
For a single extender to service all controls on the , the extender needs to track the provided property or
properties for each control. This association can be accomplished by using a hashtable to map the control to its
associated property. Listing A.3 extends the to demonstrate this.
Listing A.3 Next Step in the
--
-
-
- -
- -
The has now been updated to associate the target object with a specified color. The
methods and on lines 11 and 15, respectively, demonstrate how to expose the
provided property. Again, the reason for this approach is that there exists only one instance of the extender and that
extender will service all controls within the current .
During design-time, the property will be added to the list of properties available to the selected control. Figure A.1
shows the Property Browser with the property added to the selected .
Figure A.1. The extended property shown in the Property Browser.
Notice the icon shown in the Icon Tray in Figure A.1. Extender Providers can be added to
the Toolbox and used during design-time. In addition, notice the name of the property:
. This property name comes from the fact that there may be another
added to the form. A way to distinguish which extender is hosting the property is
necessary.
Note
To add an extender to the Toolbox, create a .NET dll assembly containing the extender. Next, customize
the Toolbox and load the .NET dll assembly just as you would any other assembly. The extender will then
be available in the Toolbox. This is how the ToolTip extender is provided.
--
-
-
- -
-
-
- -
- -
--
- -
- -
-
-
--
--
- - -
- - -
To understand how the works, locate the method located on line 26.
This method serves two purposes. First, if the color passed in is uninitialized, the target object is removed from the
collection and the mouse event handlers are also removed. Otherwise, the color is associated with the target and the
mouse events are monitored. Now, whenever the mouse enters a control being extended by the
, a border of the specified color is drawn around the control.
In addition to drawing a border around the control, when the mouse moves out of the control the border will be
removed. Figure A.2 shows the in action.
The association between the extender and a target happens when the provided property, in this case ,
is set within the Property Browser for a given control. Remember that when an extender is added to a form, all target
controls will have the specified property added to them. However, the association occurs only if the property value is
set. If the property value is deleted or nulled out, the association will also be removed.
Extender Providers are flexible mechanisms for adding properties to .NET components. Any object in .NET can be an
Extender Provider, even a control.
- As stated previously, any component can serve as an Extender Provider. All that is necessary is to implement the
interface, add the necessary attributes, and implement the /
methods for the property. Creating a control that implements the interface is the same as it
was for the sample described previously. To demonstrate this, the next Extender Provider
will be derived from a control. In addition, this new Extender Provider will only extend s to provide a
property. Figure A.3 shows the completed displaying some help text for the
selected .
Figure A.3. A control implementing .
--
- ---
--
- -
- -
- -
- - -
Extender Providers are a useful idea and an easy way to add additional properties to existing controls. Other uses for
extenders include adding layout management such as Flow, Grid, or GridBag layout to a at design-time. Of
course, you'll also have to implement the layout logic, but using extenders allows for assigning various layouts to
different elements of a form. The uses for extenders are limited only by your imagination and necessity.