Building .NET Compact Framework 2.0 Applications That Have .NET Compact Framework 1.0 Code Compatibility
4/7/2010
Maarten Struys, PTS Software bv OpenNETCF.org
April 2006
Summary
Many organizations have investments in the .NET Compact Framework version 1.0 code in addition to hardware that the .NET Compact Framework version 2.0 does not support. This article discusses strategies for maintaining the code base when developing software for older devices, while enabling builds to take advantage of the .NET Compact Framework 2.0 features. OpenNETCF.org's Smart Device Framework can play a vital role by providing some .NET Compact Framework 2.0 functionality for devices that are capable of running only the .NET Compact Framework 1.0. Obviously, you cannot make a .NET Compact Framework 2.0 binary that runs on the .NET Compact Framework 1.0. Instead, this article provides tips, ideas, and strategies that you can use to build assemblies that are compatible with the .NET Compact Framework versions 1.0 and 2.0 from the same source tree. (30 printed pages)
Download BuildingCF2AppsWithCF1Compatibility.msi from the Microsoft Download Center.
Applies To
Microsoft® Visual C#® .NET
Microsoft Visual Studio® 2005
Microsoft .NET Compact Framework version 1.0
Microsoft .NET Compact Framework version 2.0
Microsoft Windows Mobile®
Introduction
Upgrading to Visual Studio 2005 and the .NET Compact Framework 2.0
Creating Different Versions of the Same Application
Using Conditional Compilation
Designing and Maintaining the User Interface
OpenNETCF.org's Smart Device Framework
Sharing Code Between Two Different Versions of an Application
Using a Single Source Base
Separating UI Code from Business Logic
Using Object-Oriented Techniques
Conclusion
Introduction
With the release of Visual Studio 2005, it is possible to develop managed applications for devices by using the .NET Compact Framework version 2.0. The recent version of the .NET Compact Framework offers much more functionality than its predecessor. An important drawback, however, is that not all devices on the market are capable of running .NET Compact Framework 2.0–connected applications. The .NET Compact Framework 2.0 will run on Windows Mobile version 5.0–based devices, on Windows Mobile 2003 Second Edition Pocket PCs, on Windows Mobile 2003 Pocket PCs, and on generic Windows® CE version 5.0–based devices. The .NET Compact Framework 2.0 will not run on the Windows Mobile 2002–based Pocket PCs, or other devices based on Windows CE .NET version 4.1 or 4.2.
These device specifications raise the following questions:
- How can you support a broad range of devices?
- How can you work with a single code base?
- How can you incorporate .NET Compact Framework 2.0 functionality and support devices that cannot run .NET Compact Framework 2.0 code?
This article provides answers to these questions through a discussion of conditional compilation, the OpenNETCF.org Smart Device Framework for devices that can run only Microsoft .NET Compact Framework version 1.0–based code, separating User Interface (UI) code from Business Logic, and object-oriented techniques such as inheritance.
Note
The application that this article discusses in order to demonstrate these approaches is deliberately simple. In this way, this article focuses on the goal of sharing source code, and avoids describing the full functionality of a complex application.
Upgrading to Visual Studio 2005 and the .NET Compact Framework 2.0
As long as you migrate completely to Visual Studio 2005 and target only devices that support the .NET Compact Framework 2.0 from now on, you can simply open your existing project in Visual Studio 2005. In doing so, Visual Studio 2005 automatically starts the Visual Studio Conversion Wizard. By following the wizard, you can create a backup of your original project. The wizard then creates a new solution and project file for your project in Visual Studio 2005. The conversion will not change your source code.
Even though you now can use your project inside Visual Studio 2005, the application still targets the .NET Compact Framework 1.0. If you want to target the .NET Compact Framework 2.0 instead, Visual Studio can once again convert your project by means of the Upgrade Project feature found on the Visual Studio Project menu.
Note
The Upgrade Project feature is available only if you have opened a .NET Compact Framework 1.0 application that can be upgraded to the .NET Compact Framework 2.0. If the application cannot be upgraded, the entry will not be visible on the menu.
Using the Upgrade Project feature to upgrade a .NET Compact Framework 1.0–based application is not reversible, and Visual Studio 2005 does not generate automatic backup copies. If you want the ability to revert to the original .NET Compact Framework 1.0 application, you must manually create a backup of your entire project before upgrading your project. In general, upgrading means that the .NET Compact Framework 2.0 assemblies are now referenced in your project—even though your own source files remain unchanged. If you build and deploy the application, it still contains the same functionality but it targets the .NET Compact Framework 2.0.
The following devices can run .NET Compact Framework 2.0 applications:
- Windows Mobile 5.0–based Pocket PCs and Smartphones
- Windows Mobile 2003–based Pocket PCs
- Windows Mobile 2003 Second Edition–based Pocket PCs
- Generic Windows CE 5.0–based devices
There is one drawback to upgrading your projects by using the Upgrade Project feature. Because the original source files are unchanged when you upgrade your project, the upgraded project will not make use of partial classes—a new feature that is available to applications written in version 2.0 of the C# programming language.
You can use partial classes to split a class over a number of different source files. Visual Studio 2005 uses partial classes to hide form designer generated code from the developer by storing the form designer generated code in a separate source file. When a .NET Compact Framework 1.0 project is upgraded to .NET Compact Framework 2.0, the form designer in Visual Studio 2005 deals with generated code the same way Visual Studio .NET 2003 does: by combining the form designer generated code and the code that you write into one single source file, rather than using partial classes to put the form designer generated code in a separate source file as it does when creating a .NET Compact Framework 2.0 project from scratch.
Creating Different Versions of the Same Application
Consider the following scenario: You want to upgrade an existing application to take advantage of new functionality of the .NET Compact Framework 2.0, but you also need to support the original version of the application that runs on the .NET Compact Framework 1.0. As long as you target only devices that Visual Studio 2005 supports, you can start by upgrading your existing Visual Studio .NET 2003 solution as described earlier. You will end up with an upgraded solution in Visual Studio 2005 that still targets the .NET Compact Framework 1.0. You can verify that by looking at the version number of one of the .NET Compact Framework assemblies, as indicated by the red circle in the following figure.
Figure 1. A .NET Compact Framework 1.0 application in Visual Studio 2005
If you now want to have the same application target the .NET Compact Framework 2.0 as well, you can create a new project inside the same solution. Make sure that this new project is a Device Project. Visual Studio 2005 automatically creates a project for you that targets the .NET Compact Framework 2.0. To make sure that you will use the same source files as the original project, you must delete all source files from the just-created project. The source files you need to delete are named Form1.cs and Program.cs. Next, you must add the source files from the original .NET Compact Framework 1.0 application to the new project. To ensure that you use the same source files in both projects, add them as a link. To add a source file as a link, right-click on a project in Solution Explorer, and then click Add Existing Item. In the Add Existing Item dialog box, select the file that you want to add. Instead of clicking the Add button, click the arrow to the right of it (as shown in the following figure), and then click Add As Link.
Figure 2. Adding a source file as a link
In Solution Explorer, you get a visual indication when a file is added as a link. When you see an arrow by the lower-left side of a file, that file is a link to another file. In the following figure, Form1.cs is added as a link to the CF2Application. The physical file only exists once.
Even though Form1.cs is now identical for both projects, you can also see in Figure 3 that the newly added project targets the .NET Compact Framework 2.0, as indicated by the red circle.
Figure 3. A .NET Compact Framework 2.0 application in Visual Studio 2005
Note
Due to an incompatibility between the .resx files in Visual Studio .NET 2003 and the .resx files in Visual Studio 2005, sharing UI source code between two different projects will work only if you use only Visual Studio 2005. Unless the original Visual Studio .NET 2003 project has been upgraded to Visual Studio 2005 by means of the Visual Studio Conversion Wizard, Visual Studio 2005 cannot read the .resx files that are generated by Visual Studio .NET 2003.
Building the entire solution that is shown in Figure 3 results in having two different versions of the same application: one targets the .NET Compact Framework 1.0 and the other targets the .NET Compact Framework 2.0. Because you added the Form1.cs source file to the new CF2Application project as a link, making changes to this source file in one project will also influence the other project.
Using Conditional Compilation
In the sample solution that is shown in Figure 3, you may want to use functionality in the .NET Compact Framework 2.0 project that is not available in the .NET Compact Framework 1.0. To do so while maintaining a single code base, use conditional compilation. Conditional compilation enables you to select specific sections of code to compile, while excluding other sections. By using conditional compilation, you can distinguish between shared code and code that is specific for either one of the .NET Compact Framework versions that you want to target. To make use of this technique, you can add conditional compilation directives that will compile only that code that is relevant for the particular version of the .NET Compact Framework that you want to target. Conditional compilation statements influence the way an application is compiled.
The following code example demonstrates how to use conditional compilation.
#if COMPILE_FOR_CF1
// execute CF 1.0 specific code
#else
// execute CF 2.0 specific code
#endif
To be able to have specific code for one of the different versions of the application, you can define your own conditional compilation symbols. You can define conditional compilation symbols in source code or in the project's Properties box. If you have two different versions of the same application, each targeting a different version of the .NET Compact Framework, we recommend that you set the conditional compilation constants in the project settings for each project. In doing this, you assure that the C# compiler always compiles the correct code for a particular version of the application. In the Build dialog box of the Project Settings, you can set conditional compilation symbols, as shown in Figure 4. You can set as many different conditional compilation symbols as you need for each of the projects in your solution. To set a conditional compilation symbol in the project settings, follow these steps:
- In Solution Explorer, select the CF1Application project.
- On the Project menu in Visual Studio 2005, select the CF1Application Properties entry.
- The CF1Application Properties dialog box is displayed.
- In the CF1Application Properties dialog box, select the Build tab.
- Find the Conditional compilation symbols text box, and enter the NETCF1 symbol, preceded by a white space.
To add a conditional compilation symbol to the CF2Application project inside the same solution, follow these steps:
- In Solution Explorer, select the CF2Application project.
- On the Project menu in Visual Studio 2005, select the CF2Application Properties entry.
- The CF2Application Properties dialog box is displayed.
- In the CF2Application Properties dialog box, select the Build tab.
- Find the Conditional compilation symbols text box, and enter the NETCF2 symbol, preceded by a white space.
Figure 4. Setting conditional compilation symbols in Visual Studio 2005
Figure 4 shows how to set a conditional compilation symbol for the CF1Application in the SampleApplication solution. Similarly, you have set a different symbol for the CF2Application. In Solution Explorer, you can see that Form1.cs as part of the CF2Application project is a linked file, which is indicated by the arrow in the file icon. The Form1.cs file is the same physical file as the file that is in the CF1Application project.
If you modify the code in the button1_Click event handler, you can execute specific actions for the CF1Application and CF2Application projects, making use of the conditional compilation symbols that you just added.
Note
When you modify the code of Form1.cs in one project, you automatically modify the code for the other project as well.
The following code example shows how to use the conditional compilation symbols in the button1_Click event handler.
private void button1_Click(object sender, System.EventArgs e)
{
#if NETCF1
MessageBox.Show("Hello, CF1.0");
#elif NETCF2
MessageBox.Show("Hello, CF2.0");
#endif
}
Note
Because you defined the conditional compilation symbols as part of your project files, the code under #if NETCF1 is compiled when you build the CF1Application. In addition, the code under #elif NETCF2 is automatically populated when you are building the CF2Application.
It is also possible to target a different device for each application, as shown in the following figure.
Figure 5. Running the same code base in different devices by using different versions of the .NET Compact Framework
Designing and Maintaining the User Interface
If you want to maintain one code base for applications that target both the .NET Compact Framework 1.0 and the .NET Compact Framework 2.0, you must decide in advance how you want to design or maintain your application's specific parts of the UI.
After creating an application that uses a single code base, targets each version of the .NET Compact Framework, and runs on different devices, you may encounter UI challenges. Due to differences in their respective form designer generated code, it is not easy to share UI functionality among .NET Compact Framework 1.0 applications and .NET Compact Framework 2.0 applications. Even for a simple application, the UI is the first area where you might expect to encounter problems.
Autogenerated code in Visual Studio 2005 differs from autogenerated code in Visual Studio .NET 2003. When you create a Visual C# Smart Device Application by using Visual Studio .NET 2003, as shown in Figure 6, Visual Studio .NET 2003 generates all of the code that is required to create UI controls on the form.
Figure 6. Simple application, created in Visual Studio .NET 2003, running in the Pocket PC 2002 emulator
You will not encounter issues when you change the UI if you add new .NET Compact Framework 1.0–based controls and you add the controls exclusively inside the CF1Application project. The changes will also be evident in CF2Application project.
However, if you make changes to the UI inside the CF2Application project and then try to open the modified form in the CF1Application project, you will encounter difficulties. The issues arise even if you add only controls that are supported by the .NET Compact Framework 1.0.
For example, suppose you want to add a label to the form by using the form designer that is inside the CF2Application project. As expected, the code to create the label will automatically be added to the form designer generated code. The form designer, however, is adding the label with several properties that are available only in the .NET Compact Framework 2.0. For example, a label.Name property is automatically added. This property is not available in the .NET Compact Framework 1.0. Therefore, when you try to build the original CF1Application project, you get compilation errors.
The form designer also adds properties and methods for other controls that are not available in the .NET Compact Framework 1.0, such as a TabIndex property for controls that can accept keyboard input, and SuspendLayout and ResumeLayout methods to increase application performance.
Note
The form designer automatically generates all of the code for your UI; therefore, you will lose your own changes every time you are using the form designer to modify your UI.
One approach may be to use conditional compilation to create two separate areas with form designer generated code, one for the .NET Compact Framework 1.0 and one for the .NET Compact Framework 2.0, as shown in Figure 7. To do this, copy all of the form designer generated code, and surround it with conditional compilation symbols. Your projects will then compile. All of the code that is generated by Visual Studio .NET 2003 is clearly marked because it is surrounded by the following #region directive in the following code example taken from the Form1.cs source file.
#region Windows Form Designer generated code
<code that the is autogenerated>
#endregion
If you copy all of the form designer generated code, and surround each version of the autogenerated code with a conditional compilation directive, you create separate versions of autogenerated code, each of which will only be updated in whichever project targets the corresponding version of the .NET Compact Framework.
#if NETCF1
#region Windows Form Designer generated code
<code that the is autogenerated for the .NET Compact Framework 1.0>
#endregion
#elif NETCF2
#region Windows Form Designer generated code
<code that the is autogenerated for the .NET Compact Framework 2.0>
#endregion
#endif
Figure 7. Duplicating form designer generated code
When you experiment with this approach, you will see that the UI changes that you make to project are stored in the form designer generated code. However, if you want to apply the same UI changes to the other project, you must explicitly make those changes for that project as well. You This doubles the amount of work required to make UI changes to multiple projects, even though you are working in a single source file.
OpenNETCF.org's Smart Device Framework
If you need to support applications that target Windows Mobile–based devices, such as Pocket PC, Pocket PC 2002, or Smartphone 2003, or if you cannot install the .NET Compact Framework 2.0 for use on newer devices, you might benefit from OpenNETCF.org's Smart Device Framework (SDF).
In version 1.4, OpenNETCF.org's SDF is an extensive set of classes that complement the .NET Compact Framework. It is compatible with the .NET Compact Framework 1.0 and the .NET Compact Framework 2.0. The SDF is a robust, shared source library that integrates seamlessly into Visual Studio .NET 2003. The SDF provides many of the classes, properties, and methods that are available in the full .NET Framework, plus many additional classes that are specific to the Windows CE environment.
The SDF provides significant .NET Compact Framework 2.0 functionality for use with .NET Compact Framework 1.0–connected applications. The SDF contains a number of UI controls that are not available in the .NET Compact Framework 1.0, but are available in the .NET Compact Framework 2.0. The SDF also contains many extensions to existing classes in the .NET Compact Framework 1.0. The SDF and the .NET Compact Framework offer carefully selected namespaces, along with method and property name compatibility. In this way, you can use the SDF to create and maintain applications that run on multiple devices and that target the .NET Compact Framework 1.0 and the new functionality in the .NET Compact Framework 2.0.
For several controls that are available in the .NET Compact Framework 1.0, the SDF contains enhanced functionality that matches the functionality of those controls in the .NET Compact Framework 2.0. For example, Figure 8 lists the new controls in the "Windows.Forms" namespace for the .NET Compact Framework 2.0.
Figure 8. New controls in the "Windows.Forms" namespace for the .NET Compact Framework 2.0
In Figure 8, the controls in blue are also available in the SDF, usually with the same properties and methods as in the .NET Compact Framework 2.0 version of the controls, although they sometimes have slight differences. To distinguish between .NET Compact Framework controls and SDF controls, the SDF controls are part of the "OpenNETCF.Windows.Forms" namespace. The SDF contains additional UI controls, and it extends and adds many other classes of which developers who use the .NET Compact Framework can take advantage.
Note
Version 2.0 of OpenNETCF.org’s Smart Device Framework is not covered in this article. The Smart Device Framework 2.0 only works with the .NET Compact Framework 2.0. Because this article is about .NET Compact Framework 1.0 compatibility, this article only makes use of OpenNETCF.org’s Smart Device Framework 1.4.
Sharing Code Between Two Different Versions of an Application
There are two good approaches for sharing UI code among .NET Compact Framework version 1.0 and 2.0–connected applications. One approach is to manually add controls to your forms, instead of using the form designer. You can still use the form designer in a temporary form to determine locations and sizes for particular controls. In your production code, however, you manually copy those settings and the controls into your source file.
The other approach is to separate business logic from UI code. Keep the business logic in shared source files, and keep different source files for your UI code. In that way, you can benefit from the additional properties and methods that are available for .NET Compact Framework 2.0–connected applications. In addition, you have the advantage of using partial classes to separate autogenerated code from your own code in that application. If you separate business logic from UI code, you can also make use of object-oriented techniques such as inheritance.
The following sections explore these two alternatives for sharing source code.
Using a Single Source Base
The next sample application has a single form that contains a DateTimePicker control, a Label control, and a TextBox control. The application is designed to select a date, to show it in the TextBox control, and store the selected date in the registry on the device. The application must work for the .NET Compact Framework versions 1.0 and 2.0, while sharing as much source code as possible. Because a DateTimePicker control is not available in the .NET Compact Framework 1.0, you must make use of OpenNETCF.org's Smart Device Framework 1.4 for the version of the application that works with the .NET Compact Framework 1.0. Since the Smart Device Framework 1.4 integrates with Visual Studio .NET 2003, the .NET Compact Framework 1.0 application is developed in Visual Studio .NET 2003. The .NET Compact Framework 2.0 version of the application is developed in Visual Studio 2005.
Figure 9 illustrates the UI of the application inside Visual Studio .NET 2003. In the ToolBox, you can see familiar .NET Compact Framework 1.0–based device controls and an additional set of OpenNETCF Controls. You can use these controls when you install the Smart Device Framework.
Figure 9. UI of a sample application to demonstrate sharing source code
The application uses a DateTimePicker control, which is not available in the .NET Compact Framework 1.0. However, by using the installed Smart Device Framework, you can use a DateTimePicker control in the .NET Compact Framework 1.0 application by dragging it from OpenNETCF Controls to the form. In doing so, you are making use of .NET Compact Framework 2.0 functionality inside a .NET Compact Framework 1.0 application.
In the following code example, you can see three empty event handlers, one for the Form1_Load event, one for the Form1_Closing event, and one for the dateTimePicker1_ValueChanged event. This code—running under the .NET Compact Framework 1.0—is the starting point for an application that uses two different approaches to share as much source code as possible between a version running under the .NET Compact Framework 1.0 and a version running under the .NET Compact Framework 2.0. The first approach to sharing code is to separate UI Code from Business Logic. The second approach to sharing code is to make use of Object Oriented Techniques. Both are described in the upcoming sections.
After you add a number of event handlers to the application, the source code for this form looks like the following code example (omitting the code that the form designer inserts).
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Data;
namespace SmartDeviceApplication1
{
public class Form1 : System.Windows.Forms.Form
{
private OpenNETCF.Windows.Forms.DateTimePicker dateTimePicker1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.MainMenu mainMenu1;
public Form1()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
base.Dispose( disposing );
}
static void Main()
{
Application.Run(new Form1());
}
private void Form1_Load(object sender, System.EventArgs e)
{
}
private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
}
private void dateTimePicker1_ValueChanged(object sender, System.EventArgs e)
{
}
}
}
Separating UI Code from Business Logic
You can share as much code as possible between .NET Compact Framework version 1.0 and 2.0 applications by separating business logic from UI code.
Like most applications, the sample application used in this article takes action upon receiving different events. The actions are executed in the different event handlers of the application. Apart from connecting event handlers to UI controls, you should try to implement any other code in one or more separate classes that provide the functionality for the application. To do so, you can create a new class in your .NET Compact Framework 1.0 project and create methods that will be called by the different event handlers inside Form1, as shown in the class diagram in the following figure.
Figure 10. Implementing business logic in a separate class
As you might expect, and as shown in the class diagram in Figure 10, the functionality to be executed in the application is put into the Form1Logic class. The only functionality for which Form1 is responsible is to maintain the UI and to call a method inside Form1Logic each time the user requests a particular action. The code inside Form1Logic can be shared between different versions of the application. As described earlier in this article, the functionality of the application is very simple:
- In the Form1_Load event handler, the code will inspect the device registry to determine whether a selected date has been stored for the application. If it has, the program should retrieve that date, set it to the DateTimePicker control, and set it to the TextBox control. If no information is found in the registry, the current date will appear in both controls. The functionality to retrieve date information from the registry is implemented in the RetrieveLastDate method of the Form1Logic class.
- In the Form1_Closing event handler, the code will store the date that is currently selected in the DateTimePicker control into the device registry. The functionality to achieve this action is implemented in the StoreLastDate method of the Form1Logic class.
- In the dateTimePicker1_ValueChanged event handler, the code will copy the newly selected date to the TextBox control. Because no additional functionality is needed—this action is an update of a UI control—you can decide to leave that functionality inside Form1.
Inside Form1, you need to provide access to Form1Logic. To achieve this access, you can declare a variable of type Form1Logic in Form1 and instantiate it in the constructor of Form1, as you can see in the following code example.
private Form1Logic logic;
public Form1()
{
InitializeComponent();
logic = new Form1Logic();
}
Assuming that you have created the Form1Logic class, which contains all of the functionality that needs to be executed, you need to call the methods of Form1Logic only when events occur. The following code example shows the three different event handlers that are defined in Form1. Two of the event handlers are calling methods in Form1Logic, and one does a simple UI update.
private void Form1_Load(object sender, System.EventArgs e)
{
dateTimePicker1.Value = logic.RetrieveLastDate();
textBox1.Text = dateTimePicker1.Value.ToLongDateString();
}
private void Form1_Closing(object sender, <br>System.ComponentModel.CancelEventArgs e)
{
logic.StoreLastDate(dateTimePicker1.Value);
}
private void dateTimePicker1_ValueChanged(object sender, <br>System.EventArgs e)
{
textBox1.Text = dateTimePicker1.Value.ToLongDateString();
}
As you can see from the preceding code example, you have to maintain very little code inside Form1. This is an important point because Form1 will not be shared among the different versions of the application due to the form designer issues described earlier in this article. Omitting the functionality of Form1Logic for now, you need to create a new application in Visual Studio 2005 with the same UI as the previous application that will target the .NET Compact Framework 2.0. You need to add the same event handlers to get the same behavior for the application.
As you can see in Solution Explorer in Figure 11, the project already contains the class Form1Logic, which is implemented in the file Form1Logic.cs. Form1Logic is added as a link, as described earlier in this article. Therefore, the project has one physical source file—Form1Logic.cs—that will be shared between a .NET Compact Framework 1.0–connected application (CF1 application) that was developed in Visual Studio .NET 2003, and a .NET Compact Framework 2.0–connected application (CF2 application) that was developed in Visual Studio 2005.
Figure 11. UI of the sample application built in Visual Studio 2005
Similar to the code for the CF1 application, you can call methods of Form1Logic when events occur. The following code example shows the three different event handlers that are defined in Form1 for the CF2 application. Two of the event handlers are calling methods in Form1Logic; one does a simple UI update.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace DeviceApplication1V2
{
public partial class Form1 : Form
{
private Form1Logic logic;
public Form1()
{
InitializeComponent();
logic = new Form1Logic();
}
private void Form1_Load(object sender, EventArgs e)
{
dateTimePicker1.Value = logic.RetrieveLastDate();
textBox1.Text = dateTimePicker1.Value.ToLongDateString();
}
private void Form1_Closing(object sender, CancelEventArgs e)
{
logic.StoreLastDate(dateTimePicker1.Value);
}
private void dateTimePicker1_ValueChanged(object sender, <br>EventArgs e)
{
textBox1.Text = dateTimePicker1.Value.ToLongDateString();
}
}
}
As you can see, the code for the event handlers in the V2 application is identical to the code for the event handlers in the V1 application. The most important difference is that you now use the DateTimePicker control that is available in the .NET Compact Framework 2.0 instead of the DateTimePicker control from the OpenNETCF.org Smart Device Framework.
Because the DateTimePicker control in the Smart Device Framework uses the same properties and methods that are available for the .NET Compact Framework 2.0 DateTimePicker control, the code looks exactly the same.
The source code in Form1Logic.cs is most interesting because both applications share it.
using System;
using System.Windows.Forms;
#if NETCF1
using OpenNETCF.Win32;
#elif NETCF2
using Microsoft.Win32;
#endif
namespace BusinessLogic
{
/// <summary>
/// Summary description for Form1Logic.
/// </summary>
public class Form1Logic
{
private const string keyName = <br>"Software\\SmartDeviceApplication1";
private const string valueNameYear = "SelectedYear";
private const string valueNameMonth = "SelectedMonth";
private const string valueNameDay = "SelectedDay";
private const string valueNameHour = "SelectedHour";
private const string valueNameMinute = "SelectedMinute";
private const string valueNameSecond = "SelectedSecond";
public Form1Logic()
{
//
// TODO: Add constructor logic here
//
}
public DateTime RetrieveLastDate()
{
DateTime lastDate;
// Create / Open a registry key to retrieve the last selected<br> // date.
RegistryKey rk = Registry.LocalMachine.OpenSubKey(keyName);
if (rk == null)
{
// No value available in the created/opened subkey
lastDate = DateTime.Now;
}
else
{
int year = (int)rk.GetValue(valueNameYear);
int month = (int)rk.GetValue(valueNameMonth);
int day = (int)rk.GetValue(valueNameDay);
int hour = (int)rk.GetValue(valueNameHour);
int minute = (int)rk.GetValue(valueNameMinute);
int second = (int)rk.GetValue(valueNameSecond);
rk.Close();
lastDate = new DateTime(year, month, day, hour, minute,<br> second);
}
return lastDate;
}
public void StoreLastDate(DateTime lastDate)
{
// Create/open a registry key to retrieve last selected <br> // date.
RegistryKey rk = <br>Registry.LocalMachine.CreateSubKey(keyName);
if (rk == null)
{
MessageBox.Show("Could not create / open the registry <br>key");
}
else
{
rk.SetValue(valueNameYear, lastDate.Year);
rk.SetValue(valueNameMonth, lastDate.Month);
rk.SetValue(valueNameDay, lastDate.Day);
rk.SetValue(valueNameHour, lastDate.Hour);
rk.SetValue(valueNameMinute, lastDate.Minute);
rk.SetValue(valueNameSecond, lastDate.Second);
rk.Close();
}
}
}
}
In the preceding code example, conditional compilation enables us to distinguish between using directives to import types that are defined either in the "OpenNETCF.Win32" namespace or in the "Microsoft.Win32" namespace. Each application is designed to use the registry to store and retrieve data. The CF1 application does not have classes to access the registry. Therefore, in the CF1 application, you use registry classes that are available in the Smart Device Framework in the "OpenNETCF.Win32" namespace.
Because the .NET Compact Framework 2.0 has registry classes available, you can use those registry classes instead of the Smart Device Framework classes in the CF2 application. Again, the methods and properties of each set of registry classes are the same. You only need to refer to the correct namespace in order to use the particular registry classes that you require. The preceding code example is simple, and most of it can be shared between the two different versions of the application because the used SDF classes and .NET Compact Framework 2.0 classes closely match. You will probably need to do more work when working with a more complex application, but the approach can be the same.
When using a separate class for business logic, you must distinguish clearly between UI code and actual functionality. The UI code will be duplicated. In the UI code, you will have to call different methods that implement the business logic.
Using Object-Oriented Techniques
Another approach is to make use of object-oriented techniques like inheritance. In this approach, a class—the derived class—gains the nonprivate data and behavior of the base class in addition to other data or behaviors that it defines for itself. If you derive a class from Form1 and add business logic to that class, the derived class can also be a candidate for source code sharing between different versions of the application.
To demonstrate this approach, the functionality of the previous code example is implemented in a different way. The use of inheritance has advantages because you can make use of the polymorphism capabilities of the C# programming language in the following ways:
- You can change the data and behavior of a base class.
- You can replace the base member with a new derived member.
- You can override a virtual base member.
When you ensure that the event handlers in the base class (Form1) are defined as protected virtual, they are candidates for overriding default behavior. You will make use of overriding functionality in the remainder of this article.
For this approach, as shown in the class diagram in Figure 12, the functionality to be executed in the application is again placed into the Form1Logic class, but Form1Logic inherits from Form1 and overrides the event handlers that were defined in the base class. Again, Form1 is responsible only for maintaining the UI. Because you now derive Form1Logic from Form1, however, there is no need to explicitly call member functions of Form1Logic.
Figure 12. Business logic in an inherited class
The UI for this sample application is exactly the same as the UI in the preceding code example as shown in Figure 11. In the following code example for a CF1 application, both the Form1_Load and Form1_Closing event handlers are declared as protected virtual, meaning that they can be overridden in a derived class. These event handlers contain no code. The actual implementation is in the derived class Form1Logic, that you can find later in this article. Even though the dateTimePicker1_ValueChanged event handler is declared as protected virtual as well, it does contain code to update the Text property of a text box. This means that a derived class may choose to override this functionality, but if it does not do so, the code in the base class will be executed.
The code example that follows also reveals a remarkable characteristic. Instead of instantiating a new Form1 class in the Application.Run method, you now instantiate the derived class Form1Logic. Because Form1Logic is derived from Form1, Form1Logic is in fact a form. Therefore, it can be passed to the Application.Run method.
static void Main()
{
Application.Run(new Form1Logic());
}
protected virtual void Form1_Load(object sender, System.EventArgs e)
{
}
protected virtual void Form1_Closing(object sender, <br>System.ComponentModel.CancelEventArgs e)
{
}
Protected virtual void dateTimePicker1_ValueChanged(object sender, <br>System.EventArgs e)
{
textBox1.Text = dateTimePicker1.Value.ToLongDateString();
}
To provide access to the different UI controls in the derived class, you also have to ensure that the Modifiers property of the UI controls to which you need access (in the sample, dateTimePicker1 and textBox1) are changed from private to protected.
Again, you need to create a separate project for a CF2 application with the same UI as the CF1 application, this time targeting the .NET Compact Framework 2.0. You need to add the same event handlers to get the same behavior in the application. To use functionality from the derived class Form1Logic while keeping it as a single source file for both the CF1 and CF2 applications, you need to add it to your project as a linked file, as described in the Create Different Versions of the Same Application section earlier in the article.
The following code example for the Form1 class of the CF2 application is similar to the code of the CF1 application shown previously.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace DeviceApplication2V2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected virtual void Form1_Load(object sender, EventArgs e)
{
}
protected virtual void Form1_Closing(object sender, <br>CancelEventArgs e)
{
}
protected virtual void dateTimePicker1_ValueChanged(object <br>sender, EventArgs e)
{
textBox1.Text = dateTimePicker1.Value.ToLongDateString();
}
}
}
In .NET Compact Framework 2.0 applications, the Main method is moved to its own source file: Program.cs. Instead of instantiating a new Form1 class in the Application.Run method, you again must instantiate the derived class Form1Logic. With that change, the Program.cs file will look like the following code example.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace DeviceApplication2V2
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[MTAThread]
static void Main()
{
Application.Run(new Form1Logic());
}
}
}
When you are using inheritance to share code between two different versions of your application, you should be aware of the following issues. Because you derive Form1Logic from Form1, Form1Logic is in fact a form. Inside both versions of Visual Studio, Form1Logic appears as a form in Solution Explorer. It even seems as though you can open Form1Logic in the form designer. In Visual Studio .NET 2003, you get an error when you try to open Form1Logic in the form designer, because the .NET Compact Framework 1.0 does not support visual inheritance. However, in Visual Studio 2005, you can open Form1Logic in the form designer. In addition, it may seem as though you can add more controls to your derived form. Doing so, however, will lead to problems.
Because you added Form1Logic as a link to the V2 project, the actual Form1Logic source file is located in a different folder than the other files of the V2 application. When you open Form1Logic with the form designer, Visual Studio 2005 attempts to open and create a Form1Logic.resx file. In doing so, it apparently detects that Form1Logic.cs is located somewhere else, which results in the error shown in Figure 13.
Figure 13. Error when you try to add controls to the derived form by using the form designer
If you want to add additional controls to your V2 application, you should add them to the base class (Form1). This advice is not a limitation because the base class is unique for the V2 application.
Again, the source code is most interesting in Form1Logic.cs because that code is shared between both applications. The following code example for Form1Logic.cs illustrates that Form1Logic is now derived from Form1. Again, you use conditional compilation to distinguish between V1 and V2 application logic. It is now also evident that one method is exclusively available for the V2 application—dateTimePicker1_ValueChanged.
using System;
using System.Windows.Forms;
#if NETCF1
using OpenNETCF.Win32;
#elif NETCF2
using Microsoft.Win32;
#endif
#if NETCF1
namespace SmartDeviceApplication2
#elif NETCF2
namespace DeviceApplication2V2
#endif
{
/// <summary>
/// Summary description for Form1Logic.
/// </summary>
public class Form1Logic : Form1
{
private const string keyName = <br>"Software\\SmartDeviceApplication2";
private const string valueNameYear = "SelectedYear";
private const string valueNameMonth = "SelectedMonth";
private const string valueNameDay = "SelectedDay";
private const string valueNameHour = "SelectedHour";
private const string valueNameMinute = "SelectedMinute";
private const string valueNameSecond = "SelectedSecond";
public Form1Logic()
{
//
// TODO: Add constructor logic here
//
}
protected override void Form1_Load(object sender, EventArgs e)
{
// Create/open a registry key to retrieve last selected <br> // date.
RegistryKey rk = Registry.LocalMachine.OpenSubKey(keyName);
if (rk == null)
{
// No value available in the created/opened subkey
dateTimePicker1.Value = DateTime.Now;
}
else
{
int year = (int)rk.GetValue(valueNameYear);
int month = (int)rk.GetValue(valueNameMonth);
int day = (int)rk.GetValue(valueNameDay);
int hour = (int)rk.GetValue(valueNameHour);
int minute = (int)rk.GetValue(valueNameMinute);
int second = (int)rk.GetValue(valueNameSecond);
rk.Close();
dateTimePicker1.Value = new DateTime(year, month, day, <br>hour, minute, second);
}
textBox1.Text = dateTimePicker1.Value.ToLongDateString();
}
protected override void Form1_Closing(object sender, <br>System.ComponentModel.CancelEventArgs e)
{
// Create/open a registry key to retrieve last selected <br>date.
RegistryKey rk = <br>Registry.LocalMachine.CreateSubKey(keyName);
if (rk == null)
{
MessageBox.Show("Could not create / open the registry <br>key");
}
else
{
rk.SetValue(valueNameYear, dateTimePicker1.Value.Year);
rk.SetValue(valueNameMonth, <br>dateTimePicker1.Value.Month);
rk.SetValue(valueNameDay, dateTimePicker1.Value.Day);
rk.SetValue(valueNameHour, dateTimePicker1.Value.Hour);
rk.SetValue(valueNameMinute, <br>dateTimePicker1.Value.Minute);
rk.SetValue(valueNameSecond, AUTHOR: See CD48.dateTimePicker1.Value.Second);
rk.Close();
}
}
#if NETCF2
protected override void dateTimePicker1_ValueChanged(object <br>sender, EventArgs e)
{
textBox1.Text = dateTimePicker1.Value.ToShortDateString();
}
#endif
}
}
Just as the sample that uses a separate class to implement the business logic, deriving a class from Form1 results in reasonably simple code. Again, most of the code can be shared between the two different versions of the application. This capability is due to the fact that the used Smart Device Framework classes and .NET Compact Framework 2.0 classes closely match.
This way of sharing source code has an advantage over the previously described way because less application-specific code needs to be written in the Form1 class. However, the application may experience a performance penalty because overwriting methods in a derived class results in an indirect call to the particular method. In fact, two separate calls are needed to reach the desired method. When you use inheritance, you must be careful not to open the derived classes in the form designer because you will encounter problems, as shown in figure 13. Also, because functionality is now found in a class derived from a Windows.Form class, the distinction between UI code and business logic is less clear.
Running each version of the application results in a slight difference in behavior, because the V2 application overrides an additional method to show a selected date in a short format in the textbox. Figure 14 shows both versions of the application, running on different devices with different versions of the .NET Compact Framework. The emulator on the left side of Figure 14 shows the .NET Compact Framework 1.0 version of the application running on a Pocket PC 2002 emulator. This version makes use of OpenNETCF.org’s Smart Device Framework. On the right side is the .NET Compact Framework 2.0 version of the application running on a Windows Mobile 5.0 Pocket PC emulator. As you can see, the user experience for both versions of the application is the same.
Figure 14. CF1 and CF2 application sharing the same source code
Conclusion
As this article has explained, it is possible—but not simple—to share source code for an application that targets both the .NET Compact Framework 1.0 and the .NET Compact Framework 2.0. Using conditional compilation, you can compile code that is specific to one version of the application. With OpenNETCF.org's Smart Device Framework, you can use .NET Compact Framework 2.0-like functionality inside a .NET Compact Framework 1.0 application.
Having one code base for your application while targeting different versions of the .NET Compact Framework has advantages. It will be much easier to maintain and extend your application, because changes need to be made in just one single source file. To be successful, though, you must separate business logic from UI logic. You must also (as discussed in this article) maintain separate source files for your UI logic because of incompatibility between the form designers of Visual Studio .NET 2003 and Visual Studio .NET 2005. You can use an entire single source base only if you bypass the form designer and manually create all UI controls.
The different approaches to sharing source code that are described in this article are only useful if you are releasing similar applications that target different versions of the .NET Compact Framework.