Tải bản đầy đủ (.pdf) (69 trang)

Expert VB 2005 Business Objects Second Edition phần 8 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.54 MB, 69 trang )

Dim principal As New PTPrincipal(identity)
Csla.ApplicationContext.User = principal
End If
Return identity.IsAuthenticated
End Function
Notice that PTIdentity has a factory method; in fact, it is derived from Csla.ReadOnlyBase and
so is a full-fledged business object. The
username and password parameters are passed to the
PTIdentity object’s factory method. Of course, the factory method calls the data portal, which ulti-
m
ately invokes the
D
ataPortal_Fetch()
m
ethod in
P
TIdentity
.
As you’ll see, that method validates
the credentials against the database.
With a
PTIdentity object created, its IsAuthenticated property can be checked to see if the
user’s credentials were valid. If they were valid, the identity object is used to create a new
PTPrincipal object, and that object is set to be the current principal by using the
ApplicationContext object’s User property, as discussed in Chapter 4:
Dim principal As New PTPrincipal(identity)
Csla.ApplicationContext.User = principal
If the credentials weren’t valid, then the current principal value is left unchanged.
In any case, the
IsAuthenticated value is returned as a r
esult so that the UI code can take


appropriate steps based on whether the user was successfully logged in or not.
Logout
The Logout() method is much simpler
. All it needs to do is ensur
e that the current principal value
is set to an unauthenticated principal object—that means a principal object whose identity object
has an
IsAuthenticated property which returns False:
Public Shared Sub Logout()
Dim identity As PTIdentity = PTIdentity.UnauthenticatedIdentity
Dim principal As New PTPrincipal(identity)
Csla.ApplicationContext.User = principal
End Sub
To achiev
e this result, an unauthenticated
PTIdentity object is created b
y calling a special fac-
tory method for that purpose. That identity object is then used to create a new
PTPrincipal object,
and it is set as the current principal by setting
ApplicationContext.User.
The reason for creating an unauthenticated
PTPrincipal rather than an unauthenticated
GenericPrincipal (a built-in .NET type) is to support anonymous or guest users. Recall from
Chapter 4 that the data por
tal will only accept principal objects that subclass
BusinessPrincipalBase when custom authentication is used. This means the data portal will
throw an exception if a
GenericPrincipal is passed to the application server. So if the application
is to support anonymous (i.e., unauthenticated) users, then the principal must be an unauthen-

ticated
PTPrincipal, as shown here.
PTIdentity
As you’ve seen, PTPrincipal isn’t overly complex. It leaves most of the work to PTIdentity, including
implementing the
IsInRole() functionality and v
erification of the user’s credentials.
PTIdentity is a read-only object, and so it inherits from Csla.ReadOnlyBase. It is also a .NET
identity object, so it must implement
System.Security.Principal.IIdentity:
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION460
6315_c08_final.qxd 4/7/06 2:00 PM Page 460
<Serializable()> _
Public Class PTIdentity
Inherits ReadOnlyBase(Of PTIdentity)
Implements IIdentity
Being a read-only root object, PTIdentity follows the appropriate template from Chapter 7,
including
Business Methods, Factory Methods, and Data Access regions. It doesn’t implement an
Authorization Rules region because it has no authorization rules.
Business Methods
Because PTIdentity implements the IIdentity interface, it is required to implement the
AuthenticationType, IsAuthenticated, and Name properties:
Private mIsAuthenticated As Boolean
Private mName As String = ""
Public ReadOnly Property AuthenticationType() As String _
Implements System.Security.Principal.IIdentity.AuthenticationType
Get
Return "Csla"
End Get

End Property
Public ReadOnly Property IsAuthenticated() As Boolean _
Implements System.Security.Principal.IIdentity.IsAuthenticated
Get
Return mIsAuthenticated
End Get
End Property
Public ReadOnly Property Name() As String _
Implements System.Security.Principal.IIdentity.Name
Get
Return mName
End Get
End Property
These are all read-only properties and are quite straightforward. Also, because it is a subclass
of
ReadOnlyBase, the class must implement the GetIdValue() method:
Protected Overrides Function GetIdValue() As Object
Return mName
End Function
Finally, the code in PTPrincipal requires that PTIdentity implement an IsInRole() method to
determine whether the user is in a specified role:
Private mRoles As New List(Of String)
Friend Function IsInRole(ByVal role As String) As Boolean
Return mRoles.Contains(role)
End Function
This method is Friend in scope because it is only intended for use by PTPrincipal. All it does is
determine whether the specified rule exists in the list of roles for the user. That list is populated in
DataPortal_Fetch(), assuming
the user


s credentials are valid.
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION 461
6315_c08_final.qxd 4/7/06 2:00 PM Page 461
Factory Methods
Like all read-only root objects, PTIdentity implements a factory method so it can be created.
In fact, it implements two factory methods: one to verify a set of credentials, and one to return
an unauthenticated identity object to support the concept of anonymous users.
The
UnauthenticatedIdentity() factory method is simple:
Friend Shared Function UnauthenticatedIdentity() As PTIdentity
Return New PTIdentity
E
nd Function
Because mIsAuthenticated defaults to False, mName defaults to an empty value, and mRoles
defaults to being an empty list, simply creating an instance of the object is enough to provide an
unauthenticated identity object with no username and no roles.
The
GetIdentity() factory, on the other hand, creates a Criteria object and calls the data
portal so that the
DataPortal_Fetch() method can verify the supplied username and password
parameter values:
Friend Shared Function GetIdentity( _
ByVal username As String, ByVal password As String) As PTIdentity
Return DataPortal.Fetch(Of PTIdentity)(New Criteria(username, password))
End Function
This is a standard factory method to retrieve an object populated from the database.
Data Access
The DataPortal_Fetch() method actually performs the authentication: verifying the user’s creden-
tials against the values in the database. In a real application, you should store passwords as hashed
or encrypted values; but for a sample application, it is simpler to store them as clear text.

The
Criteria object passed fr
om the
GetIdentity() factor
y method to
DataPortal_Fetch()
is the most complex in the application:
<Serializable()> _
Private Class Criteria
Private mUsername As String
Private mPassword As String
Public ReadOnly Property Username() As String
Get
Return mUsername
End Get
End Property
Public ReadOnly Property Password() As String
Get
Return mPassword
End Get
End Property
Public Sub New(ByVal username As String, ByVal password As String)
mUsername = username
mPassword = password
End Sub
End Class
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION462
6315_c08_final.qxd 4/7/06 2:00 PM Page 462
Of course, “complex” is a relative term. Obviously, there’s nothing overly complex about a class
that exposes two read-only properties. But this illustrates how the

Criteria object concept can be
used to pass complex criteria to the
DataPortal_XYZ methods as needed.
The
DataPortal_Fetch() method itself accepts this Criteria object and calls the Login stored
procedure created in Chapter 6:
Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)
Using cn As New SqlConnection(Database.SecurityConnection)
cn.Open()
Using cm As SqlCommand = cn.CreateCommand
cm.CommandText = "Login"
cm.CommandType = CommandType.StoredProcedure
cm.Parameters.AddWithValue("@user", criteria.Username)
cm.Parameters.AddWithValue("@pw", criteria.Password)
Using dr As SqlDataReader = cm.ExecuteReader()
If dr.Read() Then
mName = criteria.Username
mIsAuthenticated = True
If dr.NextResult Then
While dr.Read
mRoles.Add(dr.GetString(0))
End While
End If
Else
mName = ""
mIsAuthenticated = False
End If
End Using
End Using
End Using

End Sub
The method uses standard ADO.NET data access code. It opens a connection to the database
(calling a
Database.SecurityConnection helper to get the connection string for the security data-
base). Then it sets up a
SqlCommand object, loading it with the Username and Password properties
from the
Criteria object.
When the command is executed, the resulting data reader object will either contain data or it
won’t—if it contains data, then the user’s credentials were valid, otherwise they were invalid. Given
v
alid cr
edentials
, the object’s fields are loaded with data from the database, and the list of roles are
loaded into the mRoles collection:
mName = criteria.Username
mIsAuthenticated = True
If dr.NextResult Then
While dr.Read
mRoles.Add(dr.GetString(0))
End While
End If
On the other hand, if the credentials were not valid, the object’s fields are set to appropriate
values for an unauthenticated identity:
mName = ""
mIsAuthenticated = False
mRoles.Clear()
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION 463
6315_c08_final.qxd 4/7/06 2:00 PM Page 463
The end result is a populated PTIdentity object: either authenticated or unauthenticated.

Either way, the object is returned to the client where it can be used to create a
PTPrincipal object
to support authorization activities within the business objects and the UI.
Conclusion
This chapter implemented the business objects designed in Chapter 6, using the templates and
c
oncepts discussed in Chapter 7. The result is
P
rojectTracker.Library
,
the business layer for the
sample
ProjectTracker application, including the following:

Project
• ProjectResources
• ProjectResource
• Resource
• ResourceAssignments
• ResourceAssignment
• Assignment
• RoleList
• Roles
• Role
The library also includes classes to support custom authentication:

PTPrincipal
• PTIdentity
This business
library will be used to create Windows Forms, Web Forms, and Web Services

interfaces in the next three chapters.
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION464
6315_c08_final.qxd 4/7/06 2:00 PM Page 464
Windows Forms UI
Up to this point, the focus has been on the business layer of the application. Chapters 6 through 8
walked through the design and creation of business objects and logic. Now let’s shift gears and look
at how a user interface can be created based on those business objects. This chapter will describe a
Windows Forms interface.
Windows Forms is a flexible technology that can be used to create a great many types of user
interfaces, as evidenced by the fact that there are entire books on Windows Forms UI development.
I won’t rehash that sort of material in this book; what I want to focus on here is how to make effec-
tive use of business objects and collections to create Windows Forms displays and entry forms.
When creating the CSLA .NET framework, quite a bit of effort was spent to allow business
objects to support Windows Forms development. The business objects themselves are focused
on modeling the business behaviors described in the use cases from Chapter 6. At the same
time, the fact that they inherit from CSLA .NET base classes means they possess quite a few
important features that are very useful for creating a Windows Forms UI. Most important is the
support for Windows Forms data binding. Although you could certainly write your own code to
move the data between properties of business objects and the controls on a form, it’s far easier
to use data binding whenev
er possible.
The user interface is centered around user controls. Each form will be created as a user control,
rather than a
Form object. That way, each form can be dynamically loaded into many styles of inter-
face, including the multiple document interface (MDI), multipane user interfaces such as Microsoft
Outlook, the single document interface (SDI), and other styles. The style in this chapter uses a sin-
gle
Form object that hosts the controls, showing just one at a time. This provides the user with a
simple
, easily understandable inter

face.
The impor
tant thing is that the chapter will illustrate the use of user controls, and how to
dynamically host them. You can easily adapt this code to implement a wide variety of different UI
styles.
But above all, my focus in this chapter is to show how easy it is to create an interface, given that
the business objects alr
eady implement all the business logic, including v
alidation, manipulation,
author
ization, and data access
. The result is that there’s only minimal code in the UI, and that code
is focused only on user interaction.
The chapter starts by laying out the basic design of the interface, and then walks through the
common behaviors of the menu, status display, and authentication. Once that’s done, I’ll discuss
the creation of forms to view and edit data using the
DataGridView and detail controls. I’ll also show
how to create and use dialog forms.
Interface Design
The UI application can be found within the ProjectTracker solution. The project is named PTWin.
The design of the
PTWin inter
face is that of a single main form with a menu and status bar. This
465
CHAPTER 9
■ ■ ■
6315_c09_final.qxd 4/7/06 2:12 PM Page 465
main form dynamically loads user controls and displays them to the user. Figure 9-1 shows what the
main form looks like.
Notice that the menu bar includes menus that deal with projects, resources, roles, and authen-

tication.
When the user chooses a menu option, a user control is dynamically loaded into the main
area of the form. Figure 9-2 shows the application while the user is editing a project.
466 CHAPTER 9 ■ WINDOWS FORMS UI
Figure 9-1. Appearance of the main form
Figure 9-2. E
diting a pr
oject
6315_c09_final.qxd 4/7/06 2:12 PM Page 466
Of course, there are some dialog windows used to collect input from the user as well, but the bulk
of the application’s functionality centers around the use of user controls hosted by the main form.
Table 9-1 lists the forms and controls that make up the interface.
Table 9-1. Forms and User Controls in PTWin
Form/Control Type Description
MainForm Form The main form for the application
LoginForm Form A login dialog to collect user credentials
RolesEdit Control Allows the user to edit the list of roles
ProjectSelect Form A dialog prompting the user to select from a list of projects
ProjectEdit Control Allows the user to view, add, or edit a project
ResourceSelect Form A dialog prompting the user to select from a list of resources
ResourceEdit Control Allows the user to view, add, or edit a resource
It is very important that you understand that all the data binding and business functionality
covered in this chapter works
exactly the same with regular forms as it does with user controls. I am
using user controls in this chapter because I think it is a best practice for Windows Forms UI design,
but this has
no impact on the way data binding is used to create the UI against the business objects
created in Chapter 8.
The user control approach taken in this chapter gives you a great deal of flexibility. You can
host the user controls, as shown in this chapter, you can host them in child forms in an MDI inter-

face
, or y
ou can host them in panes in a multipane interface. In short, by creating your “forms” as
user controls, you gain the flexibility to use them in many different types of UI design.
User Control Framework
Dynamically loading a user control isn’t difficult. The code needs to follow this basic process:
1. Create the control.
2. Add the control to the form’s Controls collection.
3. Set the control’s properties for size/position.
4. M
ake the contr
ol visible (
Visible = True).
5. S
et the contr
ol
’s z-order (
BringToFront()).
This is simple enough—ho
w
ev
er
, integr
ating the user contr
ols into the main form display
nicely requires some extra work. In particular, the UI in this chapter supports the following:
• A Documents menu
• Notification when the user logs in or out
• Bringing an existing control forward when appropriate
• Centralized status text and cursor handling

Let’s quickly discuss what I mean by each of these bullet points. If you look at Figure 9-1, you’ll
notice that there’s a Documents item on the menu bar, but it’s disabled. In Figure 9-2, it’s enabled.
This is because ther
e’s now a document (user control) loaded in the application. In fact, multiple
documents can be loaded at the same time
, and this Documents menu allo
ws the user to switch
between them.
CHAPTER 9 ■ WINDOWS FORMS UI 467
6315_c09_final.qxd 4/7/06 2:12 PM Page 467
■Note This application uses a Documents menu rather than a Windows menu because the menu allows the
user to switch between various documents, not between windows. If you were creating a user interface in which
t
he user chooses to display or arrange different windows, you would name the menu “Windows.”
Both figures also show that the user is logged in with the name rocky, and that there’s a Logout
button available on the menu bar. Look back at Figure 9-2 and notice how the user is allowed to edit
the fields in the form. Now look at Figure 9-3, in which the user is
not allowed to edit any of the fields.
The reason for this is that the user isn’t logged in. This is clearly shown in the menu bar, which
now has a Login button instead of a Logout button.
To make this authorization behavior work, the main form must be able to notify all the
loaded user controls when the current user logs in or out. That way, each user control can enable
and disable its contr
ols based on the authorization properties of the business object being edited
by the form. The hard work is actually handled by the
ReadWriteAuthorization control created in
Chapter 5. Still, each user control must be notified about the fact that the user logged in or out so
that the authorization code can be triggered.
I
f the user has a number of documents open in the application, he can only see the one in

front—the active document. He could easily try to open the same document a second time, and
this should result in the already open document being brought to the front to be the new active
document.
For instance, suppose the user opens project A. Then he opens some other projects and
resources, so project A is no longer active. Then suppose the user again tries to open project A.
In that case, the application won’t open a
new document—rather, it will find the already open
document for project A and will make it the active document.
Finally, as the user interacts with a document, many things may happen, some of which can
take a while
.
The user may load or save data, start a complex computing task, or any number of
CHAPTER 9 ■ WINDOWS FORMS UI468
Figure 9-3. Vie
wing a project
6315_c09_final.qxd 4/7/06 2:12 PM Page 468
things that may take some time. When this happens, the main form’s status bar should show text
telling the user what is going on, and the mouse cursor should change to indicate that the appli-
cation is busy.
It is not good to write code in every user control to handle the details of the Documents menu.
This code must detect login/logout activity, avoid duplicate documents, and display status to the
user. That is all plumbing code that should be written once and reused by user controls.
Although my intent with this chapter isn’t to create a full-blown Windows Forms UI framework,
t
hese issues must be addressed for a basically decent user experience.
User Control Design
The user will primarily interact with user controls hosted within the main form. In Visual Studio,
each user control is really just like a regular form. Visual Studio even provides a user control designer
surface
, which y

ou can use to create the user control just like you would normally create a form.
In order to support the features discussed in the previous section, each user control needs
some common functionality. To provide this functionality with the minimum amount of manual
coding, the
PTWin project includes a WinPart control. Each user control inherits from WinPart, rather
than directly from
UserControl.
The
WinPart base control implements behaviors common to all user controls that are to be
hosted in the main form, including the following:
• Overrides for common
System.Object methods
• Event notification for the pr
ocess of closing
• Event notification when the current user’s principal object is changed
By inheriting from
WinPart, a user control can often include no extra code beyond a simple
GetIdValue() method, which must be implemented to return a unique identifier for the instance
of the user control. In most cases, this method simply returns the business object being edited by
the for
m.
All other code in a typical user control centers around user interaction—dealing with button
clicks, text changes, and so forth.
Application Configuration
The application needs to provide some basic configuration information through the application’s
configuration file.
I
n the client application configur
ation file, you can either provide connection strings so that
the application can inter

act with the database dir
ectly
, or y
ou can configur
e the data por
tal to com-
municate with a remote application server. The basic concept here was discussed in Chapter 4
when the channel adapter implementation was covered. Recall that the data portal supports three
possible channels: r
emoting, E
nterprise Services, and Web Services. You can create your own chan-
nels as well if none of these meet your needs.
In Chapter 1, I discussed the trade-offs between performance, scalability, fault tolerance, and
security that come with various physical n-tier configurations. The most scalable solution for an
intelligent client UI is to use an application server to host the data access layer, while the most
performant solution is to run the data portal locally in the client process. In this chapter, I’ll show
first ho
w to r
un the data por
tal locally
, and then r
emotely using each available channel. Chapter 12
will demonstrate how to create the three types of remote data portal hosts for use by the
PTWin
application.
The configur
ation is controlled by the application’s configuration file. In the Visual Studio proj-
ect, this is named
App.config.
CHAPTER 9 ■ WINDOWS FORMS UI 469

6315_c09_final.qxd 4/7/06 2:12 PM Page 469
■Note Naming the file App.config is important. VS .NET will automatically copy the file into the appropriate
Bin directory, changing the name to match that of the program. In this case, it will change the name to PTWin.
exe.config a
s it copies it into the
Bin d
irectories. This occurs each time the project is built in Visual Studio.
The App.config file is an XML file that contains settings to configure the application. You use
different XML depending on how you want the application configured.
Authentication
The way authentication is handled by CSLA .NET is controlled through the configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CslaAuthentication" value="Csla" />
</appSettings>
</configuration>
The CslaAuthentication key shown here specifies the use of custom authentication. Chapter 8
implemented the
PTPrincipal and PTIdentity classes specifically to support custom authentica-
tion, and the UI code in this chapter will use custom authentication as well.
If you want to use Windows authentication, change the configuration to the following:
<add key="CslaAuthentication" value="Windows" />
Of course, that change would require coding changes. To start, the PTPrincipal and PTIdentity
classes should be removed from ProjectTracker.Library, as they would no longer be needed. Also,
the login/logout functionality implemented in this chapter would become unnecessary. Specifically,
the
Login form and the code to display that form would be removed from the UI project.
Local Data Portal
The configuration file also controls how the application uses the data portal. To make the client

application interact directly with the database, use the follo
wing (with
your connection string
changed to the connection string for your database):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CslaAuthentication" value="Csla" />
</appSettings>
<connectionStrings>
<add name="PTracker" connectionString="your connection string"
providerName="System.Data.SqlClient" />
<add name="Security" connectionString="your connection string"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
B
ecause
LocalProxy is the default for the data por
tal, no actual data por
tal configuration is
required, so the only settings in the configuration file are to control authentication and to provide
the database connection strings.
CHAPTER 9 ■ WINDOWS FORMS UI470
6315_c09_final.qxd 4/7/06 2:12 PM Page 470
Remote Data Portal (with Remoting)
To make the data portal use an application server and communicate using the remoting channel,
use the following configuration:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

<appSettings>
<add key="CslaAuthentication" value="Csla" />
<
add key="CslaDataPortalProxy"
value="Csla.DataPortalClient.RemotingProxy, Csla"/>
<add key="CslaDataPortalUrl"
value="http://localhost/RemotingHost/RemotingPortal.rem"/>
</appSettings>
<connectionStrings>
</connectionStrings>
</configuration>
The key lines for remoting configuration are in bold. Of course, you need to change localhost
to the name of the application server on which the data portal host is installed. Also, the
RemotingHost text needs to be replaced with the name of your virtual root on that server.
Before using this configuration, the remoting host virtual root must be created and configured.
I’ll show how this is done in Chapter 12.
Remote Data Portal (with Enterprise Services)
Similarly, to use the Enterprise Services channel, the configuration would look like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CslaAuthentication" value="Csla" />
<add key="CslaDataPortalProxy"
value="EnterpriseServicesHost.EnterpriseServicesProxy,
EnterpriseServicesHostvb"/>
</appSettings>
<connectionStrings>
</connectionStrings>
</configuration>
Before using this configuration, an Enterprise Services host must be created and registered

with C
OM+.
The r
esulting C
OM+ application must be r
egistered with COM on each client work-
station. The basic steps were discussed in Chapter 4, and I’ll show how this is done in Chapter 12.
Remote Data Portal (with Web Services)
F
inally, to use Web Services, the configuration would look like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CslaAuthentication" value="Csla" />
<add key="CslaDataPortalProxy"
value="Csla.DataPortalClient.WebServicesProxy, Csla"/>
CHAPTER 9 ■ WINDOWS FORMS UI 471
6315_c09_final.qxd 4/7/06 2:12 PM Page 471
<add key="CslaDataPortalUrl"
value="http://localhost/WebServicesHost/WebServicePortal.asmx"/>
</appSettings>
<connectionStrings>
</connectionStrings>
</configuration>
A
s with remoting, you need to change
l
ocalhost
a
nd

W
ebServicesHost
t
o the actual server
name and virtual root name used by your application. Also, the virtual root and Web Service
asmx
file must be created and configured. I’ll show how this is done in Chapter 12.
The most important thing to realize about the application configuration is that the data portal
can be changed from local to remote (using any of the network channels) with no need to change
any UI or business object code.
PTWin Project Setup
The UI application can be found within the ProjectTracker solution. The project is named PTWin.
The project references the
ProjectTracker.Library project, along with Csla.dll.
ProjectTracker.Library is a project reference, while Csla.dll is a file reference. When building
applications using the CSL
A .NET framework, it is best to establish a file reference to the framework
assembly, but use project references between the UI and any business assemblies. This makes debug-
ging easier overall, because it helps prevent accidental changes to the CSLA .NET framework project
while enabling fluid changes to both the business objects and UI code.
Let’s go through the creation of the Windows Forms UI. First, I’ll discuss the code in the main
form and the
WinPart base control. Then I’
ll co
ver the process of logging a user in and out.
With the common code out of the way, I’ll discuss the process of maintaining the roles and
project data in detail. At that point, you should have a good understanding of how to create lookup
dialogs, and both grid-based and detail forms.
User Control Framework
The main edit forms

in the application are user controls that inherit from the
WinPart base control.
This base control provides functionality that is used by the main form when it needs to interact with
the user controls it contains. The end result is that the main form can implement the Documents
menu, notify user controls when the user logs in or out, and handle other basic interactions the user
would expect.
WinPar
t
All user controls that are to be displayed on MainForm must inherit from the WinPart base control.
This contr
ol adds common behaviors used b
y the code in
MainForm to manage the display
. I
t inher
its
fr
om
UserControl:
Public Class WinPart
Inherits System.Windows.Forms.UserControl
GetIdV
alue
Somewhat like the BusinessBase class in CSLA .NET, WinPart implements a GetIdValue() method
that should be o
v
err
idden by all user controls. The value returned in this method is used as a unique
identifier for the control, and is used to implement the standard
System.Object overrides of Equals(),

GetHashCode(), and ToString(). The GetIdValue() method is declared like this:
CHAPTER 9 ■ WINDOWS FORMS UI472
6315_c09_final.qxd 4/7/06 2:12 PM Page 472
Protected Overridable Function GetIdValue() As Object
Return Nothing
End Function
The Equals() method is a bit odd, as it has to act differently at design time than at runtime:
Public Overrides Function Equals(ByVal obj As Object) As Boolean
If Me.DesignMode Then
Return MyBase.Equals(obj)
Else
Dim id As Object = GetIdValue()
If Me.GetType.Equals(obj.GetType) AndAlso id IsNot Nothing Then
Return CType(obj, WinPart).GetIdValue.Equals(id)
Else
Return False
End If
End If
End Function
When controls are loaded into Visual Studio at design time, they don’t run in the same environ-
ment as when they’re loaded into the running application. Sometimes this can cause odd errors in
your code at design time, and this is one such case. It seems that Visual S
tudio calls the
Equals()
method at design time in such a way that the new implementation of the method throws an excep-
tion. Checking the
DesignMode property allows the code to see if the control is being used in Visual
Studio, so it can just use the default behavior from the base class.
Closing the Control
When a user control is closed, it needs to notify MainForm so that the control can be gracefully

removed from the list of active user controls. To do this,
WinPart declares an event and implements
a
Close() method:
Public Event CloseWinPart As EventHandler
Protected Sub Close()
RaiseEvent CloseWinPart(Me, EventArgs.Empty)
End Sub
This way, the UI code in the user control can call the Close() method to close the user control.
Raising the
CloseWinPart event tells MainForm to remove the control from the active list and dispose
the user control.
Login/Logout Notification
F
inally, when the user logs into or out of the application,
MainForm needs to notify all activ
e user
controls of that change. This is required so that the UI code in each user control can perform any
authorization activities based on the new user identity.
As you’ll see shortly,
MainForm loops through all active user controls when the user logs in or
out, calling an
OnCurrentPrincipalChanged() method on each user contr
ol. This method is imple-
mented in
WinPart:
Protected Friend Overridable Sub OnCurrentPrincipalChanged( _
ByVal sender As Object, ByVal e As EventArgs)
RaiseEvent CurrentPrincipalChanged(sender, e)
End Sub

CHAPTER 9 ■ WINDOWS FORMS UI 473
6315_c09_final.qxd 4/7/06 2:12 PM Page 473
It is both Overridable and raises a CurrentPrincipalChanged event, declared as follows:
P
rotected Event CurrentPrincipalChanged As EventHandler
If the developer of a user control needs to respond to a login/logout event, they can either
override
OnCurrentPrincipalChanged() or handle the CurrentPrincipalChanged event. Either way,
they’ll be notified that the
CurrentPrincipal property of the Thread object has changed.
MainForm
The MainForm form is the core of the application in that it provides the menu and status bar, and
hosts the user controls for display to the user. It coordinates the flow of the entire application.
Figure 9-4 shows the layout of
MainForm.
The Resources menu has three items comparable to those in the Projects menu, while the
Admin menu has a single item: Edit Roles. The code behind each of these menu items will be dis-
cussed later in the chapter as the business functionality is implemented. For now, I want to focus
on hosting the user contr
ols
, the Documents menu, the status bar
, and the Login button.
Hosting the User Controls
What isn’t immediately obvious from Figure 9-4 is that the main region of the form contains a
Panel contr
ol. All the user contr
ols ar
e actually contained within this
Panel contr
ol r

ather within
than
MainForm itself. This is done so that resizing events can be handled more easily, and the overall
hosting process can be simplified.
The
Panel contr
ol
’s
Dock pr
oper
ty is set to
Fill, so it automatically fills the av
ailable space in
the form, even if the form is resized.
CHAPTER 9 ■ WINDOWS FORMS UI474
Figure 9-4. MainForm layout
6315_c09_final.qxd 4/7/06 2:12 PM Page 474
Loading/Adding User Controls
When a new user control is dynamically loaded (because the user chooses to view/edit a project,
resource, or role), it needs to be created, added to the host’s
Controls collection, positioned, and
sized to fit the client area of
MainForm. The same thing happens when MainForm is resized, since all
the user controls it contains need to be resized accordingly.
This process is split into two parts: adding a user control and showing a user control. The rea-
son for the split is that when a new user control is added, it must be displayed. But already-loaded
user controls also must be displayed through the Documents menu.
The
AddWinPart() method adds a user control to the Panel control:
Private Sub AddWinPart(ByVal part As WinPart)

AddHandler part.CloseWinPart, AddressOf CloseWinPart
part.BackColor = ToolStrip1.BackColor
Panel1.Controls.Add(part)
Me.DocumentsToolStripDropDownButton.Enabled = True
ShowWinPart(part)
End Sub
Remember that all user controls will inherit from the WinPart base control—hence the naming
of the
AddWinPart() method and the type of the parameter.
The
CloseWinPart() method is hooked to handle the user control’s CloseWinPart event. I’ll dis-
cuss this method shortly—but for now, you should know that its purpose is to properly remove the
user control from
MainForm.
The user control’s
BackColor property is set to match the color scheme of MainForm. Then, the
user control is added to the
Controls collection of the panel. This effectively adds the user control
to the form. Then
ShowWinPart() is called to display the user control.
Finally, the Documents menu option is enabled. At this point, it’s known that there’s at least
one user control hosted by
MainForm, so the Documents menu should be available to the user.
The
ShowWinPart() method makes sure that the user control is properly positioned and sized;
then it makes it visible:
Private Sub ShowWinPart(ByVal part As WinPart)
part.Dock = DockStyle.Fill
part.Visible = True
part.BringToFront()

Me.Text = "Project Tracker - " & part.ToString
End Sub
Remember that the Panel control’s Dock property is set to Fill, so the Panel control automati-
cally fills the available space—even when
MainForm is resized. The user control is contained within
the
Panel control and its Dock property is also set to Fill. This means that the user control is auto-
matically r
esiz
ed along with the
Panel contr
ol, so it always fills the client ar
ea of
MainForm.
Next, the user control is made visible and is brought to the front: its z-order is set so that the
user control is on top of all other controls in the
Panel control. These two steps ensure that the user
contr
ol is visible and activ
e.
Finally, the caption text of
MainForm itself is changed to reflect the ToString() value of the newly
activ
e user contr
ol. I
f you look back at Figures 9-2 and 9-3, you’ll notice that
MainForm displays the
name of the
Project object being edited. You’ll see how this flows from the ToString() value of the
user control later in the chapter.

Removing User Controls
R
ecall ho
w
the
AddWinPart() method sets up the CloseWinPart() method to handle the user con
-
trol’s
CloseWinPart event. That event is raised by the user control when it is closed, and MainForm
uses the event to properly remove the user control from the Panel control’s Controls collection:
CHAPTER 9 ■ WINDOWS FORMS UI 475
6315_c09_final.qxd 4/7/06 2:12 PM Page 475
Private Sub CloseWinPart(ByVal sender As Object, ByVal e As EventArgs)
Dim part As WinPart = CType(sender, WinPart)
RemoveHandler part.CloseWinPart, AddressOf CloseWinPart
part.Visible = False
Panel1.Controls.Remove(part)
part.Dispose()
If DocumentCount = 0 Then
Me.DocumentsToolStripDropDownButton.Enabled = False
Me.Text = "Project Tracker"
Else
' Find the first WinPart control and set
' the main form's Text property accordingly.
' This works because the first WinPart
' is the active one.
For Each ctl As Control In Panel1.Controls
If TypeOf ctl Is WinPart Then
Me.Text = "Project Tracker - " + CType(ctl, WinPart).ToString
Exit For

End If
Next
End If
End Sub
When a user control is removed, other work is required as well. The user control’s Dispose()
method is called, and the caption text on MainForm is reset (because there’s almost certainly a new
active user control now). If there’s no longer an active user control, then the caption text is set
accordingly.
Also notice that the
CloseWinPart event is unhooked. This is an important step, because han-
dling an event sets up an object reference behind the scenes, and failing to unhook events can
cause memory leaks (by keeping objects in memory when they are no longer needed).
Resizing User Controls
When MainForm is resized, the Panel control’s Resize event is automatically raised. The following
code handles that event to resize all the hosted user controls:
Private Sub Panel1_Resize( _
ByVal sender As Object, ByVal e As System.EventArgs) Handles Panel1.Resize
For Each ctl As Control In Panel1.Controls
If TypeOf ctl Is WinPart Then
ctl.Size = Panel1.ClientSize
End If
Next
End Sub
With the ability to add, remove, and resize user controls, the code in MainForm covers most of
the capabilities required. Of course, there’s the implementation of the Documents menu itself to
consider.
Documents Menu
The Documents menu is a drop-down menu listing all the active documents (user controls) cur-
rently hosted by the main form. If there are no active user controls, then the menu is disabled.
When the user selects an item fr

om the list, that particular user control becomes the active user
control.
CHAPTER 9 ■ WINDOWS FORMS UI476
6315_c09_final.qxd 4/7/06 2:12 PM Page 476
The DropDownOpening event is raised when the user clicks the Documents menu option to open
the list. Handling this event allows the code to populate the list
before it is displayed to the user:
Private Sub DocumentsToolStripDropDownButton_DropDownOpening( _
ByVal sender As Object, ByVal e As System.EventArgs) _
Handles DocumentsToolStripDropDownButton.DropDownOpening
D
im items As ToolStripItemCollection = _
DocumentsToolStripDropDownButton.DropDownItems
For Each item As ToolStripItem In items
RemoveHandler item.Click, AddressOf DocumentClick
Next
items.Clear()
For Each ctl As Control In Panel1.Controls
If TypeOf ctl Is WinPart Then
Dim item As New ToolStripMenuItem()
item.Text = CType(ctl, WinPart).ToString
item.Tag = ctl
AddHandler item.Click, AddressOf DocumentClick
items.Add(item)
End If
Next
End Sub
Remember that the menu item is only enabled if there are one or mor
e items in the
Controls

collection of the Panel control. Notice that a reference to each user control is put into the Tag prop-
erty of the corresponding
ToolStripMenuItem object.
If the user clicks an item in the list, a
Click event is raised and handled to make the selected
user control the active control:
Private Sub DocumentClick(ByVal sender As Object, ByVal e As EventArgs)
Dim ctl As WinPart = CType(CType(sender, ToolStripItem).Tag, WinPart)
ShowWinPart(ctl)
End Sub
The Tag property of the menu item references the user control associated with that item, so this
code needs only to cast the
Tag value and make the control visible by calling the ShowWinPart()
method discussed earlier.
This wraps up the code in
MainForm that deals with the user controls and the Documents menu.
Now let’s see how the status bar display and mouse cursor changes are handled.
Status Bar
MainForm has a StatusStrip contr
ol at
the bottom, so the user can be informed about any long-
running activity that is occurring. Also, when a long-running activity is going on, the mouse cursor
should be changed to indicate that the application is busy.
An easy way to handle this is to create an object that implements
IDisposable. This object
would update both the status display and mouse cursor
, and then r
eset them when it is disposed.
The r
esult is that anywhere in the UI, code can be written like this:

Using busy As New StatusBusy("Working…")
' do long-running task here
End Using
When the object is created, it sets the status display on MainForm, and it resets the text when it
is disposed. S
imilarly, when the object is created, it sets the mouse cursor to a busy cursor, and
resets it when disposed.
CHAPTER 9 ■ WINDOWS FORMS UI 477
6315_c09_final.qxd 4/7/06 2:12 PM Page 477
To do this, it needs to be able to access the MainForm object. Fortunately VB 2005 supports
default instances for forms. If your application has exactly one instance of a specific form, such
as
MainForm, then you can just refer to it by name anywhere in the project. Using this feature, the
MainForm object can be used by any code in the UI, including the StatusBusy class:
Public Class StatusBusy
Implements IDisposable
Private mOldStatus As String
Private mOldCursor As Cursor
Public Sub New(ByVal statusText As String)
mOldStatus = MainForm.StatusLabel.Text
MainForm.StatusLabel.Text = statusText
mOldCursor = MainForm.Cursor
MainForm.Cursor = Cursors.WaitCursor
End Sub
' IDisposable
Private disposedValue As Boolean = False ' To detect redundant calls
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
MainForm.StatusLabel.Text = mOldStatus

MainForm.Cursor = mOldCursor
End If
End If
Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code.
' Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class
When a StatusBusy object is cr
eated, it sets the status text and mouse cursor
, stor
ing the old
v
alues for later use:
mOldStatus = MainForm.StatusLabel.Text
MainForm.StatusLabel.Text = statusText
mOldCursor = MainForm.Cursor
MainForm.Cursor = Cursors.WaitCursor
Then, when the object is disposed, the status text and cursor ar
e r
eset to their pr
evious v
alues:
MainForm.StatusLabel.Text = mOldStatus
MainForm.Cursor = mOldCursor
This is one of the simplest ways to implement powerful status notification and cursor handling

for the user in a Windows Forms UI.
CHAPTER 9 ■ WINDOWS FORMS UI478
6315_c09_final.qxd 4/7/06 2:12 PM Page 478
Login Button
The final bit of common functionality implemented in MainForm allows the user to log into or out of
the application. It is important to realize that the
ProjectTracker application allows unauthorized
or guest users to view certain data, and so the user can interact with the application even if they
haven’t logged in.
The login process is triggered when the application first loads, and when the user clicks the
Login button on the menu. In both cases, a
DoLogin() method is called to handle the actual
l
ogin/logout behavior:
Private Sub DoLogin()
ProjectTracker.Library.Security.PTPrincipal.Logout()
If Me.LoginToolStripButton.Text = "Login" Then
LoginForm.ShowDialog(Me)
End If
Dim user As System.Security.Principal.IPrincipal = _
Csla.ApplicationContext.User
If user.Identity.IsAuthenticated Then
Me.LoginToolStripLabel.Text = "Logged in as " & user.Identity.Name
Me.LoginToolStripButton.Text = "Logout"
Else
Me.LoginToolStripLabel.Text = "Not logged in"
Me.LoginToolStripButton.Text = "Login"
End If
' reset menus, etc.
ApplyAuthorizationRules()

' notify all documents
For Each ctl As Control In Panel1.Controls
If TypeOf ctl Is WinPart Then
CType(ctl, WinPart).OnCurrentPrincipalChanged(Me, EventArgs.Empty)
End If
Next
End Sub
Before doing anything else, this method ensures that the CurrentPrincipal property of the
Thread is set to an unauthenticated PTPrincipal object:
ProjectTracker.Library.Security.PTPrincipal.Logout()
This way, if the user’s credentials are invalid, she can at least use the application as an
unauthenticated user
. R
ecall that the data por
tal r
equires that the principal object inherit from
Csla.Security.BusinessPrincipalBase. PTPrincipal meets this requirement, and so the current
principal is set to an unauthenticated
PTPrincipal object by calling the Logout() method.
Next, the text of the button on the menu is checked. If the text is
Login, then a login process
is initiated. The login process is actually handled by a
Login dialog form, which is shown to the user
as a modal dialog. That dialog prompts the user for her credentials and calls
PTPrincipal.Login()
(as implemented in Chapter 8) to validate them.
The result is that the
CurrentPrincipal property on the Thread object will either be an authen-
ticated
PTPrincipal or an unauthenticated PTPrincipal. The status of the principal object is used

to deter
mine whether the user is logged in or not:
CHAPTER 9 ■ WINDOWS FORMS UI 479
6315_c09_final.qxd 4/7/06 2:12 PM Page 479
If user.Identity.IsAuthenticated Then
Me.LoginToolStripLabel.Text = "Logged in as " & user.Identity.Name
Me.LoginToolStripButton.Text = "Logout"
Else
Me.LoginToolStripLabel.Text = "Not logged in"
Me.LoginToolStripButton.Text = "Login"
End If
If the user was authenticated, then the button text is changed to Logout and the user’s name
is displayed in the menu. Otherwise, the button text is changed to
Login, and text indicating that the
user isn’t logged in is displayed.
In any case, an
ApplyAuthorizationRules() method is called so that MainForm can update its
display based on the user’s identity (or lack thereof). Then all the active user controls are notified
that the principal has changed:
' reset menus, etc.
ApplyAuthorizationRules()
' notify all documents
For Each ctl As Control In Panel1.Controls
If TypeOf ctl Is WinPart Then
CType(ctl, WinPart).OnCurrentPrincipalChanged(Me, EventArgs.Empty)
End If
Next
Each user control is responsible for handling this event and responding appropriately. Recall
that the
WinPart base contr

ol implements the
OnCurrentPrincipalChanged() method and subse
-
quently raises a
Protected event to the code in the user control.
The
ApplyAuthorizationRules() method in MainForm is responsible for enabling and disabling
menu items. This method is somewhat long and repetitive, so I won’t show the whole thing, but
here’s the code to enable/disable one menu item:
Me.NewProjectToolStripMenuItem.Enabled = _
Project.CanAddObject
Notice how the actual authorization check is delegated to the Shared method of the Project
business class. These methods were discussed in Chapter 8, and were implemented specifically to
enable scenarios like this. The idea is that
MainForm has no idea whether particular users or roles are
authorized to add
Project objects. Instead, the Project class itself has that knowledge, and MainForm
simply asks Project whether the current user is authorized.
The end result is good separation of concerns:
Project is concerned with whether users can
and can

t add objects
, while
MainForm is
concer
ned
with the UI details of enabling and disabling
contr
ols.

Login Form
The DoLogin() method in MainForm calls a Login dialog for
m to collect and authenticate the user’s
credentials. After gathering credentials from the user, this dialog form will call
PTPrincipal.Login()
to do the authentication itself.
Figure 9-5 shows the
Login form layout.
CHAPTER 9 ■ WINDOWS FORMS UI480
6315_c09_final.qxd 4/7/06 2:12 PM Page 480
All the work occurs when OK is clicked. At that point, the credentials entered by the user are
ver
ified:
Private Sub OK_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles OK.Click
Using busy As New StatusBusy("Verifying credentials ")
ProjectTracker.Library.Security.PTPrincipal.Login( _
Me.UsernameTextBox.Text, Me.PasswordTextBox.Text)
End Using
Me.Close()
End Sub
Notice the use of the StatusBusy object to update the status text and mouse cursor. Also
notice the simplicity of this code. Since
PTPrincipal.Login() does all the work of authenticating
the user, there’s just not much work to do in the UI. This is a theme you’ll see throughout the rest
of the chapter.
Using Windows Integrated Security
If you wanted to use Windows integrated security, you wouldn’t need a login form because the
client workstation already knows the user’s identity. Instead, you would need to add a bit of code

to
MainForm so that as it loads, the CurrentPrincipal is configured with a WindowsPrincipal object.
The follo
wing code sho
ws ho
w to detect the authentication mode and adapt to use either
Windows or custom authentication appropriately:
Private Sub MainForm_Load( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
If Csla.ApplicationContext.AuthenticationType = "Windows" Then
AppDomain.CurrentDomain.SetPrincipalPolicy( _
System.Security.Principal.PrincipalPolicy.WindowsPrincipal)
Else
DoLogin()
End If
If DocumentCount = 0 Then
Me.DocumentsToolStripDropDownButton.Enabled = False
End If
ApplyAuthorizationRules()
End Sub
CHAPTER 9 ■ WINDOWS FORMS UI 481
Figure 9-5. Layout of the Login form
6315_c09_final.qxd 4/7/06 2:12 PM Page 481
Calling SetPrincipalPolicy() to set the WindowsPrincipal option tells the .NET runtime to
return the current
WindowsPrincipal object for the CurrentPrincipal property of the Thread.
■Note If you use Windows integrated security, and you are using a remote data portal, you must make sure to
change the server configuration file to also use Windows security. If the data portal is hosted in IIS, the virtual root
must be set to disallow anonymous access, thereby forcing the client to provide IIS with the Windows identity from

the client workstation via integrated security.
Business Functionality
With the common functionality in MainForm, WinPart, StatusBusy and Login covered, we can move
on to the business functionality itself. As I mentioned earlier, I’ll walk through the
RolesEdit user
control, the
ProjectSelect dialog, and the ProjectEdit user control in some detail. ResourceSelect
and ResourceEdit are available in the download and follow the same implementation approach.
All of these forms and user controls will be created using the new data binding capabilities
built into Visual Studio 2005. These capabilities allow the UI developer to literally drag-and-drop
business classes or properties onto the form to create the controls and set up data binding. The
developer productivity gained through this approach is simply amazing.
The detail edit forms (
ProjectEdit and ResourceEdit) will also make use of the
ReadWriteAuthorization and BindingSourceRefresh controls cr
eated in Chapter 5, as well as the
standard Windows Forms
ErrorProvider control. All three controls are extender controls, adding
impor
tant extra capabilities to the other controls on each form or user control.
Let’s start by looking at the business code in
MainForm that displays the other forms and user
controls.
MainForm
You’ve already seen the code in MainForm that exists to provide common functionality around the
user controls
, authentication, and authorization. But the form also implements the menu options
to add, edit, and delete project and resource data, and to edit the list of roles.
Displaying User Controls
Thanks to the common code discussed earlier, none of these menu options are difficult to imple-

ment. F
or instance
, when the user chooses the menu option to edit the list of r
oles
, the code simply
checks to see if the
RolesEdit user control is already loaded. If it is, the existing user control is made
active; otherwise, a new one is created and displayed:
Private Sub EditRolesToolStripMenuItem_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles EditRolesToolStripMenuItem.Click
' see if this form is already loaded
For Each ctl As Control In Panel1.Controls
If TypeOf ctl Is RolesEdit Then
ShowWinPart(CType(ctl, WinPart))
Exit Sub
End If
Next
CHAPTER 9 ■ WINDOWS FORMS UI482
6315_c09_final.qxd 4/7/06 2:12 PM Page 482
' it wasn't already loaded, so show it
AddWinPart(New RolesEdit)
End Sub
A slightly more complex variation occurs when the user clicks the menu to add a project or
resource. In both cases, a new instance of the appropriate business object is created and is passed
to a new instance of the appropriate user control. For example, when the user opts to add a new
project, this code is run:
Private Sub NewProjectToolStripMenuItem_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles NewProjectToolStripMenuItem.Click

Using busy As New StatusBusy("Creating project ")
AddWinPart(New ProjectEdit(Project.NewProject))
End Using
End Sub
Project.NewProject() is called to create the new Project object, and it is then passed to the
constructor of a
ProjectEdit user control. That user control, now populated with data from the
Project object, is then added to the list of active user controls and displayed.
Editing an Existing Object
Even more complex is the process of editing an existing project or resource. This is because in both
cases, the user must be prompted to select the specific item to edit. The
ProjectSelect and
ResourceSelect dialog forms are used to prompt the user for the particular object he wishes to
edit. Here’s the code behind the menu option to edit a resource:
Private Sub EditResourceToolStripMenuItem_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles EditResourceToolStripMenuItem.Click
Dim dlg As New ResourceSelect
dlg.Text = "Edit Resource"
If dlg.ShowDialog = Windows.Forms.DialogResult.OK Then
' get the project id
ShowEditResource(dlg.ResourceId)
End If
End Sub
The code for editing a project is virtually identical, but obviously uses ProjectSelect instead.
This code displays the dialog using the
ShowDialog() method and checks its result value. If the
user clicks the OK button in the dialog, then the selected
ResourceId value is retrieved from the dia-
log for

m and is passed to a
ShowEditResource() method.
ShowEditResource() checks
to see if this resource is already visible in a user control, and if so,
it makes that the active user control. Otherwise, the method takes care of retrieving the business
object from the database and adding a new
ResourceEdit user control to MainForm:
Public Sub ShowEditResource(ByVal resourceId As Integer)
' see if this project is already loaded
For Each ctl As Control In Panel1.Controls
If TypeOf ctl Is ResourceEdit Then
Dim part As ResourceEdit = CType(ctl, ResourceEdit)
If part.Resource.Id.Equals(resourceId) Then
' project already loaded so just
' display the existing winpart
CHAPTER 9 ■ WINDOWS FORMS UI 483
6315_c09_final.qxd 4/7/06 2:12 PM Page 483
ShowWinPart(part)
Exit Sub
End If
End If
Next
' the resource wasn't already loaded
' so load it and display the new winpart
Using busy As New StatusBusy("Loading resource ")
Try
AddWinPart(New ResourceEdit(Resource.GetResource(resourceId)))
Catch ex As Csla.DataPortalException
MessageBox.Show(ex.BusinessException.ToString, _
"Error loading", MessageBoxButtons.OK, _

MessageBoxIcon.Exclamation)
Catch ex As Exception
MessageBox.Show(ex.ToString, _
"Error loading", MessageBoxButtons.OK, _
MessageBoxIcon.Exclamation)
End Try
End Using
End Sub
The code to find an existing ResourceEdit user control for this resource loops through all the
controls hosted in the
Panel control. Those items that are of type ResourceEdit are checked to see
if the
Resource object they are editing has the same Id value as the one just selected by the user.
Assuming no matching
ResourceEdit user control is found, the requested Resource object is
loaded from the database. This object is passed to a new
ResourceEdit user control, which is dis-
played in
MainForm:
AddWinPart(New ResourceEdit(Resource.GetResource(resourceId)))
Any exceptions are handled so that the user is notified about the problem; otherwise, the user
is free to move ahead and view or edit the
Resource object’s data.
Deleting an Object
Deleting a project or resource is a similar process. The user is prompted to select the item to delete.
Then he is asked if he is sure he wants to delete the item, and finally the item is deleted. The code to
delete projects and resources is quite comparable; here’s the code to delete a
Resource object:
Private Sub DeleteResourceToolStripMenuItem_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles DeleteResourceToolStripMenuItem.Click
Dim dlg As New ResourceSelect
dlg.Text = "Delete Resource"
If dlg.ShowDialog = Windows.Forms.DialogResult.OK Then
' get the resource id
Dim resourceId As Integer = dlg.ResourceId
If MessageBox.Show("Are you sure?", "Delete resource", _
MessageBoxButtons.YesNo, MessageBoxIcon.Question, _
MessageBoxDefaultButton.Button2) = _
Windows.Forms.DialogResult.Yes Then
CHAPTER 9 ■ WINDOWS FORMS UI484
6315_c09_final.qxd 4/7/06 2:12 PM Page 484

×