Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / WinMobile

A Generic Mobile Data Collection Control

5.00/5 (3 votes)
30 Nov 2011CPOL6 min read 23.6K   458  
Generate Front end screens and database on a Windows Mobile 5 for data collection
Text_File_and_Output.jpg

Introduction

Most systems require a handheld device for data collection. These devices are used in the field, the data is collected and synced back to a server. The data can either be synced via a link cable, internet or wifi, etc. Many projects require different data collection methods and creating a generic screen to collect these data is a time consuming task for the developer.

My objective here is to create a generic Windows form user control, which can be placed in a Windows mobile 5-6 project and compiled. The compiled version is then used inside every device, their interfaces linked to a textfile.

The developer only changes the text file and the database and interface is generated for use in the field. The syncing process is done in a separate application which is out of the scope of this article.

Background

I am using Windows Mobile 6 SDK (WM6). and Windows Mobile 6.5 DTK (install these after VS2008).
Visual Studio 2008 Standard edition and additional updates which are required by WM6
SQL Compact edition.

NOTE: VS2010 does not support Windows mobile 6 anymore.

I will start by creating a mobile project in Visual Studio.

creating_the_Project.jpg

I then create the user control within the project:
our_generic_user_control.jpg

I then create another project which will be main main mobile project (2 projects in a solution).

Right click on the project -> add reference -> select the control project.

And you have a reference to the control. Create a Windows form and you can use the user control in your compiled project.

I'm creating this code in the fastest way possible using a text file located in the mobile device at
/My Documents/Templates/test.txt.
You may use XML and parse the XML which is a better choice than what I am using.

Another assumption on the interface I am making is that my controls are separated into sections of Tabs. Each tab collects a specific set of data and a single save button saves all these to a single table. Only during the syncing process, is where I extract data as required. Therefore if a tab does not need to be filled by data, the textbox of that data can be left blank.

This is how I parse my text file. I split the whole text into sections separated with an "enter" or "\n" and a dash "-". I then check every word and try to a find a match for the following:

  1. If there is a less than or equal sign "<", this word is the name of the tab.
  2. If there is a curly brace "{", this word is the name of the field in the table.
  3. Any other word is a label.

Using the Code

I will be placing all the code inside "Controlname.Designer.cs" file. You may change the name of the control as you see fit. Right click on the file and "View Code".

You will see the following code which is generated by the designer:

C#
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
    if (disposing && (components != null))
    {
        components.Dispose();
    }
    base.Dispose(disposing);
} 

You will then see a "region" of code called "component designer generated code". This is where most of controls will be generated inside the following method:

C#
private void InitializeComponent() { ...}

First thing I need before initializing the controls is to have methods that create the controls and return them to me. This way, I can call a method and generate several controls using different control IDs. Based on my assumption on the control grouping, I will need the following controls:

  1. Textbox
  2. Label
  3. Button
  4. Tab Page
  5. Tab Control (holds the Tab Pages)

And of course, a variable of type Arraylist so that I know how many controls I created.

C#
protected ArrayList myControls = new ArrayList();

I then start creating the following methods (add your own additional methods based on a criteria):

C#
private TabControl myTab(string ID)
{
    TabControl tabc = new TabControl();
    tabc.Name = "Tabc" + ID;
    tabc.Size = new System.Drawing.Size(this.Width, this.Height);
    tabc.SelectedIndex = 0;
    return tabc;
}
private TabPage myTabPage(string ID, string txt, TabControl tabc)
{
    TabPage tabp = new TabPage();
    tabp.Name = "Tabp" + ID;
    tabp.Text = txt;
    tabc.Controls.Add(tabp);
    return tabp;
}
private TextBox myTextbox(string ID,TabPage tabp, int myX, int myY)
{
    TextBox txt = new TextBox();
    txt.Name = "txt" + ID;
    txt.Size = new System.Drawing.Size(100, 21);
    txt.Location = new System.Drawing.Point(myX, myY);
    tabp.Controls.Add(txt);
    myControls.Add(txt);
    return txt;
}
private Label myLabel(string ID, string txt, int myX, int myY)
{
    Label lbl = new Label();
    lbl.Name = "mLbl" + ID;
    lbl.Size = new System.Drawing.Size(200, 21);
    lbl.Text = txt;
    lbl.Location = new System.Drawing.Point(myX, myY);
    return lbl;
}
private Label myLabel(string ID, string txt, TabPage tabp, int myX, int myY)
{
    Label lbl = new Label();
    lbl.Name = "Lbl" + ID;
    lbl.Size = new System.Drawing.Size(100, 21);
    lbl.Text = txt;
    tabp.Controls.Add(lbl);
    lbl.Location = new System.Drawing.Point(myX, myY);
    return lbl;
}
private Button myButton(string ID, string txt, int myX, int myY)
{
    Button btn = new Button();
    btn.Name = "Btn" + ID;
    btn.Text = txt;
    btn.Location = new System.Drawing.Point(myX, myY);
    btn.Click += new System.EventHandler(this.btnSave_Click);
    return btn;
}

After creating the controls, I need a few generic methods that check for a connection or open my text file and/or etc.

C#
private string readFile(string filename)
{
    string data = string.Empty;
    try
    {
        System.IO.StreamReader sr = 
    new System.IO.StreamReader(@"\My Documents\Templates\"+filename);
        data = sr.ReadToEnd();
        sr.Close();
    }
    catch (Exception)
    {
        throw;
    }
    return data;
}

The Read File method uses the StreamReader object to read a text file located inside the device (mobile device). If this file does not exist, the application will give an error when loaded on the mobile device.

My next method gives me the location of the database file ".sdf" in the mobile device.

C#
private String DBFile()
{
    return "/My Documents/Templates/myDBDataSet.sdf";
}

I also need to construct a connection string and check if the database file exists. You can see from the methods below, I did not select a password. You may place a password in case you need a secure database file. The CheckConnection() method ensures that a database file exists before generating the interface. If it does not exist, it will load a different interface.

C#
private String ConnectionString()
{
    return "Data Source ='"+ DBFile() +"'; Password ='';";
}
private bool CheckConnection()
{
    SqlCeConnection localCon = new SqlCeConnection(ConnectionString());
    if (!System.IO.File.Exists(DBFile()))
    {
        //MessageBox.Show("Database File Does not Exist");
        return false;
    }
    else return true;
}

If this happens and we do not have a database file or we deleted it because we updated the text file, we need to create the database. The following method creates the database for the user if it does not exist in the device.

C#
private void CreateDB()
{
    SqlCeEngine m = new SqlCeEngine(ConnectionString());
    m.CreateDatabase();
    MessageBox.Show("Database Created. Please Restart the Application");
}

Another supplementary method is opening the connection to the database and returning the SqlCeConnection:

C#
private SqlCeConnection openCon()
{
    try
    {
        SqlCeConnection con = new SqlCeConnection(ConnectionString());
        con.Open();
        return con;
    }
    catch (Exception) { return null; }
}

All is set, there are two more methods, one for creating the table inside the database and the other to INSERT values into the table.

C#
private void CreateTable()
{
    String myFields = "";
    foreach (var f in fields)
    {
        myFields += ",[" + f.ToString() + "] [nvarchar](50) NULL ";
    }
    String tsql = String.Format
    ("CREATE TABLE [tblData]([ID] [int] IDENTITY(1,1) NOT NULL {0})",myFields);
    using (SqlCeConnection con = openCon())
    {
        SqlCeCommand cmd = new SqlCeCommand(tsql, con);
        cmd.CommandType = System.Data.CommandType.Text;
        cmd.ExecuteNonQuery();
    }
}
private void SaveData()
{
    String myFields = "";
    int j = fields.Count;
    int i = 0;
    foreach (var f in fields)
    {
        i++;
        myFields += "[" + f.ToString() + "]";
        if (i < j )
        {
            myFields += ",";
        }
    }
    String myValues = "";
    j = myControls.Count;
    i = 0;
    foreach (var o in myControls)
    {
        i++;
        TextBox txt = o as TextBox;
        myValues += "'" + txt.Text + "'";
        if (i < j)
        {
            myValues += ",";
        }
    }
    String tsql = String.Format
    ("INSERT INTO [tblData]({0}) VALUES ({1})", myFields, myValues);
    using (SqlCeConnection con = openCon())
    {
        SqlCeCommand cmd = new SqlCeCommand(tsql, con);
        cmd.CommandType = System.Data.CommandType.Text;
        cmd.ExecuteNonQuery();
        MessageBox.Show("Data Saved");
    }
} 

As you can see, I have used TSQL to create the table and have assigned an identity column called [id]. You will also notice that I have used a variable called "field". This variable is an ArrayList that has the field names of the table save in it.

C#
private ArrayList fields = null;

We fill the content of this variable in the InitializeComponent method which is the next method:

C#
private void InitializeComponent()
{
    this.SuspendLayout();
    this.PerformAutoScale();
    
    //this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
    //this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
    
    int MaxX = this.Width = 160;
    int MaxY = this.Height = 250;
    int minX =5, minY = 0;
    string myResult = readFile("App.txt");
    char[] separator = (new Char[] { '-', '\n' });
    string[] splotArr = myResult.Split(separator);
    
    #region for INSERT/CREATE TABLE
    fields = new ArrayList();
    foreach (string s in splotArr)
    {
        if (s.Contains("{"))
        {
            String finalS = s.Replace("{", "");
            finalS = finalS.Replace("}", "");
            finalS = finalS.Replace("\r", "");
            fields.Add(finalS);
        }
    }
    #endregion
    if (CheckConnection())
    {
        #region ReadFile
        
        Random r = new Random();
        //always create a tab
        TabControl t = myTab("GenTab");
        TabPage p = null;
        
        foreach (string s in splotArr)
        {        
            if (s.Contains("<"))
            {
                minX = 0; minY = 0;
                String finalS = s.Replace("<", "");
                finalS = finalS.Replace("\r", "");
                finalS = finalS.Replace(">", "");
                p = myTabPage(finalS, finalS, t);
                
            }
            else if (s.Contains("{"))
            {
                String finalS = s.Replace("{", "");
                finalS = finalS.Replace("}", "");
                finalS = finalS.Replace("\r", "");
                minX = (MaxX / 2) + 30;
                //s is a control textbox
                myTextbox(finalS, p, minX, minY);
            }
            else
            {
                if (s.Trim() != "")
                {
                    minX = 5;
                    minY += 25;
                    myLabel(r.Next().ToString(), s, p, minX, minY);
                }
            }
        }
        this.Controls.Add(t); //add tab
        this.Controls.Add(myButton("bt", "Save", this.Width, this.Height + 20));
        #endregion
    }
    else
    {
        this.Controls.Add(myLabel("ID1", "Database Does Not Exist.", 50, 50));
        this.Controls.Add(myButton("bt", "Create", this.Width/2, this.Height/2));
    }
    this.Name = "Form";
    this.ResumeLayout(false);  
}

You will notice a few things after I have read the text file and parsed it. First I check if the database file exists. If it does not exist, I create a label and a create button in the center of the screen, informing the user that there is no database file. Therefore a new text file was created resulting in the following screen:

Initial_Screen_Without_Database.jpg

After the user hits the create button, the button handler contains the following:

C#
private void btnSave_Click(object sender, EventArgs e)
{
    Button btn = sender as Button;
    switch (btn.Text)
    {
        case "Create": CreateDB(); CreateTable(); break;
        case "Save": SaveData(); break;
    }
}

The Create button creates the database file and associated table and asks the user to restart the application. Once the application is restarted, the If statement in the initializecomponent method will return true and will generate the screen based on the criteria mentioned resulting in the following screen:

Interface_Generated.jpg

The save button in turn saves the data in the text boxes via the save handler and shows the user the saved dialog box.

Saved_Data.jpg

Points of Interest

As you can see, changing the text file requires the user to delete the database file manually for the application to generate a new database and table based on the text file. You can change this and improve it by creating a drop table method and checking against the columns in the old table and new text file.

One thing to note is, Windows mobile does not have a layout manager, so one cannot create the flow or grid layout (like Java AWT) without using third party controls. The design although basic, estimates the position of the controls based on certain criteria (pixel height or width). You can create your own custom layout manager and extend this to allow better visualization.

History

  • Initial draft - Aresh Saharkhiz (Dec 1 2011)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)