Well, few weeks ago, I told one of my clients to download "MyMobiler" as he wants to remote control his Windows Mobile Devices. I was surprised that it supports Android now...

Thank you very much, "MyMobiler" team...
by Elvis Hsu No comments Read More
Um, I haven't touch this Blog for a while... So... just a case study.
Usually, I use FTP to find available files to update my mobile application. However,I have a client who doesn't want to install FTP but they have web server. Therefore, I use the traditional way to update the application, "Make a http query"... It is pretty simple to use HttpWebRequest and HttpWebResponse to validate the version. If the server returns a file link, then I download it and install it.



You can make a HttpWebRequest (http://yourwebsite/?version=1.0.2.0) to find available update. If server redirects you to a cab file, so we have an update.



Once user clicks "Yes", the we start to download and install the cab file.



Have a look the following example in C#. It handles downloading the cab file with progress bar.

private void backgroundWorker1_DoWork(object sender, BCD.ComponentModel.DoWorkEventArgs e)
{
    HttpWebRequest myRequest = null;
    HttpWebResponse myResponse = null;
    string newurl = (string)e.Argument;
    string fileName = "", filePath = "";
    try
    {
        bool redirecting = true;

        while (redirecting)
        {
            try
            {
                myRequest = (HttpWebRequest)WebRequest.Create(newurl);
                // we accept any type of file
                myRequest.Accept = "*/*";
                // set the timeout to 5 seconds
                myRequest.Timeout = 5000;
                // casts the response
                myResponse = (HttpWebResponse)myRequest.GetResponse();
                // if we have redirection
                if ((int)myResponse.StatusCode == 301 || (int)myResponse.StatusCode == 302)
                {
                    string uriString = myResponse.Headers["Location"];
                    newurl = uriString;
                    // and keep going
                }
                else
                {
                    // gets the final uri
                    fileName = Path.GetFileName(myResponse.ResponseUri.LocalPath);
                    // we only want CAB file
                    if (fileName.ToLower().EndsWith(".cab"))
                    {
                        filePath = Path.Combine(Utils.TempPath, fileName);
                        // gets the total lenth for progress bar
                        long fileLength = myResponse.ContentLength;
                        // start with zero
                        int byteTotal = 0;
                        // start writing file
                        using (FileStream fileStream = File.OpenWrite(filePath))
                        {
                            // gets the stream from response object
                            using (Stream remoteStream = myResponse.GetResponseStream())
                            {
                                // we make 4 MB as our buffer
                                byte[] inBuffer = new byte[4 * 1024];

                                int nRead = remoteStream.Read(inBuffer, 0, 
                                    inBuffer.Length);

                                // we need to put e.Cancel as user may cancel downloading
                                while (nRead > 0 && !e.Cancel)
                                {
                                    if (nRead > 0)
                                        fileStream.Write(inBuffer, 0, nRead);

                                    byteTotal += nRead;
                                    // calculate the progress out of a base "100"
                                    double percentage = 
                                        (double)(byteTotal) / (double)fileLength;

                                    // update the progress
                                    backgroundWorker1.ReportProgress(
                                        (int)(percentage * 100));

                                    nRead = remoteStream.Read(inBuffer, 0, 
                                        inBuffer.Length);
                                }
                                myResponse.Close();
                                myRequest.Abort();
                            }
                        }

                        // if everything is fine
                        if (!e.Cancel)
                        {
                            // write the file time to be same as the original file time
                            BCD.IO.FileEx.SetCreationTime(filePath, 
                                myResponse.LastModified);
                            e.Result = filePath;
                        }
                        else
                        {
                            if (File.Exists(filePath))
                                File.Delete(filePath);
                        }
                    }
                    redirecting = false;
                }
            }
            catch (Exception ex)
            {
                redirecting = false;
                Utils.WriteToLog(ex);
            }
        }
    }
    catch (Exception ex)
    {
        // if error happens on retriving map, assign the error image instead
        Utils.WriteToLog(ex);
    }
    finally
    {
        if (myResponse != null)
            myResponse.Close();

        myRequest = null;
        myResponse = null;
    }
}
by Elvis Hsu No comments Read More


I remember a while ago that I needed to enable the vibration for a client's application. So I googled a bit and found the similar codes like below... And today, I found some people are looking for this solution on MSDN... So I decided to post it in C# and hope it will give some people a heads up...



       /// 
        /// This function plays the specified vibration.
        /// 
        /// Must be 0
        /// Must be NULL
        /// A Boolean value that indicates whether the song 
        /// should be repeated. If this parameter is equal to TRUE, it will refer to dwTimeout 
        /// to determine how long the vibration song should play.
        /// Must be INFINITE
        /// 
        /// This API is not supported by Windows Mobile 6 Professional.
        [DllImport("aygshell.dll", EntryPoint = "Vibrate")]
        private static extern int VibrateStart(int cvn, IntPtr rgvn, bool fRepeat, uint dwTimeout);

        /// 
        /// Stops all current vibration songs.
        /// 
        /// 
        /// 
        /// This API not supported by Windows Mobile 6 Professional.
        /// 
        [DllImport("aygshell.dll")]
        private static extern int VibrateStop();

        private const uint INFINITE = 0xffffffff;

        /// 
        /// Start the mobile vibration.
        /// This is not supported by Windows Mobile 6 Professional.
        /// 
        /// 
        public static int StartVibrate()
        {
            return VibrateStart(0, IntPtr.Zero, true, INFINITE);
        }

        /// 
        /// Stops all current vibration.
        /// This is not supported by Windows Mobile 6 Professional.
        /// 
        public static void StopVibrate()
        {
            VibrateStop();
        }
by Elvis Hsu No comments Read More
Pooh~~~ I haven't organised the blog page for ages... Well, not many clicks~~~
Anyway, I tried to check emails at home with Ubuntu and found out the Evolution could not connect to our Exchange 2007 server. I found out it works well when I am in the office or VPN connection is on. Um... I think that will be my new task to find out what causes this... Or... If any one reads this post, I would appreciated some comments or suggestions for this issue.
by Elvis Hsu No comments Read More

Hey... here is another one for everybody... As the standard paging toolbar of ExtJs 4 does not have function to change the page size of the data store, so I make a simple plugin for the paging toolbar. I believe there are many examples if you Google it, but just another sample to share though...



/**
* Ext.ux.grid.PageSize
*/
Ext.define('Ext.ux.grid.PageSize', {
    extend      : 'Ext.form.field.ComboBox',
    alias       : 'plugin.pagesize',
    beforeText  : 'Show',
    afterText   : 'rows/page',
    mode        : 'local',
    displayField: 'text',
    valueField  : 'value',
    allowBlank  : false,
    triggerAction: 'all',
    width       : 50,
    maskRe      : /[0-9]/,    
    /**
    * initialize the paging combo after the pagebar is randered
    */
    init: function(paging) {
        paging.on('afterrender', this.onInitView, this);
    },
    /**
    * create a local store for availabe range of pages
    */
    store: new Ext.data.SimpleStore({
        fields: ['text', 'value'],
        data: [['5', 5], ['10', 10], ['15', 15], ['20', 20], ['25', 25], ['50', 50], ['100', 100], ['200', 200], ['500', 500]]
    }),    
    /**
    * assing the select and specialkey events for the combobox 
    * after the pagebar is rendered.
    */
    onInitView: function(paging) {
        this.setValue(paging.store.pageSize); 
        paging.add('-', this.beforeText, this, this.afterText);
        this.on('select', this.onPageSizeChanged, paging);
        this.on('specialkey', function(combo, e) {
            if(13 === e.getKey()) {
                this.onPageSizeChanged.call(paging, this);        
            }
        });
    },
    /**
    * refresh the page when the value is changed
    */
    onPageSizeChanged: function(combo) {
        this.store.pageSize = parseInt(combo.getRawValue(), 10);
        this.doRefresh();
    }
}); 
by Elvis Hsu 8 comments Read More
Dynamic GridPanel for ExtJs 4
Recently, I was trying to update our internal system from ExtJs 3 to version 4. Well, I think many V3 plugins are needed to be updated. So I was a bit busy on that... Anyway, I am going to share a bit of code which is the dynamic grid for ExtJs 4. As the sample from Erhan Abay is for ExtJs 3, so I made a few changes for ExtJs 4.


/**
* Ext.ux.grid.DynamicGridPanel
*/
Ext.define('Ext.ux.grid.DynamicGridPanel', {
    extend: 'Ext.grid.GridPanel',
    alias: 'widget.dynamicgrid',
    /**
    * initialising the components
    */
    initComponent: function(){
        /**
        * set the config we want
        */
        var config = {
            columns:[],
            rowNumberer: false
        };
        
        // appy to this config
        Ext.apply(this, config);
        // apply to the initialConfig
        Ext.apply(this.initialConfig, config);
        // call the arguments
        this.callParent(arguments);
    },
    /**
    * When the store is loading then reconfigure the column model of the grid
    */
    storeLoad: function()
    {
        /**
        * JSON data returned from server has the column definitions
        */
        if(typeof(this.store.proxy.reader.jsonData.columns) === 'object') {
            var columns = [];
            /**
            * adding RowNumberer as we need to add them 
            * before other columns to display first
            */
            if(this.rowNumberer) { columns.push(Ext.create('Ext.grid.RowNumberer')); }
            /**
            * assign new columns from the json data columns
            */
            Ext.each(this.store.proxy.reader.jsonData.columns, function(column){
                columns.push(column);
            });
            /**
            *  reconfigure the column model of the grid
            */
            this.reconfigure(this.store, columns);
        }
    },
    /**
    * assign the event to itself when the object is initialising
    */
    onRender: function(ct, position){
            /**
            *  well, old fashion way, but works well.
            */
            Ext.ux.grid.DynamicGridPanel.superclass.onRender.call(this, ct, position);
            /**
            * hook the store load event to our function
            */
            this.store.on('load', this.storeLoad, this);
    }
});

Client Side Example: (Tested on ExtJs 4.0.7 and 4.1.1)
// Start loading the page        
Ext.onReady(function(){
    // we need to define the model but the field values will be parsed
    // automatically since we provided fields in the metaData from server
   Ext.define('dynamicModel', {
     extend: 'Ext.data.Model',
     //set the proxy
     proxy: {
       type: 'rest',
       url: 'data.php' // the sample server address
     }
   });
   // create a data store
   var myStore = Ext.create('Ext.data.Store', {
         model:'dynamicModel',
         autoLoad:true,
   });    
   // create dynamic grid
   var myGrid = {
       title:'Dynamic Grid',
       xtype:'dynamicgrid',
       forceFit:true,
       region:'center',
       store:myStore,
       dockedItems: [{
         xtype: 'pagingtoolbar',
         store: myStore,
         dock: 'bottom',
         displayInfo: true
       }]              
   };   
    // finally, build the main layout once all the pieces are ready.
    Ext.create('Ext.container.Viewport', {
        layout:'border',
        items:[myGrid]
    });
});

Server Side Example: (data.php)
    
    $total = 100;
    // you can pre-define the required property parameters
    $output["metaData"]["idProperty"]="id";
    $output["metaData"]["totalProperty"]="total";
    $output["metaData"]["successProperty"]="success";
    $output["metaData"]["root"]="data";
    // you can parse field values via your database schema
    $output["metaData"]["fields"][]=array("name"=>"id","type"=>"int");
    $output["metaData"]["fields"][]=array("name"=>"name","type"=>"string");
    $output["metaData"]["fields"][]=array("name"=>"firstName","type"=>"string");
    $output["metaData"]["fields"][]=array("name"=>"lastName","type"=>"string");
    // you can parse column values via your database schema
    $output["columns"][]=array("dataIndex"=>"id","header"=>"ID", "width"=>10);
    $output["columns"][]=array("dataIndex"=>"name","header"=>"User Name","width"=>20);
    $output["columns"][]=array("dataIndex"=>"firstName","header"=>"First Name");
    $output["columns"][]=array("dataIndex"=>"lastName","header"=>"Last Name");
    // the misc properties
    $output["total"]=$total;
    $output["success"]=true;
    $output["message"]="success";
    // parse pages     
    $start = $_GET['start'] + 1;
    $max = $_GET['start'] + $_GET['limit'];
    // make sample data
    for($i = $start; $i <= $max; $i++ ){
     $output["data"][]= array(
                    "id"=>$i,
                    "name"=>"UserName-". $i,
                    "firstName"=>"My First Name No. ". $i,
                    "lastName"=>"My Last Name No. ". $i);
    }    
    // output the value
    echo json_encode($output);
by Elvis Hsu 55 comments Read More
Today, I was trying to connect to our VPN with Ubuntu 11.04 and found out that was not working. Well, after a bit searches on Google, I found out there are some points of VPN settings that I needed to change.


Tricky VPN settings on Ubuntu 11.04
  1. Username should be domain\username.
  2. Do not fill out domain in the VPN configuration.
  3. Under the advanced option make sure you tick "Use Point-to-Point encryption (MPPE)" option. (see above screenshot)
by Elvis Hsu 3 comments Read More
I was trying to setup my laptop with Ubuntu 10.10. Well, so far, most of applications and devices are working as expected. However, the microphone with Gmail Chat is pretty much a pain.


First of all, I found this link and installed the pavucontrol and changed the settings to Microphone 2. Well, it works with Skype but not with Gmail Chat. The Gmail Chat driver auto adjusts volume so it all went back to the default settings again... WTF...


Anyway... keep fighting on it at the moment... Has anyone had this problem and resolved it?
by Elvis Hsu No comments Read More

Well, this is long way back when I was a rookie to develop Windows Mobile applications with C#.
I believe there are some people want to sync the device date/time with their web server as the device date/time somehow doesn't tick correctly after certain days. So first of all, you should have a look the SetSystemTime function as you need a SYSTEMTIME structure to pass the value to SetSystemTime function.

We start to P/Invoke the SetSystemTime method.

[DllImport("coredll.dll", SetLastError = true)]
private static extern bool SetSystemTime(ref SYSTEMTIME time);

Create the SYSTEMTIME structure.

public struct SYSTEMTIME
{
     public short year;
     public short month;
     public short dayOfWeek;
     public short day;
     public short hour;
     public short minute;
     public short second;
     public short milliseconds;
}

Create a simple function to convert DateTime to SYSTEMTIME and also calls the SetSystemTime function.

public void SetSystemDateTime(DateTime time)
{
    SYSTEMTIME s = new SYSTEMTIME();
     s.Year = (short)time.Year;
     s.Month = (short)time.Month;
     s.DayOfWeek = (short)time.DayOfWeek;
     s.Day = (short)time.Day;
     s.Hour = (short)time.Hour;
     s.Minute = (short)time.Minute;
     s.Second = (short)time.Second;
     s.Milliseconds = (short)time.Millisecond;
     SetSystemTime(ref s);
}

As most http web pages contains "Date" part of the document header and the "Date" part usually is the server time. So we gets the date/time and sync with our device. We use HttpWebRequest and HttpWebResponse to get the web page header.

public bool SyncDateTime(string url)
{
    HttpWebRequest myRequest = null;
    HttpWebResponse myResponse = null;
    try
    {
        //create a HTTP request of the file and capture the response
        myRequest = (HttpWebRequest)WebRequest.Create(url);
        myRequest.Accept = "*/*";
        myRequest.KeepAlive = false;

        // Assign the response object of 'HttpWebRequest' to a 'HttpWebResponse' variable.          
        myResponse = (HttpWebResponse)myRequest.GetResponse();

        if (myResponse != null)
        {
            if (myResponse.Headers["Date"] != null)
            {
                DateTime dt = DateTime.Parse(myResponse.Headers["Date"], CultureInfo.CurrentCulture);
                // Sets the parsed time to device.
                SetSystemDateTime(dt.ToUniversalTime());
                return true;
            }
        }
    }
    finally
    {
        // Releases the resources of the response.
        if (myResponse != null)
            myResponse.Close();

        myRequest = null;
        myResponse = null;
    }
    return false;
}

Above method has few millisecond or event seconds inaccuracy as we doesn't calculate the time difference from request to response. However, it is quite good enough for many customers. Ha... I am lazy again...
by Elvis Hsu No comments Read More
When I deploy the application to customers, I like to wrap all cabs in one as it would be much easier. For example, .NETCF and SQLCE. If the device is new, the .NETCF or SQLCE needs to be installed prior running my application. Thanks for CeSetupDriver and here. They helped me a lot to deploy the application with the way I want. However, I don't want to prompt user every time that the .NETCF or SQLCE has already installed. So I come up with a checking method to validate that the application has already installed and skip to next cab installation. All you need to do is passing you name of application and CSP (Configuration Service Provider) xml to validate. However, somehow I feel the code is a bit long. Can anyone shorten it? Ha... I am just lazy...

/** 
 * Query the installation files
 *
 * @param LPTSTR xmlDoc The xml document 
 * @param appName The application name
 * @return Returns true if installed
 */
BOOL Utils::QueryInstalledByCSP(LPTSTR xmlDoc, const TCHAR *appName)
{
  BOOL szResult = FALSE;
  
  if(xmlDoc == NULL)
   return FALSE;
   IXMLDOMDocument *pDOM = NULL;
    
    // Load the XML DOM
    if(SUCCEEDED(CoInitializeEx(NULL,COINIT_MULTITHREADED)))
 {    
  if(SUCCEEDED(CoCreateInstance(CLSID_DOMDocument, NULL, 
   CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, 
   IID_IXMLDOMDocument, (LPVOID *) &pDOM)))
  {
   // Tell the "doc" that we're not going to load asynchronously.
   if (SUCCEEDED(pDOM->put_async(VARIANT_FALSE)))
   {
    VARIANT_BOOL vBool;
    pDOM->loadXML(xmlDoc,&vBool);
    if (vBool == VARIANT_TRUE)
    {
     IXMLDOMNode *pNode;
     if (SUCCEEDED(pDOM->selectSingleNode(_T("wap-provisioningdoc"), &pNode)))
          {   
      IXMLDOMNodeList *pNodeList;
      IXMLDOMNode *pNodeChild;
      IXMLDOMNode *pNodeValue;
      IXMLDOMNamedNodeMap *pXMLNamedNodeMap;
      if (SUCCEEDED(pNode->selectSingleNode(_T("characteristic"), &pNode)))
      {   
       if(SUCCEEDED(pNode->selectNodes(_T("characteristic"), &pNodeList)))
       {
        long length = 0;
        pNodeList->get_length(&length);
        if(length > 0)
        {
         HRESULT hr = pNodeList->nextNode(&pNodeChild);
         while(hr == S_OK)
         {         
          if(SUCCEEDED(pNodeChild->get_attributes(&pXMLNamedNodeMap)))
          {
           if(SUCCEEDED(pXMLNamedNodeMap->getNamedItem(_T("type"), &pNodeValue)))
           {
            VARIANT varNodeValue;
            if(SUCCEEDED(pNodeValue->get_nodeValue(&varNodeValue)))
            {
             if(lstrcmpi(varNodeValue.bstrVal, appName) == 0)
             {
              szResult = TRUE;
              break;
             }
            }
            pNodeValue->Release();
           }
          }
          hr = pNodeList->nextNode(&pNodeChild);
         }
         pXMLNamedNodeMap->Release();
        }
        pNodeList->Release();
       }
      }
           }
     pNode->Release();
     pNode = NULL;
    }
   }
   pDOM->Release();
   // release COM
   CoUninitialize();
  }
 }
  return szResult;
}

So now what you have to do is add another method to check name of the cabs.
So far, it works all fine on all Motorola (Symbol) devices.

/** 
 * Check the installed application name with related cab file 
 */
BOOL Utils::CheckCabInstalled(LPTSTR xmlDoc, const TCHAR *cabName)
{
 if(lstrcmpi(cabName, _T("NETCFv35.wm.armv4i.cab")) == 0)
  return QueryInstalledByCSP(xmlDoc, _T("Microsoft .NET CF 3.5"));
 else if(lstrcmpi(cabName, _T("NETCFv35.Messages.EN.wm.cab")) == 0)
  return QueryInstalledByCSP(xmlDoc, _T("Microsoft .NET CF 3.5 EN-String Resource"));
 else if(lstrcmpi(cabName, _T("sqlce.ppc.wce5.armv4i.CAB")) == 0)
  return QueryInstalledByCSP(xmlDoc, _T("SQLServerCompact 3.5 Core"));
 else if(lstrcmpi(cabName, _T("sqlce.repl.ppc.wce5.armv4i.CAB")) == 0)
  return QueryInstalledByCSP(xmlDoc, _T("SQLServerCompact 3.5 Repl"));
 else if(lstrcmpi(cabName, _T("sqlce.dev.ENU.ppc.wce5.armv4i.CAB")) == 0)
  return QueryInstalledByCSP(xmlDoc, _T("SQLServerCompact 3.5 Tools EN"));
 else 
  return FALSE;
}
by Elvis Hsu No comments Read More


It was a while ago to play the colours in .NETCF. The case was drawing darker or lighter colour when user clicks my custom design buttons. Well, I believe it would be much easier to change the colour by its original colour. So I came up with two methods to extend the existing System.Drawing.Color object. These two methods can be found similar over the Internet. So, I have to thank all the developers in the Internet...



public static Color GetColorDarker(this Color color, double factor)
{
    // The factor value value cannot be greater than 1 or smaller than 0.
    // Otherwise return the original colour
    if (factor < 0 || factor > 1)
        return color;

    int r = (int)(factor * color.R);
    int g = (int)(factor * color.G);
    int b = (int)(factor * color.B);
    return Color.FromArgb(r, g, b);
}


public static Color GetColorLighter(this Color color, double factor)
{
    // The factor value value cannot be greater than 1 or smaller than 0.
    // Otherwise return the original colour
    if (factor < 0 || factor > 1)
        return color;

    int r = (int)(factor * color.R + (1 - factor) * 255);
    int g = (int)(factor * color.G + (1 - factor) * 255);
    int b = (int)(factor * color.B + (1 - factor) * 255);
    return Color.FromArgb(r, g, b);
}
by Elvis Hsu No comments Read More
Today, I was trying to install the Android SDK to my laptop and got "JDK not found on Installing Android SDK"... Well, I was a bit confused as I have installed the latest JDK.

So, I decided to google it. Most of results are for 64bit OS but I use 32bit Windows 7. Well, then I pressed "Back" button and then pressed "Next" again in the initialization page. Ha... It allows me to install it then...

I believe the installer caches the previous data if you haven't installed the JDK. Well, good to know that is easy to fix this installation issue...
by Elvis Hsu No comments Read More

I just tried to write a function to validate URL address. Well, as usual, I've done some researches on Google and not many efficient and satisfy solutions. However, I've got a solution which is using Regex in C# to validate a URL and it seems a acceptable method to test the URL input.

public static bool IsValidUrl(this string url)
{
    string strRegex = "^(https?://)"
        + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" // user@
        + @"(([0-9]{1,3}\.){3}[0-9]{1,3}" // IP- 10.0.0.1
        + "|" // allows either IP or domain
        + @"([0-9a-z_!~*'()-]+\.)*" // tertiary domain(s)- www.
        + @"([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]" // second level domain
        + @"(\.[a-z]{2,6})?)" // first level domain- .com or .museum is optional
        + "(:[0-9]{1,5})?" // port number- :80
        + "((/?)|" // a slash isn't required if there is no file name
        + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$";

    return new Regex(strRegex).IsMatch(url);
}
by Elvis Hsu No comments Read More


Today I was think how to optimize the drawing string method on my library... so far I worked out this... However I still couldn't workout how to really wrap long string within the sentence...

Any help?


public static string[] WrapString(this Graphics gx, Font font, string text, float maxWidth, bool wrap)
{
    // find if actual max text width is smaller than max width or maxWidth is smaller than zero or wrap is set to false
    if (gx.MeasureString(text, font).Width < maxWidth || maxWidth <= 0 || !wrap)
    {
        return text.Split(new char[] { '\n' });
    }

    int maxChars = (int)(maxWidth / (gx.MeasureString("ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789", font).Width / 37));
    //text = text.BreakLongString(maxChars);

    text = text.Replace(Environment.NewLine, "\n");
    text = text.Replace("\r", "");
    text = text.Replace("\t", " ");

    string[] words = text.Split(new char[] { ' ' });
    List lines = new List((int)(text.Length / maxChars));
    string currentLine = "";

    for (int i = 0; i < words.Length; i++)
    {
        // if the word is empty, then repace it to one space
        if (string.IsNullOrEmpty(words[i]))
            words[i] = " ";

        float currWidth = gx.MeasureString(currentLine + " " + words[i], font).Width;
        
        // check if the current width is greater than max length
        if (currWidth < maxWidth)
        {
            // if first entry, then put current line to the first word
            if ((lines.Count == 0) && string.IsNullOrEmpty(currentLine))
                currentLine = words[i];
            else
            {
                // if not, append each word to current line
                currentLine += " " + words[i];

                // check if the currentline has \n in there.
                string[] newLines = currentLine.Split('\n');
                // if it does, then add a new line
                if (newLines.Length > 1)
                {
                    // do not loop to last as it will be the new currentline
                    for (int j = 0; j < newLines.Length - 1; j++)
                        lines.Add(newLines[j]);

                    // the current line is the last line of the new lines
                    currentLine = newLines[newLines.Length - 1];
                }
            }
        }
        else
        {
            // if the currentline width is greater than max width
            // then add a new line to the list
            if (!string.IsNullOrEmpty(currentLine))
                lines.Add(currentLine);

            // make the current line to the last word
            currentLine = words[i];
        }
    }
    
    // if still has word, add it to the list
    if (!string.IsNullOrEmpty(currentLine))
        lines.Add(currentLine);

    return lines.ToArray();
}
by Elvis Hsu No comments Read More