CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Peter's Gekko

public Blog MyNotepad : Imho { }

Mobile

  • Vista RTM released. Back to Longhorn or on to mobile ?

    Hereby I join the happy crowd running Vista RTM. So far nearly everything is running. Including Virtual PC 2007, I blogged about before. Here it is running the first public Longhorn alpha bits, as handed out on the PDC 2003.

    Installing it was a snap. After installing Virtual PC additions straight from the Virtual PC menu, it’s running, fine. No it’s not useful, just for fun. Want to start all over again ?

    The only thing which does not work right under Vista is mobile development with VS 2005. The emulator runs fine, but cannot reach the network. Network connections were based on cradling the emulator. Vista does not use ActiveSync , so Akhune’s post does not help. Reading this tells me I just have to wait. Yes it is painful.

  • A small "Designed for Windows mobile" rant

    As you perhaps know I have a weak spot for mobile development. For a project I recently dug through the "Designed for Windows Mobile" logo specifications. This logo is meant as a certification that the app will run on any mobile device. One of the logo requirements is that the app will adapt to the screen real estate available. The first generation of devices were all 240 by 320; these days smaller square screen as well as a full size VGA ones are abundant. To make sure your app will handle these smaller or bigger sizes is, using the compact framework, no big deal.

    Being interested in mobile I also read the MS Mobile newsletter. Last issue offered some quite appealing downloads. Space-invaders for my iPaq as well as a lite version of 1-calc. The most interesting part of the latter are endless conversions; living in the metric Europe it would be a great help to understand foreign metrics. Now here is the rant: Both applications don't carry the logo and would never get one. They are hard coded to a 240*320 screen size and don't display right on my 240*240 mobile messenger. I can see the aliens coming but I can't fire back. The calc application uses hard coded skins. The trial version of the full product includes (and installs) a square skin; the free version does not. Even when the skin fits the soft keyboard gets in the way and some of the dialogs are completely clipped.

    It would have been so nice if MS took their own logo serious and either use it to convince software publishers to comply or use it to select something less frustrating. The apps look great but they just don't work for me.

  • Web services on the world wild web

    No more lamenting woes, back to real code. This was the core of my SDN presentation.

    The web is an omnipresent infrastructure which your software can use to communicate. The way to communicate are XML webservices over HTTP. Any attempt to do it different often ends in a firewall as soon as it is deployed into the real world (wild web). In Visual Studio it's easy to set up a project which will serve a web service on your localhost and a consumer which interoperates with it. Such a service will communicate on the real web but some differences with your development setup will turn up.

    • Bandwidth. On a local machine, a local network or even a broadband flat-rate dsl it will be no a problem. But the internet also connects cell phones which have a far narrower path. Besides that on those branches of the web you often have to pay for every byte sent or received.
    • Latency. On your own machine or on a local network a response will be instantaneous. But when you're requesting something over the web it's not unusual to take a couple of seconds.

    The main point is that the web is something you cannot control. A good web service is designed as something which interchanges messages over a not to reliable channel and not as just another way to call your object's methods. People like Christian Weyer often give very passionate presentations on this,  here's a photo impression on one. I'm not going to use his contract first tool here, nor WCF but just plain web services as offered by .NET right out of the box to show how you can do something about it there as well.

    I assume you know the main things about setting up a web service project so I can jump right in. My demo web service publishes information from the Northwind database. It provides some bundled summary data of a customer based on an ID. The .NET way to bundle data is in a dataset. After designing the dataset a first shot at the web service could look like this.

    [WebMethod]

    public CustomerSummary GetCustomerSummary(string id)

    {

        CustomerSummary ds = new CustomerSummary();

        CustomersTableAdapter ta = new CustomersTableAdapter();

        ta.FillBy(ds.Customers, id);

        return ds;

    }

    This will result in well typed customerinfo. A typical result when invoking it

     

    <?xml version="1.0" encoding="utf-8" ?>

    - <CustomerSummary xmlns="http://Gekko-Software.nl/SDN/SDE">

      - <xs:schema id="CustomerSummary" targetNamespace="http://MyCompany.com/CustomersSummary.xsd" xmlns:mstns="http://MyCompany.com/CustomersSummary.xsd" xmlns="http://MyCompany.com/CustomersSummary.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" attributeFormDefault="qualified" elementFormDefault="qualified">

        - <xs:element name="CustomerSummary" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">

          - <xs:complexType>

            - <xs:choice minOccurs="0" maxOccurs="unbounded">

              - <xs:element name="Customers">

                - <xs:complexType>

                  - <xs:sequence>

                    - <xs:element name="CustomerID">

                      - <xs:simpleType>

                        - <xs:restriction base="xs:string">

                          <xs:maxLength value="5" />

                        </xs:restriction>

                      </xs:simpleType>

                    </xs:element>

                    - <xs:element name="CompanyName">

                      - <xs:simpleType>

                        - <xs:restriction base="xs:string">

                          <xs:maxLength value="40" />

                        </xs:restriction>

                      </xs:simpleType>

                    </xs:element>

                    - <xs:element name="ContactName" minOccurs="0">

                      - <xs:simpleType>

                        - <xs:restriction base="xs:string">

                          <xs:maxLength value="30" />

                        </xs:restriction>

                      </xs:simpleType>

                    </xs:element>

                    - <xs:element name="Phone" minOccurs="0">

                      - <xs:simpleType>

                        - <xs:restriction base="xs:string">

                          <xs:maxLength value="24" />

                        </xs:restriction>

                      </xs:simpleType>

                    </xs:element>

                  </xs:sequence>

                </xs:complexType>

              </xs:element>

            </xs:choice>

          </xs:complexType>

          - <xs:unique name="Constraint1" msdata:PrimaryKey="true">

            <xs:selector xpath=".//mstns:Customers" />

            <xs:field xpath="mstns:CustomerID" />

          </xs:unique>

        </xs:element>

      </xs:schema>

      - <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">

        - <CustomerSummary xmlns="http://MyCompany.com/CustomersSummary.xsd">

          - <Customers diffgr:id="Customers1" msdata:rowOrder="0">

            <CustomerID>ALFKI</CustomerID>

            <CompanyName>Alfreds Futterkiste</CompanyName>

            <ContactName>Maria Anders</ContactName>

            <Phone>030-0074321</Phone>

          </Customers>

        </CustomerSummary>

      </diffgr:diffgram>

    </CustomerSummary>

    Which are quite a lot of bytes traveling over the wire. The main part of the result is the dataset's schema. The good thing is that the consumer does not need this schema on every invocation of the service. It can use the schema to build a type but that information is also available in the wsdl (Web Service Description Language). In .NET 2 you can exclude the schema form the result.

    [WebMethod]

    public CustomerSummary GetCustomerSummary(string id)

    {

        CustomerSummary ds = new CustomerSummary();

        CustomersTableAdapter ta = new CustomersTableAdapter();

        ta.FillBy(ds.Customers, id);

        ds.SchemaSerializationMode = SchemaSerializationMode.ExcludeSchema;

        return ds;

    }

    Now the result is far smaller.

     

    <?xml version="1.0" encoding="utf-8" ?>

    - <CustomerSummary msdata:SchemaSerializationMode="ExcludeSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns="http://Gekko-Software.nl/SDN/SDE">

      - <xs:schema id="CustomerSummary" targetNamespace="http://MyCompany.com/CustomersSummary.xsd" xmlns:mstns="http://MyCompany.com/CustomersSummary.xsd" xmlns="http://MyCompany.com/CustomersSummary.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" attributeFormDefault="qualified" elementFormDefault="qualified">

        - <xs:element name="CustomerSummary" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">

          - <xs:complexType>

            <xs:choice minOccurs="0" maxOccurs="unbounded" />

          </xs:complexType>

        </xs:element>

      </xs:schema>

      - <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">

        - <CustomerSummary xmlns="http://MyCompany.com/CustomersSummary.xsd">

          - <Customers diffgr:id="Customers1" msdata:rowOrder="0">

            <CustomerID>ALFKI</CustomerID>

            <CompanyName>Alfreds Futterkiste</CompanyName>

            <ContactName>Maria Anders</ContactName>

            <Phone>030-0074321</Phone>

          </Customers>

        </CustomerSummary>

      </diffgr:diffgram>

    </CustomerSummary>

    But it is still a very verbose way to return the small summary.

    The ultimate mobile consumer is a smartphone. You can build smartphone applications with .net but these applications are based on the 1.1. version of the compact framework. CF 2.0 can consume a web service with typed datasets, but version 1 can not. It does understand untyped datasets. Changing the service contract to an untyped dataset would work.

    [WebMethod]

    public DataSet GetCustomerSummary(string id)

    {

        CustomerSummary ds = new CustomerSummary();

        CustomersTableAdapter ta = new CustomersTableAdapter();

        ta.FillBy(ds.Customers, id);

        ds.SchemaSerializationMode = SchemaSerializationMode.ExcludeSchema;

        return ds;

    }

    The consumer does not have the many benefits of a strongly typed result but the amount of data going over the wire is still pretty large.

    Thank goodness there is a more compact way to serialize a dataset. The GetXML() method returns a minimalist string representation of it's contents.

    [WebMethod]

    public string GetCustomerSummary(string id)

    {

        CustomerSummary ds = new CustomerSummary();

        CustomersTableAdapter ta = new CustomersTableAdapter();

        ta.FillBy(ds.Customers, id);

        return ds.GetXml();

    }

    This will lead to a far smaller result message.

    <?xml version="1.0" encoding="utf-8" ?>

    <string xmlns="http://Gekko-Software.nl/SDN/SDE">

      <CustomerSummary xmlns="http://MyCompany.com/CustomersSummary.xsd">

        <Customers>

          <CustomerID>ALFKI</CustomerID>

          <CompanyName>Alfreds Futterkiste</CompanyName>

          <ContactName>Maria Anders</ContactName>

          <Phone>030-0074321</Phone>

        </Customers>

      </CustomerSummary>

    </string>

    The CF 1 consumer of the web service can deserialize this result into an untyped dataset.

    private void GetCustomerSummary()

    {

        CustomerService ws = new CustomerService();

        ws.Url = "http://192.168.1.90/SDE/SDE/CustomerService/CustomerService.asmx";

     

        string rxm = ws.GetCustomerSummary(textBox1.Text);

        StringReader sr = new StringReader(rxm);

        XmlTextReader xr = new XmlTextReader(sr);

        DataSet ds = new DataSet();

        ds.ReadXml(xr);

     

        label1.Text = ds.Tables["Customers"].Rows[0]["ContactName"].ToString();

        label2.Text = ds.Tables["Customers"].Rows[0]["CompanyName"].ToString();

    }

    After creating the proxy you have to set the URL to the service. A CF device nor the emulator understand localhost. Invoking the service returns a string; it takes a StringReader to read that and an XmlTextReader to deserialize that into a DataSet.

    We have now found a way to reduce the precious bandwidth, but not yet done anything about the latency. The moment the smartphone starts invoking the webservice it's UI freezes until the web service returns. Not very nice to the user. The good thing is that the web service can be invoked asynchronously on a background thread. The bad thing is that there are some strings attached to that, especially in version 1 of the framework.

    The proxy has a method to invoke the service on a background thread. This method is passed a callback function, when the result of the service returns this function will be called.

    private void GetCustomerSummary()

    {

        CustomerService ws = new CustomerService();

        ws.Url = "http://192.168.1.90/SDE/SDE/CustomerService/CustomerService.asmx";

        ws.BeginGetCustomerSummary(textBox1.Text, new AsyncCallback(wsCallBack), ws);

    }

     

     

    private void wscb(IAsyncResult asResult)

    {

        // Implementation here

    }

    The BeginGetCustomerService method takes (in this case) three parameters. First comes the parameter to the web method. Second a delegate for the callback and third the proxy itself. The AyncCallback delegate needs a method with a signature like the wscb method. When a response from the web service is received this method will be called. The main mistake made in its implementation, also by me in the past, is that the method is running on the background thread, which is not the same one as the UI is running on. If you want to set UI properties, like a label caption, there's a chanchee you'll get threading exceptions. The safe way to reach the UI thread is by using the Invoke method. In the full framework there is the InvokeRequired property to query whether you need need Invoke or can reach the properties directly. In CF 1 this property is not available but you can bet you have to use invoke. Another hurdle is that the Invoke method in CF 1 cannot take any parameters, you can only invoke a method with the EventHandler signature (object sender, EventArgs e). The parameter values will be null. To pass some values a shared member is needed.

    This leads to this implementation.

    DataSet ds = new DataSet();

     

    private void wscb(IAsyncResult asResult)

    {

        customerhost.CustomerService ws = asResult.AsyncState as customerhost.CustomerService;

     

        string rawXml = ws.EndGetCustomerSummary(asResult);

        System.IO.StringReader sr = new StringReader(rawXml);

        XmlTextReader xr = new XmlTextReader(sr);

        lock (ds)

        {

            ds.Clear();

            ds.ReadXml(xr);

        }

     

        this.Invoke(new EventHandler(toonResultaat));

    }

     

    private void toonResultaat(object sender, EventArgs e)

    {

        label1.Text = ds.Tables["Customers"].Rows[0]["ContactName"].ToString();

        label2.Text = ds.Tables["Customers"].Rows[0]["CompanyName"].ToString();

    }

    The dataset is now a private member. The callback function receives an IAsyncResult object and extracts the proxy object out of the AsyncState member. On this the EndGetCustomerSummary method is called to get the result of the service. The dataset has to be locked when writing to. A dataset is not threadsafe for writing and we are now on a different thread as the UI. To get to the UI thread the form's Invoke method is invoked. Which takes a delegate to the method which will update the UI.

    So far I have presented a worst case scenario, a slow and narrow web consumed by a "primitive" CF 1 consumer. A 2.0 consumer has easier ways to do these things. But a non .net consumer can make life harder on you. The nice thing about the string representation of the XML is that you can use it as a base for XML DOM programming. The web service wrapper might get completely lost in (typed) datasets but with the tXmlDocument API (the name in Delphi) you can get anything in and out. For a deep-going example here's a story how Delphi 6 can assemble and dissemble diffgrams.

    This is by far not the end. For instance I have assumed that a web service will always return something. The web is unreliable enough for that not to happen. Before jumping into that or any other "what if" scenario I suggest you take a look at WCF first as that has so much more to offer right out of the box. Web service in .NET <= 2 are just like the web itself. It usually works but don't count on it.

     

  • Switching from the VS development server to IIS

    By default a web project in VS is hosted by Cassini, the included web-server. Nevertheless I prefer IIS for my projects. For several reasons:

    • Security. A site running in IIS is running under the asp.net account which is quite a stricter security context than the one Cassini is running in. This can lead to some unpleasant surprises when the app is taken to production. The issues would have popped up earlier when the app had been developed using IIS.
    • Clearness of URL's. Especially when developing web services their consumers need clear url's . Using Cassini every service uses another port, by default this is random. In such a scenario it will be a puzzle to find out the url's of the services in a new round, or a round on another development machine. You can assign fixed ports to an app, but I don't think that's very clear.
    • Security. When consuming a web service with a mobile app, or even from the emulator, you are crossing a network border. By default the firewall of your machine blocks the incoming request. Opening port 80 for "normal" web requests is doable, opening all the different ports Cassini is using will end in a nightmare.

    Switching from Cassini to IIS is no big deal, there is a web property page in a web project. Switching smooth required a little more attention.

    • Check "USE IIS Web server"
    • Click "Create Virtual Directory"
    • Click save. Now the somewhat enigmatic dialog shown in the screenshot pops up
    • In the solution explorer refresh the web project

    After the last two steps the new (service-consumer) projects will have the right url. In case you skip them they will use the old Cassini-type url.

    This is no big deal but I would like to have IIS as a default. Without a doubt there is a setting for that but I just can't find it. Anyone?

  • Add a custom icon to your (mobile) app

    The devil is in the details. I found this very, very true when doing something as simple as adding a custom icon to a mobile app. This post describes to add a custom icon to a mobile app but the majority of the points are just as well applicable to a full Windows app.

    The Designed for Windows Mobile logo requirements state that an app includes at least two icons: a 16*16 and a 32*32. The larger one is used to display the (required) shortcut in the programs folder, the small one is used in the running programs list, the first item in the PDA's windows menu. (This list does not show in an emulator) . To look well these icons need a transparent background, especially the taskbar looks bad when the background is solid.

    You can create your icons in VS. The icon editor has a color palette, in the top left hand is a small monitor image. All pixels drawn in this color will be rendered transparent.

    Here the icon has the dark-bluish background, in a folder it will be white and in the task bar it will be a blue or red gradient, depending on the user's theme.

    There are several ways to include the icon in the app. The most costly one is to include it as an embedded resource in every form of the application. The icon is a property of a form, you can set it from the property window and edit it in the resx file (which shows up in the solution explorer after selecting "show all files"). Including it in every form makes sense when every form should display it's own icon, but is a waste of (costly mobile) resources in every other scenario. This was another optimization we did in our "from WTF to logo" app. The easiest way to assign the icon is to set it in the application tab of the app's property pages. When building the app the icon will be embedded. There is no need to include the ico file itself when deploying the app.

    There is one final gotcha which did cost me a lot of hair. When installing the updated app on the device, even after a full un-install of the previous version, the application will still display the old icon. It is preserved somewhere in a cache, to see the updated icon you have to reboot the device. I wish I had known that in advance.

    Posted Aug 11 2006, 06:27 AM by pvanooijen with 1 comment(s)
    Filed under:
  • Deploying Mobile apps the easy way using Inno Setup

    Deploying a mobile application is not as simple as just clicking next, next, finish in a msi wizard. As a result a lot of articles have been written on it, searching the web you will find many. One of the most detailed I found was this one on DevExpress. It works well but requires the .net framework 2.0 to be present on the PC from which to install the mobile app. In my case this was a no-go, I'm in a situation where I cannot count on any version of the framework being present and even the usage of msi files is discouraged. Nevertheless it should be possible to install a mobile CF 2 app according to the Mobile logo requirements, that is using ActiveSync with at the utmost two or three OK clicks from the user. Which was in the end, after a lot of analyzing and searching not only possible but even simpler and clearer than the msi way.

    Deploying a mobile app boils down to these steps

    • Build a CAB file containing the app and it's resources. This is a standard project in VS 2005
    • Write an ini file describing the setup
    • Package cab and ini in a redistributable
    • Unpack the files on the client PC to which the device is connected
    • Fire up the CEappmanager, this is the add/remove programs part of ActiveSync
    • Feed the CE app manger the ini file in the command line

    To do this in an msi requires adding an afterinstall action to the setup. All this action does is fire up the CEappmanager with the ini file in the command line. Adding a custom action to an msi either requires .net 2.0 which supports the Microsoft installer or doing some low-level editing in a finished msi and adding a custom dll to do the action. The first one is easy following the article (but requires the .net 2.0), the second one a tedious nightmare.

    In comes inno setup. Inno setup is freeware and follows a completely different approach to creating a setup. It does so by writing a script. In the script is a list of files to pack, some events to hook into and full custom scripting capability. The nice thing about these scripts is they are in Delphi syntax. Inno setup itself is also written in Delphi, source code is available. Noteworthy is that Delphi versions 2 to 5 are mentioned. I'm not the only one who thinks the real Delphi stopped with that version.

    Inno setup produces one executable which contains the compressed files to redistribute and runs the script. Step by step I'll walk you through a script to deploy a CF application, including the Compact framework itself. Using visual studio I've created a cab file which includes the mobile application and will setup some shortcuts on the device.

    The first [Setup] part of the script describes the setup with the usual info. The app I'm describing here is a PDA extension to TimeWriter, which is a killer app to keep track of your (billable) time. There is a freeware version available, do give it a try.

    [Setup]
    AppName=TimeWriter PDA
    AppVerName=TimeWriter 1.5
    AppPublisher=XSO
    AppPublisherURL=http://www.timewriter.com
    AppSupportURL=http://www.timewriter.com
    AppUpdatesURL=http://www.timewriter.com
    CreateAppDir=yes
    DefaultDirName={pf}\twPDA
    OutputBaseFilename=twPDAsetup
    Compression=lzma
    SolidCompression=yes

    The next section contains the list of files to pack.

    [Files]
    Source: "netcf.ini"; DestDir: "{app}";
    Source: "wce400\armv4\NETCFv2.ppc.armv4.cab"; DestDir: "{app}\wce400\armv4";
    Source: "wce500\armv4i\NETCFv2.wm.armv4i.cab"; DestDir: "{app}\wce500\armv4i";
    Source: "wce500\armv4i\NETCFv2.wce5.armv4i.cab"; DestDir: "{app}\wce500\armv4i";

    Source: "twPDA.ini"; DestDir: "{app}";
    Source: "TimeWriterPDA.cab"; DestDir: "{app}";
     

    The first set is the Compact Framework 2. The three cab files are all targeting the arm processor. Although the compact framework does target other processors, for pocket pc 2003 and later an arm is required. So I can skip the other cab's. The three different cab's target the different versions of pocket pc and windows mobile running on Windows CE 4 and 5. The netcf.ini  describes them. The second set of files is the TimeWriter app with it's ini file. Inno setup will place all these files in the the folder on the client PC.

    Now everything is ready for the CEappmanager to do the real install. The next section in the script fires any application you need after unpacking the files. In my case I want to fire the CEappmanager twice, first to install the Compact Framework, next to install my app.

    [Run]
    FileName: {code:GetCEappManager}; Parameters: {code:GetIniFile|\netcf.ini}
    FileName: {code:GetCEappManager}; Parameters: {code:GetIniFile|\twpda.ini}

    The CEappmanager requires one parameter with the full path of the ini file to process. I don't know where the CEappmager is located and I don't know either where the user has installed the files. In comes the [Code] section, in here you can write full Delphi code to do the work. This code can use functions the inno library but also almost any other Delphi, Win32 or COM (!) function.

    [Code]
    function GetCEappManager(Param : string) : string;
    var Path: String;
    begin
      Path:= '';
      RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\CEAPPMGR.EXE','', Path)
      result:= Path
    end;

    function GetIniFile(Param : string) : string;
    begin
      result:= ExpandConstant('"{app}') + Param + '"';
    end;

    The GetCEappManager reads the location from the registry. The getIni file expands the constant {app} to get the directory used for installation and appends the name of the ini file passed in the parameter. Note the quotes, without them a parameter like C:\program files\TwPda\twpda.ini would be interpreted as two command line parameters.

    CEappmanager processes the ini file, which looks like this.

    [CEAppManager]
    Version = 1.0
    Component = NETCF


    [NETCF]
    Description = .NET Compact Framework v2.0
    CabFiles=wce400\armv4\NETCFv2.ppc.armv4.cab,wce500\armv4i\NETCFv2.wm.armv4i.cab,wce500\armv4i\NETCFv2.wce5.armv4i.cab

    It contains a list of cab files. These should located relative to the directory of the ini file. The list in the ini file locates them in the subfolders and should not contain any spaces. The good thing about the CEappmanager is that it knows which cabinet it needs for the specific device. It will only install the one matching the target. To keep a tab on what the appmanager is doing you can add to the registry entry HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\AppMgr a DWORD named ReportErrors with the value 1. Some sources state that passing /t in the commandline has the same effect. I cannot confirm that. Now CEappmanager will pop up dialogs telling which files it has found and also info like this:

    It has found three cab files for CF 2.0. The 0 in front of the third indicates it will install that one. That's the one for my device.

    <update>

    Instead of firing the CEappmanager twice you can pass both ini files in one go. This will change the [Run] section to

    [Run]
    FileName: {code:GetCEappManager}; Parameters: {code:GetIniFile|\netcf.ini} {code:GetIniFile|\twpda.ini}

    This undocumented feature probably explains why passing /T as a debugging parameter does not work. /t is seen as a file.

    </update>

    Having run CEappmanager my app and the needed CF 2 is up and running.

    This whole setup script depends totally on the CEappmanager. In case ActiveSync is not installed the appmanger will not be there either and the whole setup is useless as there is no connection to the device. To cancel the script in such a scenario there's the InitializeSetup event. Also part of the [Code] section. It's a boolean function telling to proceed or cancel.

    [Code]
    function InitializeSetup() : boolean;
       var Path: String;
    begin
    Path:= '';
    RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\CEAPPMGR.EXE','', Path)
    if Path = '' then
       begin
       MsgBox('Activesync not found !', mbError, MB_OK);
       result:= false;
       end
    else
       result:= true;
    end;

    You cannot use the other script functions in the section, so the code to dive into the registry has to be duplicated. But the way to fire up a dialog is cool.

    There are so many more things you can do with Inno Setup. The tool is very intuitive to use and includes even integrated debugging.

    Delphi style, with the exchanged F5 and F9. But instead of wasting more words on it I can only suggest to try it yourself. To get my mobile setup to work took far less and very clear code compared to the msi way. From now on it is going to be part of my toolbox.

    Posted Aug 03 2006, 09:13 AM by pvanooijen with 5 comment(s)
    Filed under:
  • Mobile devices with a VGA display

    In my previous post I talked a little on make your apps work on a 240*240 display size. On the other side of the spectrum are huge PDA's with a VGA (640*480) display. CF 2 applications adapt to these screens automatically using two properties of the form. But a converted CF 1 app looks awful on such a device. To repair this you have to set these properties by hand. They are set in the InitializeComponent  method of the form

    this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);

    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;

    The autoscalemode in a converted app has the value of AutoScaleMode.Inherited. After setting it to AutoScaleMode.Dpi you form looks great on a VGA screen. The AutoScaleDimensions give the form a reference size. In a new form this property is introduced by the designer.

    On such a large screen the size of the SIP input panel is different as well. All reference documentation, including the logo requirements, mentions a height of 80 pixels for the SIP. Instead of hard coding that value as I did in the first post you can read the actual height and use that value..

    private void inputPanel1_EnabledChanged((object sender, EventArgs

    {

       if (inputPanel1.Enabled)

          panel1.Height -= inputPanel1.Bounds.Height;

       else

          panel1.Height += inputPanel1.Bounds.Height;

    }

    Now your app will work well on a small and on a big screen, including SIP support.

    Posted Aug 01 2006, 10:47 AM by pvanooijen with no comments
    Filed under:
  • Screen real estate on mobile devices and the SIP

    Over the last months I've been doing more and more mobile apps. The CF framework 2 really rocks and mobile apps which communicate with PC based app are a welcome extension of functionality. This post marks the start of a new post category : Mobile. I'll add my older post on the subject.

    Mobile devices have small screens. 320*240 pixels used to be default but with smartphones and the iPaq mobile messenger 240*240 is becoming a new standard. Another thing eating up screen real estate is the Soft Input Panel, the screen based keyboard. The problem with that is that it steals away the bottom 80 pixels of the screen. Guidelines for mobile apps advise not to use the bottom 80 liner for text based input controls. That would be quite a waste.

    Container controls like a form or a panel have the AutoScroll property. Setting this to true will automatically show up a scrollbar in case the contained controls don't fit. So when you develop a mobile app based on 320*240 forms it will show with a scrollbar on a 240*240 screen. But this autoscroll property does not work with the SIP. Popping up that still covers the bottom of the form. Searching around for a way to solve this you will find information on a resize of the form when the SIP pops up. This does not work either, the resize event of the form does not fire when the SIP pops up. What does work is this:

    • Cover your form with a panel.
    • Set the panel's anchors to top, left, bottom, right. So the panel will always cover the full form, whatever size.
    • Set the panel's autoscroll property to true.
    • Place all the controls on this panel.
    • Add an inputpanel to the form.

    An example form will look like this in VS:

    Add the following code to the input panel's enablechanged event

    private void inputPanel1_EnabledChanged(object sender, EventArgs e)

    {

       const int SIPsize = 80;

       if (inputPanel1.Enabled)

           panel1.Height -= SIPsize;

       else

           panel1.Height += SIPsize;

    }

    The standard size of the SIP is 80 pixels. When the SIP pops up the panel shrinks by 80 pixels. Autoscroll will keep all controls accessible. When the SIP goes away the panelsize is restored. This is the app on a mobile messenger.

    It would be tempting to resize the form itself instead of the panel. This does not work, you cannot change a mobile form from code.

    Posted Aug 01 2006, 07:39 AM by pvanooijen with 2 comment(s)
    Filed under:
  • Connecting to the local network from the Smart Device emulator

    Mobile apps make great consumers for a webservice. But debugging such a scenario can be hard to set up. I lost some hair in getting it to work right; but in the end it was simple. There are loads and loads of good manuals how to setup your device, this one on Akhune's blog tells all, up to connecting to the internet from the emulator. All except how to connect to the web server of the PC your emulator is running on. Which didn't work for me and that's what I really needed to debug my app.

    In a typical scenario you have one solution with a webservice and the "smart device app"; where the latter has a web reference to the service. The first problem is the URI the smart app needs to contact the service. The emulator has an emulated network card and so becomes part of your network. It will not be able to find localhost, what works well is using the IP address of the PC hosting the emulator. The emulator and the PC are two nodes in the network. To the PC the emulator is an external source trying to access your local web server. By default the Windows firewall prohibits this. I had bumped into this before, this time I had to add the web server (HTTP) protocol to the settings of the Local Area Connection.

    Now the emulator runs as intended. Another great setting of the emulator is zoom under the display options.

    It will result in a giant PDA, very readable, very easy to navigate.

    Posted Jul 11 2006, 04:05 AM by pvanooijen with 6 comment(s)
    Filed under:
  • Nintendo meditation (my first PDA)

    This holiday a lot of kids will get their first information processing device. After that their life will never be the same again.

    My son Cas, wishing you a merry Christmas.

    (Having portrayed my eldest Niek selling MSDN leftovers and my youngest Mees playing with a tablet PC jr, Cas deserved his place as well.)

    Peter

More Posts

This Blog

Syndication

News