Server/MainForm.cs
author sl
Tue, 19 Aug 2014 21:26:17 +0200
changeset 37 405a2590eda4
parent 36 a3aa661da810
child 38 babab407f2bb
permissions -rw-r--r--
Font height now also checked on startup.
     1 using System;
     2 using System.Collections.Generic;
     3 using System.ComponentModel;
     4 using System.Data;
     5 using System.Drawing;
     6 using System.Linq;
     7 using System.Text;
     8 using System.Threading.Tasks;
     9 using System.Windows.Forms;
    10 using System.IO;
    11 using CodeProject.Dialog;
    12 using System.Drawing.Imaging;
    13 using System.ServiceModel;
    14 using System.Threading;
    15 using System.Diagnostics;
    16 //
    17 using SharpDisplayInterface;
    18 using SharpDisplayClient;
    19 
    20 
    21 namespace SharpDisplayManager
    22 {
    23     public partial class MainForm : Form
    24     {
    25         DateTime LastTickTime;
    26         Display iDisplay;
    27         System.Drawing.Bitmap iBmp;
    28         bool iCreateBitmap; //Workaround render to bitmap issues when minimized
    29         ServiceHost iServiceHost;
    30         /// <summary>
    31         /// Our collection of clients
    32         /// </summary>
    33         public Dictionary<string, ClientData> iClients;
    34         public bool iClosing;
    35 
    36         public MainForm()
    37         {
    38             LastTickTime = DateTime.Now;
    39             iDisplay = new Display();
    40             iClients = new Dictionary<string, ClientData>();
    41 
    42             InitializeComponent();
    43             UpdateStatus();
    44 
    45             //Load settings
    46             marqueeLabelTop.Font = Properties.Settings.Default.DisplayFont;
    47             marqueeLabelBottom.Font = Properties.Settings.Default.DisplayFont;
    48             checkBoxShowBorders.Checked = Properties.Settings.Default.DisplayShowBorders;
    49             checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
    50             checkBoxReverseScreen.Checked = Properties.Settings.Default.DisplayReverseScreen;
    51             //
    52             tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
    53             //We have a bug when drawing minimized and reusing our bitmap
    54             iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
    55             iCreateBitmap = false;
    56         }
    57 
    58         private void MainForm_Load(object sender, EventArgs e)
    59         {
    60             StartServer();
    61 
    62             //
    63             CheckFontHeight();
    64             //
    65 
    66 
    67             if (Properties.Settings.Default.DisplayConnectOnStartup)
    68             {
    69                 iDisplay.Open();
    70                 UpdateStatus();
    71             }
    72         }
    73 
    74 
    75         private void buttonFont_Click(object sender, EventArgs e)
    76         {
    77             //fontDialog.ShowColor = true;
    78             //fontDialog.ShowApply = true;
    79             fontDialog.ShowEffects = true;
    80             fontDialog.Font = marqueeLabelTop.Font;
    81 
    82             fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
    83 
    84             //fontDialog.ShowHelp = true;
    85 
    86             //fontDlg.MaxSize = 40;
    87             //fontDlg.MinSize = 22;
    88 
    89             //fontDialog.Parent = this;
    90             //fontDialog.StartPosition = FormStartPosition.CenterParent;
    91 
    92             //DlgBox.ShowDialog(fontDialog);
    93 
    94             //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
    95             if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
    96             {
    97 
    98                 //MsgBox.Show("MessageBox MsgBox", "MsgBox caption");
    99 
   100                 //MessageBox.Show("Ok");
   101                 marqueeLabelTop.Font = fontDialog.Font;
   102                 marqueeLabelBottom.Font = fontDialog.Font;
   103                 Properties.Settings.Default.DisplayFont = fontDialog.Font;
   104                 Properties.Settings.Default.Save();
   105                 //
   106                 CheckFontHeight();
   107             }
   108         }
   109 
   110         /// <summary>
   111         /// 
   112         /// </summary>
   113         void CheckFontHeight()
   114         {
   115             if (marqueeLabelBottom.Font.Height > marqueeLabelBottom.Height)
   116             {
   117                 labelWarning.Text = "WARNING: Selected font is too height by " + (marqueeLabelBottom.Font.Height - marqueeLabelBottom.Height) + " pixels!";
   118                 labelWarning.Visible = true;
   119             }
   120             else
   121             {
   122                 labelWarning.Visible = false;
   123             }
   124 
   125         }
   126 
   127         private void buttonCapture_Click(object sender, EventArgs e)
   128         {
   129             System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
   130             tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
   131             //Bitmap bmpToSave = new Bitmap(bmp);
   132             bmp.Save("D:\\capture.png");
   133 
   134             marqueeLabelTop.Text = "Sweet";
   135 
   136             /*
   137             string outputFileName = "d:\\capture.png";
   138             using (MemoryStream memory = new MemoryStream())
   139             {
   140                 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
   141                 {
   142                     bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
   143                     byte[] bytes = memory.ToArray();
   144                     fs.Write(bytes, 0, bytes.Length);
   145                 }
   146             }
   147              */
   148 
   149         }
   150 
   151         private void CheckForRequestResults()
   152         {
   153             if (iDisplay.IsRequestPending())
   154             {
   155                 switch (iDisplay.AttemptRequestCompletion())
   156                 {
   157                     case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
   158                         if (iDisplay.PowerSupplyStatus())
   159                         {
   160                             toolStripStatusLabelPower.Text = "ON";
   161                         }
   162                         else
   163                         {
   164                             toolStripStatusLabelPower.Text = "OFF";
   165                         }
   166                         //Issue next request then
   167                         iDisplay.RequestDeviceId();
   168                         break;
   169 
   170                     case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
   171                         toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
   172                         //Issue next request then
   173                         iDisplay.RequestFirmwareRevision();
   174                         break;
   175 
   176                     case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
   177                         toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
   178                         //No more request to issue
   179                         break;
   180                 }
   181             }
   182         }
   183 
   184 
   185         public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
   186 
   187 
   188         public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
   189         {
   190             return aBmp.Width - aX - 1;
   191         }
   192 
   193         public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
   194         {
   195             return iBmp.Height - aY - 1;
   196         }
   197 
   198         public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
   199         {
   200             return aX;
   201         }
   202 
   203         public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
   204         {
   205             return aY;
   206         }
   207 
   208 
   209         //This is our timer tick responsible to perform our render
   210         private void timer_Tick(object sender, EventArgs e)
   211         {
   212             //Update our animations
   213             DateTime NewTickTime = DateTime.Now;
   214 
   215             marqueeLabelTop.UpdateAnimation(LastTickTime, NewTickTime);
   216             marqueeLabelBottom.UpdateAnimation(LastTickTime, NewTickTime);
   217 
   218             //Update our display
   219             if (iDisplay.IsOpen())
   220             {
   221                 CheckForRequestResults();
   222 
   223                 //Draw to bitmap
   224                 if (iCreateBitmap)
   225                 {
   226                     iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
   227                 }
   228                 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
   229                 //iBmp.Save("D:\\capture.png");
   230 
   231                 //Select proper coordinate translation functions
   232                 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
   233                 CoordinateTranslationDelegate screenX;
   234                 CoordinateTranslationDelegate screenY;
   235 
   236                 if (Properties.Settings.Default.DisplayReverseScreen)
   237                 {
   238                     screenX = ScreenReversedX;
   239                     screenY = ScreenReversedY;
   240                 }
   241                 else
   242                 {
   243                     screenX = ScreenX;
   244                     screenY = ScreenY;
   245                 }
   246 
   247                 //Send it to our display
   248                 for (int i = 0; i < iBmp.Width; i++)
   249                 {
   250                     for (int j = 0; j < iBmp.Height; j++)
   251                     {
   252                         unchecked
   253                         {
   254                             uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
   255                             //For some reason when the app is minimized in the task bar only the alpha of our color is set.
   256                             //Thus that strange test for rendering to work both when the app is in the task bar and when it isn't.
   257                             iDisplay.SetPixel(screenX(iBmp, i), screenY(iBmp, j), Convert.ToInt32(!(color != 0xFF000000)));
   258                         }
   259                     }
   260                 }
   261 
   262                 iDisplay.SwapBuffers();
   263 
   264             }
   265 
   266             //Compute instant FPS
   267             toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " FPS";
   268 
   269             LastTickTime = NewTickTime;
   270 
   271         }
   272 
   273         private void buttonOpen_Click(object sender, EventArgs e)
   274         {
   275             if (iDisplay.Open())
   276             {
   277                 UpdateStatus();
   278                 iDisplay.RequestPowerSupplyStatus();
   279             }
   280             else
   281             {
   282                 UpdateStatus();
   283                 toolStripStatusLabelConnect.Text = "Connection error";
   284             }
   285 
   286         }
   287 
   288         private void buttonClose_Click(object sender, EventArgs e)
   289         {
   290             iDisplay.Close();
   291             UpdateStatus();
   292         }
   293 
   294         private void buttonClear_Click(object sender, EventArgs e)
   295         {
   296             iDisplay.Clear();
   297             iDisplay.SwapBuffers();
   298         }
   299 
   300         private void buttonFill_Click(object sender, EventArgs e)
   301         {
   302             iDisplay.Fill();
   303             iDisplay.SwapBuffers();
   304         }
   305 
   306         private void trackBarBrightness_Scroll(object sender, EventArgs e)
   307         {
   308             Properties.Settings.Default.DisplayBrightness = trackBarBrightness.Value;
   309             Properties.Settings.Default.Save();
   310             iDisplay.SetBrightness(trackBarBrightness.Value);
   311 
   312         }
   313 
   314         private void UpdateStatus()
   315         {
   316             if (iDisplay.IsOpen())
   317             {
   318                 buttonFill.Enabled = true;
   319                 buttonClear.Enabled = true;
   320                 buttonOpen.Enabled = false;
   321                 buttonClose.Enabled = true;
   322                 trackBarBrightness.Enabled = true;
   323                 trackBarBrightness.Minimum = iDisplay.MinBrightness();
   324                 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
   325                 trackBarBrightness.Value = Properties.Settings.Default.DisplayBrightness;
   326                 trackBarBrightness.LargeChange = Math.Max(1,(iDisplay.MaxBrightness() - iDisplay.MinBrightness())/5);
   327                 trackBarBrightness.SmallChange = 1;
   328                 iDisplay.SetBrightness(Properties.Settings.Default.DisplayBrightness);
   329 
   330                 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
   331                 //+ " - " + iDisplay.SerialNumber();
   332             }
   333             else
   334             {
   335                 buttonFill.Enabled = false;
   336                 buttonClear.Enabled = false;
   337                 buttonOpen.Enabled = true;
   338                 buttonClose.Enabled = false;
   339                 trackBarBrightness.Enabled = false;
   340                 toolStripStatusLabelConnect.Text = "Disconnected";
   341             }
   342         }
   343 
   344 
   345 
   346         private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
   347         {
   348             //Save our show borders setting
   349             tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
   350             Properties.Settings.Default.DisplayShowBorders = checkBoxShowBorders.Checked;
   351             Properties.Settings.Default.Save();
   352         }
   353 
   354         private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
   355         {
   356             //Save our connect on startup setting
   357             Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
   358             Properties.Settings.Default.Save();
   359         }
   360 
   361         private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
   362         {
   363             //Save our reverse screen setting
   364             Properties.Settings.Default.DisplayReverseScreen = checkBoxReverseScreen.Checked;
   365             Properties.Settings.Default.Save();
   366         }
   367 
   368         private void MainForm_Resize(object sender, EventArgs e)
   369         {
   370             if (WindowState == FormWindowState.Minimized)
   371             {
   372                 // Do some stuff
   373                 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
   374                 iCreateBitmap = true;
   375             }
   376         }
   377 
   378         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
   379         {
   380             StopServer();
   381             e.Cancel = iClosing;
   382         }
   383 
   384         public void StartServer()
   385         {
   386             iServiceHost = new ServiceHost
   387                 (
   388                     typeof(DisplayServer),
   389                     new Uri[] { new Uri("net.tcp://localhost:8001/") }
   390                 );
   391 
   392             iServiceHost.AddServiceEndpoint(typeof(IDisplayService), new NetTcpBinding(SecurityMode.None,true), "DisplayService");
   393             iServiceHost.Open();
   394         }
   395 
   396         public void StopServer()
   397         {
   398             if (iClients.Count > 0 && !iClosing)
   399             {
   400                 //Tell our clients
   401                 iClosing = true;
   402                 BroadcastCloseEvent();
   403             }
   404             else if (iClosing)
   405             {
   406                 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
   407                 {
   408                     iClosing = false; //We make sure we force close if asked twice
   409                 }
   410             }
   411             else
   412             {
   413                 //We removed that as it often lags for some reason
   414                 //iServiceHost.Close();
   415             }
   416         }
   417 
   418         public void BroadcastCloseEvent()
   419         {
   420             Trace.TraceInformation("BroadcastCloseEvent - start");
   421 
   422             var inactiveClients = new List<string>();
   423             foreach (var client in iClients)
   424             {
   425                 //if (client.Key != eventData.ClientName)
   426                 {
   427                     try
   428                     {
   429                         Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
   430                         client.Value.Callback.OnCloseOrder(/*eventData*/);
   431                     }
   432                     catch (Exception ex)
   433                     {
   434                         inactiveClients.Add(client.Key);
   435                     }
   436                 }
   437             }
   438 
   439             if (inactiveClients.Count > 0)
   440             {
   441                 foreach (var client in inactiveClients)
   442                 {
   443                     iClients.Remove(client);
   444                     Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
   445                 }
   446             }
   447         }
   448 
   449         private void buttonStartClient_Click(object sender, EventArgs e)
   450         {
   451             Thread clientThread = new Thread(SharpDisplayClient.Program.Main);
   452             clientThread.Start();
   453             BringToFront();
   454         }
   455 
   456         private void buttonSuspend_Click(object sender, EventArgs e)
   457         {
   458             timer.Enabled = !timer.Enabled;
   459             if (!timer.Enabled)
   460             {
   461                 buttonSuspend.Text = "Suspend";
   462             }
   463             else
   464             {
   465                 buttonSuspend.Text = "Pause";
   466             }
   467         }
   468 
   469         private void buttonCloseClients_Click(object sender, EventArgs e)
   470         {
   471             BroadcastCloseEvent();
   472         }
   473 
   474         private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
   475         {
   476 
   477         }
   478 
   479         //Delegates are used for our thread safe method
   480         public delegate void AddClientDelegate(string aSessionId, IDisplayServiceCallback aCallback);
   481         public delegate void RemoveClientDelegate(string aSessionId);
   482         public delegate void SetTextDelegate(string SessionId, int aLineIndex, string aText);
   483         public delegate void SetTextsDelegate(string SessionId, System.Collections.Generic.IList<string> aTexts);
   484         public delegate void SetClientNameDelegate(string aSessionId, string aName);
   485 
   486 
   487         /// <summary>
   488         ///
   489         /// </summary>
   490         /// <param name="aSessionId"></param>
   491         /// <param name="aCallback"></param>
   492         public void AddClientThreadSafe(string aSessionId, IDisplayServiceCallback aCallback)
   493         {
   494             if (this.InvokeRequired)
   495             {
   496                 //Not in the proper thread, invoke ourselves
   497                 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
   498                 this.Invoke(d, new object[] { aSessionId, aCallback });
   499             }
   500             else
   501             {
   502                 //We are in the proper thread
   503                 //Add this session to our collection of clients
   504                 ClientData newClient = new ClientData(aSessionId, aCallback);
   505                 Program.iMainForm.iClients.Add(aSessionId, newClient);
   506                 //Add this session to our UI
   507                 UpdateClientTreeViewNode(newClient);
   508             }
   509         }
   510 
   511         /// <summary>
   512         ///
   513         /// </summary>
   514         /// <param name="aSessionId"></param>
   515         public void RemoveClientThreadSafe(string aSessionId)
   516         {
   517             if (this.InvokeRequired)
   518             {
   519                 //Not in the proper thread, invoke ourselves
   520                 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
   521                 this.Invoke(d, new object[] { aSessionId });
   522             }
   523             else
   524             {
   525                 //We are in the proper thread
   526                 //Remove this session from both client collection and UI tree view
   527                 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
   528                 {
   529                     Program.iMainForm.iClients.Remove(aSessionId);
   530                     Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
   531                 }
   532 
   533                 if (iClosing && iClients.Count == 0)
   534                 {
   535                     //We were closing our form
   536                     //All clients are now closed
   537                     //Just resume our close operation
   538                     iClosing = false;
   539                     Close();
   540                 }
   541             }
   542         }
   543 
   544         /// <summary>
   545         ///
   546         /// </summary>
   547         /// <param name="aLineIndex"></param>
   548         /// <param name="aText"></param>
   549         public void SetTextThreadSafe(string aSessionId, int aLineIndex, string aText)
   550         {
   551             if (this.InvokeRequired)
   552             {
   553                 //Not in the proper thread, invoke ourselves
   554                 SetTextDelegate d = new SetTextDelegate(SetTextThreadSafe);
   555                 this.Invoke(d, new object[] { aSessionId, aLineIndex, aText });
   556             }
   557             else
   558             {
   559                 ClientData client = iClients[aSessionId];
   560                 if (client != null)
   561                 {
   562                     //Make sure all our texts are in place
   563                     while (client.Texts.Count < (aLineIndex + 1))
   564                     {
   565                         client.Texts.Add("");
   566                     }
   567                     client.Texts[aLineIndex] = aText;
   568 
   569                     //We are in the proper thread
   570                     //Only support two lines for now
   571                     if (aLineIndex == 0)
   572                     {
   573                         marqueeLabelTop.Text = aText;
   574                     }
   575                     else if (aLineIndex == 1)
   576                     {
   577                         marqueeLabelBottom.Text = aText;
   578                     }
   579 
   580 
   581                     UpdateClientTreeViewNode(client);
   582                 }
   583             }
   584         }
   585 
   586         /// <summary>
   587         ///
   588         /// </summary>
   589         /// <param name="aTexts"></param>
   590         public void SetTextsThreadSafe(string aSessionId, System.Collections.Generic.IList<string> aTexts)
   591         {
   592             if (this.InvokeRequired)
   593             {
   594                 //Not in the proper thread, invoke ourselves
   595                 SetTextsDelegate d = new SetTextsDelegate(SetTextsThreadSafe);
   596                 this.Invoke(d, new object[] { aSessionId, aTexts });
   597             }
   598             else
   599             {
   600                 ClientData client = iClients[aSessionId];
   601                 if (client != null)
   602                 {
   603                     //Populate our client with the given texts
   604                     int j = 0;
   605                     foreach (string text in aTexts)
   606                     {
   607                         if (client.Texts.Count < (j + 1))
   608                         {
   609                             client.Texts.Add(text);
   610                         }
   611                         else
   612                         {
   613                             client.Texts[j]=text;
   614                         }
   615                         j++;
   616                     }
   617                     //We are in the proper thread
   618                     //Only support two lines for now
   619                     for (int i = 0; i < aTexts.Count; i++)
   620                     {
   621                         if (i == 0)
   622                         {
   623                             marqueeLabelTop.Text = aTexts[i];
   624                         }
   625                         else if (i == 1)
   626                         {
   627                             marqueeLabelBottom.Text = aTexts[i];
   628                         }
   629                     }
   630 
   631 
   632                     UpdateClientTreeViewNode(client);
   633                 }
   634             }
   635         }
   636 
   637 
   638         /// <summary>
   639         ///
   640         /// </summary>
   641         /// <param name="aSessionId"></param>
   642         /// <param name="aName"></param>
   643         public void SetClientNameThreadSafe(string aSessionId, string aName)
   644         {
   645             if (this.InvokeRequired)
   646             {
   647                 //Not in the proper thread, invoke ourselves
   648                 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
   649                 this.Invoke(d, new object[] { aSessionId, aName });
   650             }
   651             else
   652             {
   653                 //We are in the proper thread
   654                 //Get our client
   655                 ClientData client = iClients[aSessionId];
   656                 if (client != null)
   657                 {
   658                     //Set its name
   659                     client.Name = aName;
   660                     //Update our tree-view
   661                     UpdateClientTreeViewNode(client);
   662                 }
   663             }
   664         }
   665 
   666         /// <summary>
   667         ///
   668         /// </summary>
   669         /// <param name="aClient"></param>
   670         private void UpdateClientTreeViewNode(ClientData aClient)
   671         {
   672             if (aClient == null)
   673             {
   674                 return;
   675             }
   676 
   677             TreeNode node = null;
   678             //Check that our client node already exists
   679             //Get our client root node using its key which is our session ID
   680             TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
   681             if (nodes.Count()>0)
   682             {
   683                 //We already have a node for that client
   684                 node = nodes[0];
   685                 //Clear children as we are going to recreate them below
   686                 node.Nodes.Clear();
   687             }
   688             else
   689             {
   690                 //Client node does not exists create a new one
   691                 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
   692                 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
   693             }
   694 
   695             if (node != null)
   696             {
   697                 //Change its name
   698                 if (aClient.Name != "")
   699                 {
   700                     //We have a name, us it as text for our root node
   701                     node.Text = aClient.Name;
   702                     //Add a child with SessionId
   703                     node.Nodes.Add(new TreeNode(aClient.SessionId));
   704                 }
   705                 else
   706                 {
   707                     //No name, use session ID instead
   708                     node.Text = aClient.SessionId;
   709                 }
   710 
   711                 if (aClient.Texts.Count > 0)
   712                 {
   713                     //Create root node for our texts
   714                     TreeNode textsRoot = new TreeNode("Text");
   715                     node.Nodes.Add(textsRoot);
   716                     //For each text add a new entry
   717                     foreach (string text in aClient.Texts)
   718                     {
   719                         textsRoot.Nodes.Add(new TreeNode(text));
   720                     }
   721                 }
   722 
   723                 node.ExpandAll();
   724             }
   725         }
   726 
   727 
   728     }
   729 
   730     /// <summary>
   731     /// A UI thread copy of a client relevant data.
   732     /// Keeping this copy in the UI thread helps us deal with threading issues.
   733     /// </summary>
   734     public class ClientData
   735     {
   736         public ClientData(string aSessionId, IDisplayServiceCallback aCallback)
   737         {
   738             SessionId = aSessionId;
   739             Name = "";
   740             Texts = new List<string>();
   741             Callback = aCallback;
   742         }
   743 
   744         public string SessionId { get; set; }
   745         public string Name { get; set; }
   746         public List<string> Texts { get; set; }
   747         public IDisplayServiceCallback Callback { get; set; }
   748     }
   749 }