[ Team LiB ] |
9.2 .NET Compact Framework
Instead of accessing a web application from a browser, the .NET Compact Framework allows you to write rich clients and stand-alone applications that actually run on the mobile devices themselves. These applications allow the device users to be productive without being connected to the network. Unless you like to deal with the complexity of writing code directly to the device's operating system API, .NET Compact Framework is the answer to writing mobile device applications. Being the subset of the .NET Framework, the Compact Framework provides the same common language runtime and managed code execution. Architecturally, the .NET Compact Framework acts as the abstraction layer on top of the device-specific API so that the mobile applications written on the .NET Framework can be device independent. Figure 9-16 shows the .NET Compact Framework in relation to the mobile application and the device API. Figure 9-16. NET Compact Framework application architectureIn addition to being abstracted from the native operating system services, these mobile applications also inherit other advantages such as fault isolation, security, and better resource management, because they run within separate application domain hosts. Currently, the .NET Compact Framework supports Pocket PC and Windows CE .NET-based platforms. 9.2.1 The .NET Framework Versus the .NET Compact FrameworkHaving a much smaller footprint than the .NET Framework, the .NET Compact Framework provides only the core functionality for writing applications for mobile devices. The following list summarizes the main differences between the two frameworks.
Visual Studio .NET 2003 fully support the development and deployment of .NET applications on mobile devices. To show how easy it is to write a mobile device application with the Compact Framework, let's write and demonstrate (using an emulator) a simple Hello World program that displays the words "Hello World" in a Windows message box on a Pocket PC. The first thing you do is to create a New Project in Visual Studio .NET 2003. Choose the language of your choice and pick the Smart Device Application template in the New Project window. Click OK. The Smart Device Application Wizard window shown in Figure 9-17 will be displayed. Figure 9-17. Smart device wizardThe two platforms currently supported are Pocket PC and Windows CE. For each of these platforms, there are a number of project templates you can choose from: Windows Application, Class Library, Non-graphical Application, or Empty Project. Because you want to write a standard Windows style application that targets a Pocket PC, you will accept the Wizard's default settings by clicking OK. VS.NET uses the Smart Device Application Template to generate all the files necessary for your application and presents you a blank form with a default menu attached to it. In this first example, you will not want to do anything with this default menu. You can also start to drag and drop controls from the toolbox onto the form and write code that associates with events for these controls the way you've always done it in previous VB environments. Again, in this example, we will not add any controls to the form but only write code to handle the Load event of the form and display a message box that says "Hello World!" To do this, just double click on the form itself. VS.NET creates the default handler for the load event and moves you to the code view where you can type the following VB.NET statement: Messagebox.Show("Hello World!") That's all to it. Now you start the debugging process by pressing the F5 key. The message shown in Figure 9-18 will appear to ask you to name the type of device to which you wish to deploy the program. We have defaulted our setting to the emulator so all we have to do at this point is click the Deploy button. VS.NET starts the emulator and deploys the application.[6]
Figure 9-18. Smart Device Application DeploymentFigure 9-19 shows the output of our first Compact Framework-enabled mobile device application. Figure 9-19. Hello World application running on Pocket PCNow that you know the process, you're ready for something a little more involved. 9.2.2 Mobile Devices and SQL Server 2000 CEA primary motivation for writing stand-alone applications for mobile devices (or any offline device) is because such devices are not always connected to enterprise resources or other data an application needs to do its work. Mobile device applications have to be able to extract the data they need when they are off line. Updates to the disconnected data must be merged enterprise data when it comes time for reconciliation. Disconnected data can be stored off-line in at least two ways. Data can be stored in a proprietary format file as XML that the application manipulates using custom code or an XML parser, such as the one provided with .NET. With SQL Server 2000 CE, the off-line data can also be stored in a relational database format and manipulated via traditional database management tools. The following two samples show a movie listing application where you can pick a movie and see where it's shown and the show times or you can pick a nearby theater and see the movies that the selected theater shows (also with show times). The first sample shows how this is done with just XML. The XML file represents the off-line data store that is downloaded into the mobile device periodically. For simplicity, we do not show how this file is generated (let's assume that there is a Web Service out there somewhere that we can ask for local theater and showing information). The example does not have any update to the data, hence, there is no data reconciliation needed. The second sample shows how the application can be implemented with SQL Server for CE. Here the .sdf file replaces the XML file, but the part of generating the .sdf file is conveniently omitted in order to simplify the presentation. SQL Server CE provides three methods for synchronization of data between the device and the enterprise data source: Pull, Push, and SubmitSQL. The Pull and Push methods allow your mobile device application to pull tables from and push tables to the enterprise database. Once the tables are on the mobile device, you don't need to have network connectivity in order for your application to work until you are ready to sync back to the enterprise database. The SubmitSQL method allows the mobile device application to send SQL directly to the enterprise database. This is obviously to keep the database state as synced as possible but it requires constant network connectivity. You can also setup SQL Server replication so that the application on the mobile device can subscribe and replicate part or the whole database. The replication is similar to how the standard SQL Server Replication works except that your node is on a mobile device and the connectivity between the mobile node and the publisher is through HTTP. As intended, our examples do not cover how to set up your development environment to enable Remote Data Access or SQL Server CE Replication. 9.2.2.1 Storing off-line data as XMLIn this example, we omit how the enterprise data (in this case, movie listing for a selected group of local theaters) XML is generated, and synced to the device. We start out with the assumption that the mobile device application will have the xml to consume. This application basically just parses the xml data and provides a GUI where the user queries movies or theaters information. The XML for the movie listing is in the following format: <?xml version="1.0" encoding="utf-8" ?> <root> <theaters> <theater id="1" name="Centreville Multiplex" . . . /> <theater id="2" name="Lee Highway Multiplex" . . . /> < . . . > </theaters> <movies> <movie mid="1" name="Narc (R)" summary=" . . . " /> <movie mid="2" name="25th Hour (R)" summary=" . . . " /> < . . . > </movies> <xrefs> <xref theaterid="1"> <movietime refmid="5" time="0:5 Fri - Feb 07: 8:00 12:30 | Sat - Feb 08: 12:30 | . . . " /> <movietime refmid="2" time="0:4 Fri - Feb 07: 7:00 10:30 | Sat - Feb 08: 8:00 11:30 . . . " /> < . . . > </xref> <xref theaterid="2"> <movietime refmid="5" time=" . . . " /> <movietime refmid="4" time=" . . . " /> < . . . > </xref> < . . . > </xrefs> </root> There are basically three collections of data: theaters, movies, and xrefs. As the names imply, the theaters and movies collections hold basic information for the theater and movie entities. The xrefs collection serves as the cross-reference between the theaters and the movies to hold the movie show times. The application starts with the loading of the offline content into the menus and building appropriate data structures to store the theaters, movies, and references between them: XmlDocument doc = new XmlDocument( ); doc.Load("\\Program Files\\XMLMovieListing\\listing.xml"); XmlNodeReader oReader = new XmlNodeReader(doc.DocumentElement); oReader.Read( ); while(oReader.Read( )) { if(oReader.Name == "theaters" && oReader.IsStartElement( )) { ProcessTheaters(oReader); } else if(oReader.Name == "movies" && oReader.IsStartElement( )) { ProcessMovies(oReader); } else if(oReader.Name == "xrefs" && oReader.IsStartElement( )) { ProcessCrossRef(oReader); } } For example, the ProcessTheaters creates the menu with theaters as items, associates the menu click with mnuTheater_Click event handler. This function also creates two hashtables: one to store general theater information keying on the theater id and the other is a hashtable that points to another hashtable that stores movie show times based on movie id for the current theater. Because this application is just an example and the list of local theaters and movies are small, it is ok to store the information in memory. Remember, when you are developing for these mobile devices, memory resource can be scarce and some alternative design should be considered: private void ProcessTheaters(XmlNodeReader oReader) { bool bDone = false; string sTheaterKey = ""; while(!bDone && oReader.Read( )) { if(oReader.Name == "theater" && oReader.IsStartElement( )) { MenuItem mnuItem = new MenuItem( ); string sText = oReader["id"] + " " + oReader["name"]; mnuItem.Text = sText; mnuItem.Click += new EventHandler(this.mnuTheater_Click); this.mnuTheater.MenuItems.Add(mnuItem); sTheaterKey = oReader["id"]; THEATERS_TIME.Add(sTheaterKey, new Hashtable( )); THEATERS.Add(sTheaterKey, new Theater(oReader["name"], oReader["addr1"], oReader["addr2"], oReader["phone"])); } if(oReader.Name == "theaters" && oReader.IsStartElement( ) == false) { bDone = true; } } } At this point, you probably wonder if you can just rely on the XPath query to find movie/theater associations. As it turns out, because you are using the "compact" framework, some of the functionality that you might be familiar with in the desktop/web world might not be implemented here. In this case, XPath is not implemented so you won't be able to do SelectNodes to simplify your life. Similar to the ProcessTheaters function, the ProcessMovies creates the menu with movies as items, associates the menu click with mnuMovie_Click event handler. This function also creates two hashtables: one to store general movie information keying on the movie id and the other is a hashtable that points to another hashtable that stores movie times based on theater id for the current movie: private void ProcessMovies(XmlNodeReader oReader) { bool bDone = false; while(!bDone && oReader.Read( )) { if(oReader.Name == "movie" && oReader.IsStartElement( )) { MenuItem mnuItem = new MenuItem( ); string sText = oReader["mid"] + " " + oReader["name"]; mnuItem.Text = sText; mnuItem.Click += new EventHandler(this.mnuMovie_Click); this.mnuMovie.MenuItems.Add(mnuItem); string sMovieKey = oReader["mid"]; MOVIES_TIME.Add(sMovieKey, new Hashtable( )); MOVIES.Add(sMovieKey, new Movie(oReader["name"], oReader["summary"])); } if(oReader.Name == "movies" && oReader.IsStartElement( ) == false) { bDone = true; } } } The ProcessCrossRef function fills in the two cross-ref structure so that we can list showing times for a particular movie across all theater or showing times for all movies at a particular theater: private void ProcessCrossRef(XmlNodeReader oReader) { bool bDone = false; string sTheaterKey = ""; while(!bDone && oReader.Read( )) { if(oReader.Name == "xref" && oReader.IsStartElement( )) { sTheaterKey = oReader["theaterid"]; } if(oReader.Name == "movietime" && oReader.IsStartElement( )) { Hashtable o = (Hashtable)THEATERS_TIME[sTheaterKey]; o.Add(oReader["refmid"], oReader["time"]); Hashtable o1 = (Hashtable)MOVIES_TIME[oReader["refmid"]]; o1.Add(sTheaterKey, oReader["time"]); } if(oReader.Name == "xrefs" && oReader.IsStartElement( ) == false) { bDone = true; } } } The menu click handler: mnuTheater_Click and mnuMovie_Click perform similar tasks, which basically setup the current mode of selection (by theater or by movie), the current theater or movie based on the mode, the list of movie and show times if the mode is by theater, or the list of theater and show times if the mode is by movie. Other supporting functions help in navigating to show movies and show times by theater or theater and show times by movie.[7]
Figure 9-20 and Figure 9-21 show the application running on a Pocket PC while having the Movies and Theaters menu expanded. Figure 9-20. XML movie listing by movieFigure 9-21. XML movie listing by theaterWhen you select a movie, the application traverses all theaters that show the selected movie and display the show times (see Figure 9-22 and Figure 9-23). Figure 9-22. By movieFigure 9-23. Same movie, other theater, also buying ticketWhen you pick a theater instead of a movie, the application starts to traverse all movies showing at the selected theater (see Figure 9-24 and Figure 9-25). Figure 9-24. By theaterFigure 9-25. Same theater, another movie, also buying ticket9.2.2.2 Storing off-line data in SQL Server for Windows CEIn the next example, you will rely on SQL Server CE for the data instead of an XML file. This change simplifies the code you write but requires the installation of SQL Server CE on the mobile device, thus the total amount of memory used might be higher than the xml version of the application. We can ignore the resource problem and exchange it with the simplification of the code and, fortunately, VS.NET automatically deploys SQL Server CE on the device the same way it does the .NET Compact Framework. This version of the application starts by opening a database connection and generating the Theater and Movie menus. Unlike the previous version, you don't have to build your own data structure to hold the data and cross references. The tables and their references are managed by the database in this example: m_oConn = new SqlCeConnection( "Data Source=\\Program Files\\SQLCEMovieListing\\moviedb.sdf" ); m_oConn.Open( ); GenerateMenus("select theaterid, name from theater", new EventHandler(this.mnuTheater_Click), this.mnuTheater); GenerateMenus("select movieid, name from movie", new EventHandler(this.mnuMovie_Click), this.mnuMovie); The GenerateMenus function just creates the menu items and associates the event handler to each item: private void GenerateMenus(string sCommand, EventHandler handler, MenuItem mnuBranch) { SqlCeCommand oCmd = new SqlCeCommand( ); SqlCeDataReader oReader = null; oCmd.Connection = m_oConn; oCmd.CommandText = sCommand; oReader = oCmd.ExecuteReader( ); while(oReader.Read( )) { MenuItem mnuItem = new MenuItem( ); mnuItem.Text = oReader["name"].ToString( ); mnuItem.Click += handler; mnuBranch.MenuItems.Add(mnuItem); } oReader.Close( ); } Here are the handlers for Theater and Movie: private void mnuMovie_Click(object o, System.EventArgs e) { // Get information from the menu item. string sName = ProcessMenuClick((MenuItem)o); // Obtain the current movie information. SqlCeCommand oCmd = new SqlCeCommand( "select summary from movie where name = '" + sName + "'" ); oCmd.Connection = m_oConn; SqlCeDataReader oReader = oCmd.ExecuteReader( ); oReader.Read( ); m_oCurrentMovie = new Movie(sName, oReader["summary"].ToString( )); // Obtaining all theaters and their show times for this movie SqlCeDataAdapter oAdapter = new SqlCeDataAdapter ( @" select t.name, t.addr1, t.addr2, t.phone, st.showtime from theater t inner join showtime st on t.theaterid = st.theaterid inner join movie m on m.movieid = st.movieid where m.name = '" + sName + "'", m_oConn ); m_oDS.Clear( ); oAdapter.Fill(m_oDS); // Start in-memory cursor. m_iMode = 0; m_iCurrentIndex = 0; displayCurrent( ); } private void mnuTheater_Click(object o, System.EventArgs e) { // Get information from the menu item. string sName = ProcessMenuClick((MenuItem)o); // Obtain the current theater information. SqlCeCommand oCmd = new SqlCeCommand( "select addr1, addr2, phone from theater where name = '" + sName + "'" ); oCmd.Connection = m_oConn; SqlCeDataReader oReader = oCmd.ExecuteReader( ); oReader.Read( ); m_oCurrentTheater = new Theater(sName, oReader["addr1"].ToString( ), oReader["addr2"].ToString( ), oReader["phone"].ToString( )); // Obtain all movie and show times at this theater. SqlCeDataAdapter oAdapter = new SqlCeDataAdapter ( @" select m.name, m.summary, st.showtime from movie m inner join showtime st on m.movieid = st.movieid inner join theater t on t.theaterid = st.theaterid where t.name = '" + sName + "'", m_oConn ); m_oDS.Clear( ); oAdapter.Fill(m_oDS); // Start in-memory cursor. m_iMode = 1; m_iCurrentIndex = 0; displayCurrent( ); } The rest of the code can be found at http://www.oreilly.com/catalog/dotnetfrmess3/. |
[ Team LiB ] |