Windows Forms

.NET Framework 1.1 Provides Expanded Namespace, Security, and Language Support for Your Projects

Chris Sells

Code download available at:WindowsForms.exe(135 KB)

This article assumes you're familiar with Windows Forms and C#

Level of Difficulty123

SUMMARY

With the much-anticipated release of the .NET Framework 1.1, developers are eager to know what's been added to their programming bag of tricks. In this article, the author focuses on new developments in Windows Forms, such as namespace additions, support for hosting managed controls in unmanaged clients, and designer support for C++ and J#. Integrated access to the Compact Framework and new mobile code security settings also make this release noteworthy. Along with these features, the author reviews the best ways to handle multiple versions of the common language runtime and highlights some potential pitfalls.

Contents

What's New
Moving to 1.1
Namespace Additions
Managed Controls in Unmanaged Hosts
Designer Support for C++ and J#
Compact Framework
Mobile Code Security Settings
Where Are We?

Y ou know that the old saying that goes everyone complains about the weather but nobody does anything about it? Well, it can't be applied to programming. Microsoft® is continually working hard to produce the next greatest set of development tools and technologies. In the latest release of the Microsoft .NET Framework, version 1.1, forecasts are very promising. While originally intended only as a bug-fix release (the .NET Framework version 1.0 was released just last February, after all), version 1.1 has both fixed bugs and integrated much needed new features into one of my favorite parts of the .NET Framework—Windows® Forms.

This article is based on the final beta of the .NET Framework 1.1, which ships alongside Visual Studio® .NET 2003. Most features are likely to stay very similar, but some are bound to change by the time it is officially released.

What's New

According to a document that comes with the new .NET Framework SDK entitled "API Changes from v1.0 to v1.1," there are 48 additions, 14 removals, and no breaking changes in the System.Windows.Forms namespace. This news may, at first, cause you to rejoice at the addition of 48 new features and mourn the loss of 14 old features, while hoping that you weren't using anything that was axed. You might also wonder how there can be no breaking changes if 14 things were removed. Since the lack of breaking changes means that all of your existing Windows Forms code should continue to run without any changes, all of the removals have corresponding additions that are really changes, like the change of the declaration of the ProcessDialogKey method on the AxHost class, as shown here:

class AxHost { // .NET 1.0 declaration protected virtual bool ProcessDialogKey(Keys keyData); // .NET 1.1 declaration protected override bool ProcessDialogKey(Keys keyData); ••• }

This change shows that AxHost no longer provides a new virtual method but overrides an existing one, which means that ProcessDialogKey was declared as virtual further up the inheritance hierarchy. The vast majority of the additions fall into this category, making the changes to the Windows Forms namespace itself seem very boring. There are really only three noteworthy additions to the Windows Forms namespace (and one interesting addition to the System.Drawing namespace):

  • the System.Windows.Forms.FolderBrowserDialog class
  • the System.Windows.Forms.Application.EnableVisualStyles method
  • the System.Windows.Forms.CurrencyManager.MetaDataChanged event
  • the System.Drawing.Printing.PrintDocument.OriginAtMargins property

If that was the whole story, I certainly wouldn't need an entire article to describe what's new. There's much more, including namespace additions, support for hosting managed controls in unmanaged clients, Windows Forms designer support for managed C++ and Visual J#™, integrated access to the Compact Framework, and new mobile code security settings. Before I move on to all that is new, however, I'll discuss how to handle multiple versions of the common language runtime (CLR).

Moving to 1.1

With any new version, I always hope that Microsoft has added the features that I need without ruining any of the ones I depend on. As opposed to Win32® DLLs—where developers pretended that there weren't any versioning issues—and COM, where they pretended that versioning wasn't needed at all—the .NET Framework provides thorough, explicit support for multiple versions of multiple, simultaneously installed versions of components and the runtime itself. In fact, if you're running a version of Windows that's had Windows Update running, you've most likely got CLR version 1.0.3705.288, which is the .NET Framework 1.0 SP2. After installing .NET Framework 1.1, your CLR version will be bumped up to version 1.1.4322.510, which is the .NET Framework 1.1 final beta. However, installing version 1.1 doesn't replace any existing versions installed on your machine. Instead, both versions live in harmony in separate directories.

The version of the CLR that's loaded at run time depends on the version that the EXE assembly was built against. So, if you build a Windows Forms application against .NET Framework 1.0, even on a machine that has .NET Framework 1.1 (or later) installed on it, the 1.0 version of the CLR will be loaded to host your application. However, if a 1.0 application is launched on a machine without version 1.0 installed, will automatically be loaded into CLR version 1.1. This is to prevent every Windows Forms 1.0 application from requiring a .config file that says that it's OK to load a 1.0 application under 1.1. If you'd prefer to turn this behavior off, you can create your own .config file and turn off this auto-upgrade feature:

<!-- support only .NET 1.0 Framework --> <configuration> <startup> <requiredRuntime version="v1.0.3705"/> <supportedRuntime version="v1.0.3705"/> </startup> </configuration>

In that case, if your version 1.0 application is run on a machine without version 1.0 of the CLR, regardless of whether or not version 1.1 is installed, the user will get the error message indicating that she requires a specific version of the .NET Framework.

Finally, if you'd like to support version 1.0 but prefer version 1.1 if it's available (because it performs better, for example), take a look at the following code:

<!-- prefer .NET Framework 1.1, but support .NET Framework 1.0 --> <configuration> <startup> <requiredRuntime version="v1.0.3705"/> <!-- order of these keys determines preference --> <supportedRuntime version="v1.1.4322"/> <supportedRuntime version="v1.0.3705"/> </startup> ••• </configuration>

Unfortunately, that's not the whole story. Since every assembly comes with a list of required assemblies (including their version numbers), building an application under the .NET Framework version 1.1 is going to require version 1.1 for all of the assemblies. Running a version 1.0 application that's been built under 1.1 on a machine with only 1.0 on it will fail since it won't be able to find the appropriate versions of the referenced assemblies. So, getting your application built under version 1.1 to actually run on an earlier version of the assemblies it references requires entries in the .config file for each assembly, as shown in Figure 1.

Figure 1 Running with Earlier Assemblies

<!-- Bring in appropriate referenced assembly version --> <configuration> <startup> <requiredRuntime version="v1.0.3705"/> <supportedRuntime version="v1.1.4322"/> <supportedRuntime version="v1.0.3705"/> </startup> <runtime> <!-- binding rules under .NET 1.0: --> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" appliesTo="v1.0.3705"> <dependentAssembly> <!-- when binding to System.Windows.Forms, --> <assemblyIdentity name="System.Windows.Forms" publicKeyToken="b77a5c561934e089" culture="neutral"/> <!-- load System.Windows.Forms 1.0 --> <bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="1.0.3300.0"/> </dependentAssembly> ••• </assemblyBinding> </runtime> </configuration>

These configuration settings specify that when running under version 1.0 of the .NET Framework and loading System.Windows.Forms from any version, version 1.0 of System.Windows.Forms will be loaded, even though the code was built against version 1.1. While this is a handy feature that allows you to use the updated version 1.1 tools, setting up the .config file to make all this happen is a bit of a pain. Luckily, Visual Studio .NET 2003 provides an extra project setting accessible from Project | Properties | Common Properties | General | Supported Runtimes. This setting exposes three runtime options through the .NET Framework Version dialog, namely: Microsoft .NET Framework version 1.1 (default), version 1.0 (advanced), and both.

By default, any application compiled under Visual Studio .NET 2003 will target only the 1.1 version of the CLR. If you'd like to restrict yourself to building just version 1.0 applications, you can check the version 1.0 option. The last option is supporting both versions of the Framework, but preferring 1.1. All three of these options will add an app.config file to your project with the appropriate settings, including an exhaustive list of how to map framework assemblies, saving you the trouble. The resulting app.config file will be copied to the output directory at project build time and renamed projectName.exe.config, as required by the CLR.

If you stop to read the text of the .NET Framework Version dialog, you'll notice that it uses some pretty strong language to dissuade you from choosing to support version 1.0 while using Visual Studio .NET 2003. The problem is that darn middle option that says to only support version 1.0. While Visual Studio .NET 2003 will update the .config file to properly require only version 1.0 of the Framework, neither IntelliSense® nor the compiler will tell you if you make use of a type or a member that's not available under version 1.0. You won't know until run time that the code has run afoul of the 1.0 feature list, and then only if the method that makes use of the missing type or method is actually called. If you decide to use Visual Studio .NET 2003 to build applications targeted at version 1.0, you'll need to do some thorough code coverage testing to make sure that it will actually run under version 1.0.

If you're targeting version 1.0 only and you're desperate to use Visual Studio .NET 2003, but just can't give up the compiler-time checking and accurate IntelliSense, there is a way to eat your cake and have it too. Unfortunately, you'll be without frosting, as you'll have to do more than just flip the bit in the .NET Framework Version dialog. First, you'll have to set Project | Properties | Configuration Properties | Advanced | Do not use Mscorlib to "true" (it defaults to "false") so that you don't pull in the version 1.1 mscorlib.dll when you're compiling. Second, you'll have to remove all of the assembly references that Visual Studio .NET 2003 adds to your project, as it will default to getting them from the version 1.1 Framework directory. Finally, you'll have to add project references from the version 1.0 Framework directory using the browser button in the Add Reference dialog to choose assemblies manually (including mscorlib.dll and system.dll). Since the compiler (and IntelliSense) will be referencing the version 1.0 assemblies, these manual steps let you continue to use Visual Studio .NET 2003, even for version 1.0-only assemblies.

On the other hand, if you're targeting both version 1.0 and version 1.1, it's likely that you'll want to take advantage of the new features of version 1.1. When you do that, you have to be careful to check the version number of the runtime through System.Environment.Version and to only use 1.1 features in methods that will never be called by any version 1.0 code. Even if the 1.1 code won't be called under 1.0, the just-in-time (JIT) compiler will require all types and methods to be present when a method is first called, throwing an exception if something is missing. Figure 2 provides an example of one way to isolate code that requires version 1.1. Notice that this code will only call a method with version 1.1-specific code in it if the version of the runtime supports it.

Figure 2 Specifying Version

// This method called under .NET 1.0 void UseMyFolderBrowser() { MyFolderBrowseDialog dlg = new MyFolderBrowseDialog(); dlg.ShowDialog(); } // This method called under .NET 1.1 void UseFolderBrowser() { FolderBrowserDialog dlg = new FolderBrowserDialog(); dlg.ShowDialog(); } // This method called under .NET 1.0 or 1.1 // No 1.1 types or methods used in this method void browseButton_Click(object sender, EventArgs e) { if( Environment.Version <= new Version("1.1") ) { UseMyFolderBrowser(); } else { UseFolderBrowser(); } }

Namespace Additions

While I'm talking about taking advantage of Windows Forms features available only in version 1.1, recall the CurrencyManager.MetaDataChanged event, the PrintDocument.OriginAtMargins property, the FolderBrowserDialog class, and the Application.EnableVisualStyles method. The MetaDataChanged event is useful if you're employing data binding and you're interested in the underlying metadata changing in addition to new or updated rows and columns. The OriginAtMargins property is useful if you actually want your printed document's margins to align with the edge of the paper instead of the edge of the printable region of the printer (the former is the intuitive location, but the latter was the only option under the .NET Framework 1.0 and is still the default under version 1.1). The FolderBrowserDialog class is a wrapper around the Win32 ShBrowseForFolder function and works the way you'd expect:

void browseButton_Click(object sender, EventArgs e) { FolderBrowserDialog dlg = new FolderBrowserDialog(); dlg.Description = "Please choose a folder:"; DialogResult res = dlg.ShowDialog(); if( res == DialogResult.OK ) { MessageBox.Show(dlg.SelectedPath, "Selected Path"); } }

Of course, the FolderBrowserDialog class is available for drag and drop from the Visual Studio .NET Toolbox, so you can use the Property Browser to set things like the initial folder and whether to show the Make New Folder button.

One other interesting thing about the Browse For Folder dialog box is the use of the cute, rounded, Windows XP-themed buttons. By default, a Windows Forms application will not get the cute, rounded, Windows XP theme—even running under Windows XP and .NET Framework version 1.1. Instead, the classic controls will be used, making your app look old-fashioned when running under Windows XP, especially in comparison to the standard dialogs, which always display in a themed manner.

Under version 1.0, enabling Windows XP themes required a .manifest file to select the themed version of the common controls and dialogs (see References for the manifest required under version 1.0 to enable themes). Enabling themes in version 1.1 no longer requires a manifest. Instead, you need to call the EnableVisualStyles method from the System.Windows.Forms.Application class before showing any UI:

static void Main() { Application.EnableVisualStyles(); Application.Run(new Form1()); }

While EnableVisualStyles sounds comprehensive, you'll also need to set the FlatStyle on each control on your form from the default value of FlatStyle.Standard to FlatStyle.System. Figure 3 shows the difference when running under Windows XP. As a shortcut to setting the FlatStyle property on the subset of controls that support it (buttons, group boxes, and labels), consider the code shown in Figure 4.

Figure 4 Setting FlatStyle Properties

public MyThemedForm() { // Required for Windows Form Designer support InitializeComponent(); // Set the FlatStyle for all controls SetFlatStyleSystem(this); } void SetFlatStyleSystem(Control parent) { foreach( Control control in parent.Controls ) { // Only these controls have a FlatStyle property ButtonBase button = control as ButtonBase; GroupBox group = control as GroupBox; Label label = control as Label; if( button != null ) button.FlatStyle = FlatStyle.System; else if( group != null ) group.FlatStyle = FlatStyle.System; else if( label != null ) label.FlatStyle = FlatStyle.System; // Set contained controls FlatStyle, too SetFlatStyleSystem(control); } }

Figure 3 FlatStyle

Figure 3** FlatStyle **

Managed Controls in Unmanaged Hosts

While I'm on the topic of managed controls, one thing that version 1.0 supported was hosting Windows Forms controls in exactly one unmanaged host: Microsoft Internet Explorer. The syntax was special-purpose and tailored just for hosting Windows Forms controls, as shown here:

<html> <body> <object id="iectrl" classid="iectrl.dll#iectrl.UserControl1" width="100" height="100" > </object> </body> </html>

While some special interop attributes are required on a Windows Forms control to support events, Internet Explorer didn't require any of the normal COM registry entries to make the control available to it as a COM control. Instead, the classid attribute of the object element takes the name of a Windows Forms control in the following form:

<dllName>#<namespace>.<className>

Internet Explorer simply loads the Windows Forms control itself and treats it as a COM control. However, no other popular COM control hosts, like MFC, Visual Basic® 6.0, or ATL, were supported in version 1.0. While neither Visual Basic 6.0 nor ATL is officially supported in version 1.1 either, the Windows Forms team did a full test cycle on hosting Windows Forms controls as COM controls in MFC 7.1—the version of MFC that comes with Visual Studio .NET 2003—and it is now an officially supported unmanaged host. However, while it is fully supported, it's not fully integrated. You'll need to augment the standard COM control containment infrastructure to support Windows Forms controls.

The core of the MFC support for COM controls lies in COleControlSite, which creates controls on demand given a CLSID. Instead, I'll be creating the Windows Forms control before attempting to host it, so my COleControlSite derivative will ignore the CLSID and create a host around the existing control (see Figure 5).

Figure 5 COleControlSite Implementation

class CWinFormsControlSite : public COleControlSite { public: CWinFormsControlSite(COleControlContainer* pCtrlCont) : COleControlSite(pCtrlCont) {} // Most of this implementation is copied from occsite.cpp virtual HRESULT CreateControl( CWnd* pWndCtrl, REFCLSID clsid, // ignored ...) { HRESULT hr = E_FAIL; ••• // Wrap existing WinForms control // instead of creating a new COM control if (SUCCEEDED(hr = WrapWinFormsControl(...))) { ••• } ••• return hr; } protected: // This code is based on COleControlSite::CreateOrLoad, // but without support for persistence or licensing virtual HRESULT WrapWinFormsControl(...) { HRESULT hr = E_FAIL; ••• CWinFormsControlWnd* pWndCtrlLocal = (CWinFormsControlWnd*)m_pWndCtrl; CComPtr<IUnknown> spunk; hr = pWndCtrlLocal->GetControl(&spunk); ••• return hr; } };

The COleControlSite CreateControl method is used when the CWnd base class CreateControl method is called to create a COM control. My implementation of CreateControl is mostly a copy of the base class implementation, except that instead of calling the CreateOrLoad method that creates a COM control, it calls my custom WrapWinFormsControl method. My Windows Forms control hosting code depends on the COM interop that the .NET Framework supplies for Windows Forms controls exposing the appropriate COM interfaces.

Also, notice the use of the CWinFormsControlWnd class. Instances of this class hold the HWND that hosts the COM control as well as the IUnknown pointer on the Windows Forms control. The CWinFormsControlSite depends on instances of CWinFormsControlWnd to return the interface pointer on the COM façade around the Windows Forms control on demand via the GetControl method (see Figure 6).

Figure 6 CWinFormsControlWnd Class

class CWinFormsControlWnd : public CWnd { public: BOOL Create( IUnknown* punkControl, // Interface of WinForms control DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID) { // Cache control interface pointer spunkControl = punkControl; // This call will end up in CWinFormsControlSite // CreateControl, which is why it's OK to pass a // NULL CLSID (we're wrapping a pre-existing // WinForms control, not creating a new COM control) return CreateControl(CLSID_NULL, ...); } HRESULT GetControl(IUnknown** ppunk) { if( !ppunk ) return E_POINTER; if( !spunkControl ) return E_UNEXPECTED; return spunkControl->QueryInterface(ppunk); } ••• CComPtr<IUnknown> spunkControl; };

The implementation of GetControl returns a cached COM interface pointer from the Windows Forms control as passed into the Create method. The Create method caches the interface pointer and causes the CWinFormsControlSite class's CreateControl method to be called by calling the base CWnd class's CreateControl method. My custom CWinFormsControlSite class is provided to the CWnd class by overriding the CreateControlSite method of any CWnd-derived class that must host a Windows Forms control, as shown in the following:

BOOL CMfcWinFormsHostDlg::CreateControlSite(...) { ASSERT(ppSite); *ppSite = new CWinFormsControlSite(this->GetControlContainer()); return TRUE; }

As an example of such a class, Figure 7 shows a dialog that hosts the unmanaged and managed MonthCalendar controls side by side on the same MFC dialog.

Figure 7 Hosting a Control in an MFC 7.1 Container

Figure 7** Hosting a Control in an MFC 7.1 Container **

The dialog in Figure 7 was created using the standard MFC wizard, overriding the CreateControlSite method and creating an instance of the CWinFormsControlWnd class:

class CMfcWinFormsHostDlg : public CDialog { ••• virtual BOOL CreateControlSite(...); private: CWinFormsControlWnd m_wndWinFormsCalendar; };

After hooking into the COM control creation process of MFC, I'm finally able to create instances of Windows Forms controls as the dialog is being created. This can be done from unmanaged code by manually hosting the common language runtime (CLR) and using COM interop to create an instance of any Windows Forms control. However, a far easier solution is to turn on the managed extensions to C++ by setting the Project | Properties | Configuration Properties | General | Use Managed Extensions option to "yes" (it defaults to "no"). This will enable the use of managed types, including Windows Forms controls, from your MFC application without requiring you to make any of your existing unmanaged C++ classes managed (see Figure 8).

Figure 8 Managed Types in Unmanaged C++

// Can't use this and create instances of managed types //#ifdef _DEBUG //#define new DEBUG_NEW //#endif // Bring in the System.Windows.Forms assembly #using <System.dll> #using <System.Windows.Forms.dll> BOOL CMfcWinFormsHostDlg::OnInitDialog() { CDialog::OnInitDialog(); // Create WinForms control(s) System::Windows::Forms::MonthCalendar* pcal = new System::Windows::Forms::MonthCalendar(); // Get interface pointer from control CComPtr<IUnknown> spunkControl; spunkControl.Attach((IUnknown*)System::Runtime::InteropServices:: Marshal::GetIUnknownForObject(pcal).ToPointer()); // Get rect of placeholder control and then hide it CRect rectPlaceHolder; ••• // Wrap control and place in on the parent window m_wndWinFormsCalendar.Create( spunkControl, WS_VISIBLE | WS_TABSTOP, rectPlaceHolder, this, 0); return TRUE; // return TRUE unless you set the focus to a control }

While these instructions are a bit cumbersome, the CWinFormsControlWnd and the CWinFormsControlSite helper classes provided in the sample with this article take most of the sting out and require no special support in your Windows Forms controls to host them in MFC 7.1 above and beyond what you already do to support Internet Explorer 5.01 and higher hosting. For more information about the interop story between COM and .NET, see Adam Nathan's book .NET and COM: The Complete Interoperability Guide (Sams, 2002).

Designer Support for C++ and J#

Like hosting Windows Forms controls as COM controls, support for languages other than C# and Visual Basic .NET in Visual Studio .NET 2002 was available, but not completely integrated. You could write a Windows Forms application in managed C++ or J#, but there was no designer support; you had to write all the code by hand. As big a fan as I am of Windows Forms, I wouldn't wish that task on anyone.

Luckily, for programmers who need to stay in C++ or J#, Visual Studio .NET 2003 provides full designer support for managed C++ and J#. When creating a new project, in the Visual C++® and Visual J#™ project types you'll see projects for building a Windows Control Library and a Windows Forms application, just like you're used to for C# and Visual Basic .NET. When creating a Visual C++ Windows Forms app, you'll see a very familiar environment, complete with a toolbox full of controls and components, the designer surface ready for your drag and drop operations, and the Property Browser. In fact, the only thing that's different is the Window tab, which says Form1.h instead of Form1.cs.

When a new C++ Windows Forms application project is created, you'll see a Form1.cpp and an AssemblyInfo.cpp file, as you'd imagine, but you'll also see a .h file for each form class you add to the project using Project | Add New Item. The .h file is where the designer does all of its work and where you're expected to add your own code. The .cpp file is merely a legacy from days gone by and serves only to include the precompiled header and the corresponding .h file. Only the wizard-generated Form1.cpp is at all interesting; it contains the entry point of the app (see Figure 9).

Figure 9 Form1.cpp

// Form1.cpp #include "stdafx.h" #include "Form1.h" #include <windows.h> using namespace CppWinForms; int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { System::Threading::Thread::CurrentThread->ApartmentState = System::Threading::ApartmentState::STA; Application::Run(new Form1()); return 0; }

For former C++ programmers who now use C# to build their Windows Forms applications, this syntax should feel either like waking from a dream or falling into a nightmare. Either way, it's pure managed C++, although all of the real action happens in the header file, as shown in Figure 10.

Figure 10 Header File

// Form1.h namespace CppWinForms { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public __gc class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: void Dispose(Boolean disposing) { if (disposing && components) { components->Dispose(); } __super::Dispose(disposing); } private: System::ComponentModel::Container * components; void InitializeComponent(void) { this->components = new System::ComponentModel::Container(); this->Size = System::Drawing::Size(300,300); this->Text = S"Form1"; } }; }

For C# programmers, this code should also be very familiar. The custom Form1 class derives from the Form base class. The form's constructor calls the InitializeComponent class (although without the condescending comment granting permission to add other code to this function). The Dispose function is there to handle integration with nonvisual components, using the __super keyword to call the base class. InitializeComponent is there to set up the initial properties of the form and the controls on the form. Just as in C# or Visual Basic .NET, this function is owned by the designer and should be left alone unless you like playing with fire (and getting burned).

After the wizard generates the initial C++ code, the designer adds member variables as you drag and drop controls onto the form, sets their properties in InitializeComponent as you make changes in the Property Browser, and adds event handlers for you. Honestly, it's all pretty boring until you realize that now you've got all of the convenience of the Windows Forms tools that Visual Basic .NET andC# programmers enjoy, married with the power of both managed and unmanaged C++. For example, one of my favorite features that managed C++ projects allow is adding unmanaged resources, something that neither C# nor Visual Basic .NET support. That's in addition to templates, multiple inheritance, and the mixing of managed and unmanaged code and types, which managed C++ is already known and loved for.

Compact Framework

In Visual Studio .NET 2003, not only can you build desktop applications in C#, Visual Basic .NET, managed C++, and J#, but you now have support for building smart device applications in C# and Visual Basic .NET. This functionality was available in beta form as an add-in for Visual Studio .NET 2002, but it's now built right into the product.

A smart device application runs on a Pocket PC or a machine running Windows CE. When you choose the Smart Device Application project template, you're actually allowed to build an application or a class library. Choosing to build provides you with a familiar environment, except that the common language runtime classes are fewer and smaller. For example, getting 98 percent of an existing Windows Forms game application ported to the .NET Compact Framework was easy, but the rest took some digging. Unlike the .NET Framework, the .NET Compact Framework has, at most, one way to do anything—so some of the shortcuts you're used to taking in Windows Forms are unavailable. For example, when trying to set the background bitmap on the control, I had to draw it during the Paint event. Likewise, reading the bitmap in from a resource wasn't as easy as I'm used to:

// Loading a bitmap from a resource in the .NET // Framework Bitmap logo = new Bitmap(this.GetType(), "sblogo.PNG"); // Loading a bitmap from a resource in the // Compact Framework Assembly assem = Assembly.GetExecutingAssembly(); string file = "PocketWahoo.sblogo.PNG"; Bitmap logo = new Bitmap(assem.GetManifestResourceStream(file));

However, there's nothing like running your Windows Forms application on another device, even if the device is being emulated, as shown in Figure 11.

Figure 11 Windows Forms in Emulator

Figure 11** Windows Forms in Emulator **

Mobile Code Security Settings

One more detail that you may find interesting has nothing to do with code at all, but with the security settings for smart clients and Windows Forms controls downloaded over the Web. In version 1.0, the .NET Framework provided the wonderful ability to host Windows Forms controls on a page in Internet Explorer, just like COM controls. Even better, version 1.0 introduced the idea of smart clients—that is, client applications executed from across the Web. If you haven't done it before, create a virtual directory, plop a Windows Forms application into it, and surf to it from Start | Run using a URL like this: https://localhost/itweb/hr452.exe. This will cause the application to be downloaded to the local machine and run like a standalone client app, but will be constrained by the permissions associated with where the app was launched from (see References in the SDK for more information about smart clients).

Both ways to download and host code from across the Web carry security considerations, of course, since that code could come from either the friendly intranet or the hostile Internet. To make sure that no bad things happen, the .NET Framework Code Access Security (CAS) model provides a partially trusted security sandbox for each assembly based on where it comes from. In version 1.0 SP0, assemblies from both intranet and Internet zones were allowed to run, but the Internet permissions were considerably reduced from intranet permissions. As of version 1.0 SP1, Microsoft changed the settings to disable code from the Internet altogether (although you can turn access back on). Now version 1.1 is a more secure release, and Microsoft has turned execution of code from the Internet back on by default without reducing the Internet permissions themselves. But that's not all. Version 1.1 of the .NET Framework also brings the Authenticode® model back from COM.

With the COM Authenticode model, if the user OK'd code to run, it could do absolutely anything it wanted, subject to the permissions of the user (most of whom run as administrators). If the code did something bad, a team of experts could track it, find the certificate, and bring the bad guys who wrote the code to justice. The .NET CAS model, on the other hand, is preventative in that the user is never asked, but the code only has a limited set of permissions if it's from a location other than the local hard drive. In practice, it turns out that both models have their uses. CAS is great, of course, for keeping bad things from happening. Authenticode, on the other hand, is good at letting the user know that code from outside their machine is about to execute and asking them if that's OK. Whether to ask or not is determined by the same Internet security settings that determine whether or not to ask for a COM control. The default settings for code from the Internet zone as of the final beta of version 1.1 are shown in Figure 12.

Figure 12 Version 1.1 Security Settings

Figure 12** Version 1.1 Security Settings **

You'll notice when executing code from the Internet zone that there are no user prompts by default. The same is true of the intranet zone. This is more permission than the default settings for ActiveX®/COM controls, which default to Prompt for signed controls and Disable for unsigned controls. Of course, for COM controls, Authenticode is all the security that's available while in the .NET Framework, there's all of CAS to continue to protect the user.

Where Are We?

Windows Forms in version 1.1 of the .NET Framework provides a number of new and interesting features, ranging from some needed additions to the Windows Forms and Drawing namespaces to entire new subsystems, like the Compact Framework and managed C++ and J# support in the designer. As if that weren't enough, new security settings allow you new choices for your common language runtime-targeted code deployed over the Web. For a city that normally gets a lot of rain, things are definitely rather sunny in Redmond.

For related articles see:
Visual Studio .NET: Managed Extensions Bring .NET CLR Support to C++
You Can Take It with You

For background information see:
.NET Zero Deployment: Security and Versioning Models in the Windows Forms Engine Help You Create and Deploy Smart Clients
Windows Forms: A Modern-Day Programming Model for Writing GUI Applications

Chris Sellsis an independent consultant, speaker, and author specializing in distributed applications in the .NET Framework and COM. More information about Chris and his various projects is available at https://www.sellsbrothers.com.