Adding tray icon support and minimize to tray option.
2 using System.Collections.Generic;
3 using System.ComponentModel;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
11 using CodeProject.Dialog;
12 using System.Drawing.Imaging;
13 using System.ServiceModel;
14 using System.Threading;
15 using System.Diagnostics;
16 using System.Deployment.Application;
17 using System.Reflection;
19 using SharpDisplayClient;
22 namespace SharpDisplayManager
25 public delegate uint ColorProcessingDelegate(int aX, int aY, uint aPixel);
26 public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
27 //Delegates are used for our thread safe method
28 public delegate void AddClientDelegate(string aSessionId, ICallback aCallback);
29 public delegate void RemoveClientDelegate(string aSessionId);
30 public delegate void SetFieldDelegate(string SessionId, DataField aField);
31 public delegate void SetFieldsDelegate(string SessionId, System.Collections.Generic.IList<DataField> aFields);
32 public delegate void SetLayoutDelegate(string SessionId, TableLayout aLayout);
33 public delegate void SetClientNameDelegate(string aSessionId, string aName);
37 /// Our Display manager main form
39 public partial class MainForm : Form
42 DateTime LastTickTime;
44 System.Drawing.Bitmap iBmp;
45 bool iCreateBitmap; //Workaround render to bitmap issues when minimized
46 ServiceHost iServiceHost;
47 // Our collection of clients sorted by session id.
48 public Dictionary<string, ClientData> iClients;
49 // The name of the client which informations are currently displayed.
50 public string iCurrentClientSessionId;
51 ClientData iCurrentClientData;
54 //Function pointer for pixel color filtering
55 ColorProcessingDelegate iColorFx;
56 //Function pointer for pixel X coordinate intercept
57 CoordinateTranslationDelegate iScreenX;
58 //Function pointer for pixel Y coordinate intercept
59 CoordinateTranslationDelegate iScreenY;
62 /// Manage run when Windows startup option
64 private StartupManager iStartupManager;
69 private NotifyIconAdv iNotifyIcon;
73 iCurrentClientSessionId = "";
74 iCurrentClientData = null;
75 LastTickTime = DateTime.Now;
76 iDisplay = new Display();
77 iClients = new Dictionary<string, ClientData>();
78 iStartupManager = new StartupManager();
79 iNotifyIcon = new NotifyIconAdv();
81 InitializeComponent();
83 //We have a bug when drawing minimized and reusing our bitmap
84 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
85 iCreateBitmap = false;
87 if (Properties.Settings.Default.StartMinimized)
89 WindowState = FormWindowState.Minimized;
97 /// <param name="sender"></param>
98 /// <param name="e"></param>
99 private void MainForm_Load(object sender, EventArgs e)
101 if (ApplicationDeployment.IsNetworkDeployed)
103 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
107 this.Text += " - development";
112 if (Properties.Settings.Default.DisplayConnectOnStartup)
114 OpenDisplayConnection();
117 //Check if "run on Windows startup" is enabled
118 checkBoxAutoStart.Checked=iStartupManager.Startup;
121 //Setup notification icon
122 iNotifyIcon.Icon = GetIcon("vfd.ico");
123 iNotifyIcon.Text = "Sharp Display Manager";
124 iNotifyIcon.Visible = true;
125 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
130 // To make sure start up with minimize to tray works
131 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
138 /// Access icons from embedded resources.
140 /// <param name="name"></param>
141 /// <returns></returns>
142 public static Icon GetIcon(string name)
144 name = "SharpDisplayManager.Resources." + name;
147 Assembly.GetExecutingAssembly().GetManifestResourceNames();
148 for (int i = 0; i < names.Length; i++)
150 if (names[i].Replace('\\', '.') == name)
152 using (Stream stream = Assembly.GetExecutingAssembly().
153 GetManifestResourceStream(names[i]))
155 return new Icon(stream);
165 /// Set our current client.
166 /// This will take care of applying our client layout and set data fields.
168 /// <param name="aSessionId"></param>
169 void SetCurrentClient(string aSessionId)
171 if (aSessionId == iCurrentClientSessionId)
173 //Given client is already the current one.
174 //Don't bother changing anything then.
178 //Set current client ID.
179 iCurrentClientSessionId = aSessionId;
180 //Fetch and set current client data.
181 iCurrentClientData = iClients[aSessionId];
182 //Apply layout and set data fields.
183 UpdateTableLayoutPanel(iCurrentClientData);
186 private void buttonFont_Click(object sender, EventArgs e)
188 //fontDialog.ShowColor = true;
189 //fontDialog.ShowApply = true;
190 fontDialog.ShowEffects = true;
191 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[0];
192 fontDialog.Font = label.Font;
194 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
196 //fontDialog.ShowHelp = true;
198 //fontDlg.MaxSize = 40;
199 //fontDlg.MinSize = 22;
201 //fontDialog.Parent = this;
202 //fontDialog.StartPosition = FormStartPosition.CenterParent;
204 //DlgBox.ShowDialog(fontDialog);
206 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
207 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
210 //MsgBox.Show("MessageBox MsgBox", "MsgBox caption");
212 //MessageBox.Show("Ok");
213 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
215 ctrl.Font = fontDialog.Font;
217 cds.Font = fontDialog.Font;
218 Properties.Settings.Default.Save();
227 void CheckFontHeight()
229 //Show font height and width
230 labelFontHeight.Text = "Font height: " + cds.Font.Height;
231 float charWidth = IsFixedWidth(cds.Font);
232 if (charWidth == 0.0f)
234 labelFontWidth.Visible = false;
238 labelFontWidth.Visible = true;
239 labelFontWidth.Text = "Font width: " + charWidth;
242 MarqueeLabel label = null;
243 //Get the first label control we can find
244 foreach (Control ctrl in tableLayoutPanel.Controls)
246 if (ctrl is MarqueeLabel)
248 label = (MarqueeLabel)ctrl;
253 //Now check font height and show a warning if needed.
254 if (label != null && label.Font.Height > label.Height)
256 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
257 labelWarning.Visible = true;
261 labelWarning.Visible = false;
266 private void buttonCapture_Click(object sender, EventArgs e)
268 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
269 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
270 //Bitmap bmpToSave = new Bitmap(bmp);
271 bmp.Save("D:\\capture.png");
273 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
276 string outputFileName = "d:\\capture.png";
277 using (MemoryStream memory = new MemoryStream())
279 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
281 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
282 byte[] bytes = memory.ToArray();
283 fs.Write(bytes, 0, bytes.Length);
290 private void CheckForRequestResults()
292 if (iDisplay.IsRequestPending())
294 switch (iDisplay.AttemptRequestCompletion())
296 case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
297 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
298 //Issue next request then
299 iDisplay.RequestPowerSupplyStatus();
302 case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
303 if (iDisplay.PowerSupplyStatus())
305 toolStripStatusLabelPower.Text = "ON";
309 toolStripStatusLabelPower.Text = "OFF";
311 //Issue next request then
312 iDisplay.RequestDeviceId();
315 case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
316 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
317 //No more request to issue
323 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
325 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
332 public static uint ColorUntouched(int aX, int aY, uint aPixel)
337 public static uint ColorInversed(int aX, int aY, uint aPixel)
342 public static uint ColorChessboard(int aX, int aY, uint aPixel)
344 if ((aX % 2 == 0) && (aY % 2 == 0))
348 else if ((aX % 2 != 0) && (aY % 2 != 0))
356 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
358 return aBmp.Width - aX - 1;
361 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
363 return iBmp.Height - aY - 1;
366 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
371 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
377 /// Select proper pixel delegates according to our current settings.
379 private void SetupPixelDelegates()
381 //Select our pixel processing routine
382 if (cds.InverseColors)
384 //iColorFx = ColorChessboard;
385 iColorFx = ColorInversed;
389 iColorFx = ColorWhiteIsOn;
392 //Select proper coordinate translation functions
393 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
394 if (cds.ReverseScreen)
396 iScreenX = ScreenReversedX;
397 iScreenY = ScreenReversedY;
407 //This is our timer tick responsible to perform our render
408 private void timer_Tick(object sender, EventArgs e)
410 //Update our animations
411 DateTime NewTickTime = DateTime.Now;
413 //Update animation for all our marquees
414 foreach (Control ctrl in tableLayoutPanel.Controls)
416 if (ctrl is MarqueeLabel)
418 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
424 if (iDisplay.IsOpen())
426 CheckForRequestResults();
431 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
433 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
434 //iBmp.Save("D:\\capture.png");
436 //Send it to our display
437 for (int i = 0; i < iBmp.Width; i++)
439 for (int j = 0; j < iBmp.Height; j++)
443 //Get our processed pixel coordinates
444 int x = iScreenX(iBmp, i);
445 int y = iScreenY(iBmp, j);
447 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
448 //Apply color effects
449 color = iColorFx(x,y,color);
451 iDisplay.SetPixel(x, y, color);
456 iDisplay.SwapBuffers();
460 //Compute instant FPS
461 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
463 LastTickTime = NewTickTime;
467 private void OpenDisplayConnection()
469 CloseDisplayConnection();
471 if (iDisplay.Open((Display.TMiniDisplayType)cds.DisplayType))
474 iDisplay.RequestFirmwareRevision();
479 toolStripStatusLabelConnect.Text = "Connection error";
483 private void CloseDisplayConnection()
489 private void buttonOpen_Click(object sender, EventArgs e)
491 OpenDisplayConnection();
494 private void buttonClose_Click(object sender, EventArgs e)
496 CloseDisplayConnection();
499 private void buttonClear_Click(object sender, EventArgs e)
502 iDisplay.SwapBuffers();
505 private void buttonFill_Click(object sender, EventArgs e)
508 iDisplay.SwapBuffers();
511 private void trackBarBrightness_Scroll(object sender, EventArgs e)
513 cds.Brightness = trackBarBrightness.Value;
514 Properties.Settings.Default.Save();
515 iDisplay.SetBrightness(trackBarBrightness.Value);
521 /// CDS stands for Current Display Settings
523 private DisplaySettings cds
527 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
528 if (settings == null)
530 settings = new DisplaysSettings();
532 Properties.Settings.Default.DisplaysSettings = settings;
535 //Make sure all our settings have been created
536 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
538 settings.Displays.Add(new DisplaySettings());
541 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
542 return displaySettings;
547 /// Check if the given font has a fixed character pitch.
549 /// <param name="ft"></param>
550 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
551 public float IsFixedWidth(Font ft)
553 Graphics g = CreateGraphics();
554 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
555 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
557 bool fixedWidth = true;
559 foreach (char c in charSizes)
560 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
571 private void UpdateStatus()
573 //Synchronize UI with settings
575 checkBoxShowBorders.Checked = cds.ShowBorders;
576 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
578 //Set the proper font to each of our labels
579 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
581 ctrl.Font = cds.Font;
585 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
586 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
587 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
588 checkBoxReverseScreen.Checked = cds.ReverseScreen;
589 checkBoxInverseColors.Checked = cds.InverseColors;
590 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
591 timer.Interval = cds.TimerInterval;
592 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
594 SetupPixelDelegates();
596 if (iDisplay.IsOpen())
598 //Only setup brightness if display is open
599 trackBarBrightness.Minimum = iDisplay.MinBrightness();
600 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
601 trackBarBrightness.Value = cds.Brightness;
602 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
603 trackBarBrightness.SmallChange = 1;
604 iDisplay.SetBrightness(cds.Brightness);
606 buttonFill.Enabled = true;
607 buttonClear.Enabled = true;
608 buttonOpen.Enabled = false;
609 buttonClose.Enabled = true;
610 trackBarBrightness.Enabled = true;
611 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
612 //+ " - " + iDisplay.SerialNumber();
614 if (iDisplay.SupportPowerOnOff())
616 buttonPowerOn.Enabled = true;
617 buttonPowerOff.Enabled = true;
621 buttonPowerOn.Enabled = false;
622 buttonPowerOff.Enabled = false;
625 if (iDisplay.SupportClock())
627 buttonShowClock.Enabled = true;
628 buttonHideClock.Enabled = true;
632 buttonShowClock.Enabled = false;
633 buttonHideClock.Enabled = false;
638 buttonFill.Enabled = false;
639 buttonClear.Enabled = false;
640 buttonOpen.Enabled = true;
641 buttonClose.Enabled = false;
642 trackBarBrightness.Enabled = false;
643 buttonPowerOn.Enabled = false;
644 buttonPowerOff.Enabled = false;
645 buttonShowClock.Enabled = false;
646 buttonHideClock.Enabled = false;
647 toolStripStatusLabelConnect.Text = "Disconnected";
648 toolStripStatusLabelPower.Text = "N/A";
654 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
656 //Save our show borders setting
657 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
658 cds.ShowBorders = checkBoxShowBorders.Checked;
659 Properties.Settings.Default.Save();
663 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
665 //Save our connect on startup setting
666 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
667 Properties.Settings.Default.Save();
670 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
672 //Save our "Minimize to tray" setting
673 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
674 Properties.Settings.Default.Save();
678 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
680 //Save our "Start minimized" setting
681 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
682 Properties.Settings.Default.Save();
685 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
687 iStartupManager.Startup = checkBoxAutoStart.Checked;
691 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
693 //Save our reverse screen setting
694 cds.ReverseScreen = checkBoxReverseScreen.Checked;
695 Properties.Settings.Default.Save();
696 SetupPixelDelegates();
699 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
701 //Save our inverse colors setting
702 cds.InverseColors = checkBoxInverseColors.Checked;
703 Properties.Settings.Default.Save();
704 SetupPixelDelegates();
707 private void MainForm_Resize(object sender, EventArgs e)
709 if (WindowState == FormWindowState.Minimized)
712 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
713 iCreateBitmap = true;
717 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
723 public void StartServer()
725 iServiceHost = new ServiceHost
728 new Uri[] { new Uri("net.tcp://localhost:8001/") }
731 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
735 public void StopServer()
737 if (iClients.Count > 0 && !iClosing)
741 BroadcastCloseEvent();
745 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
747 iClosing = false; //We make sure we force close if asked twice
752 //We removed that as it often lags for some reason
753 //iServiceHost.Close();
757 public void BroadcastCloseEvent()
759 Trace.TraceInformation("BroadcastCloseEvent - start");
761 var inactiveClients = new List<string>();
762 foreach (var client in iClients)
764 //if (client.Key != eventData.ClientName)
768 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
769 client.Value.Callback.OnCloseOrder(/*eventData*/);
773 inactiveClients.Add(client.Key);
778 if (inactiveClients.Count > 0)
780 foreach (var client in inactiveClients)
782 iClients.Remove(client);
783 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
788 private void buttonStartClient_Click(object sender, EventArgs e)
790 Thread clientThread = new Thread(SharpDisplayClient.Program.Main);
791 clientThread.Start();
795 private void buttonSuspend_Click(object sender, EventArgs e)
797 LastTickTime = DateTime.Now; //Reset timer to prevent jump
798 timer.Enabled = !timer.Enabled;
801 buttonSuspend.Text = "Run";
805 buttonSuspend.Text = "Pause";
809 private void buttonCloseClients_Click(object sender, EventArgs e)
811 BroadcastCloseEvent();
814 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
823 /// <param name="aSessionId"></param>
824 /// <param name="aCallback"></param>
825 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
827 if (this.InvokeRequired)
829 //Not in the proper thread, invoke ourselves
830 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
831 this.Invoke(d, new object[] { aSessionId, aCallback });
835 //We are in the proper thread
836 //Add this session to our collection of clients
837 ClientData newClient = new ClientData(aSessionId, aCallback);
838 Program.iMainForm.iClients.Add(aSessionId, newClient);
839 //Add this session to our UI
840 UpdateClientTreeViewNode(newClient);
847 /// <param name="aSessionId"></param>
848 public void RemoveClientThreadSafe(string aSessionId)
850 if (this.InvokeRequired)
852 //Not in the proper thread, invoke ourselves
853 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
854 this.Invoke(d, new object[] { aSessionId });
858 //We are in the proper thread
859 //Remove this session from both client collection and UI tree view
860 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
862 Program.iMainForm.iClients.Remove(aSessionId);
863 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
866 if (iClosing && iClients.Count == 0)
868 //We were closing our form
869 //All clients are now closed
870 //Just resume our close operation
880 /// <param name="aSessionId"></param>
881 /// <param name="aLayout"></param>
882 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
884 if (this.InvokeRequired)
886 //Not in the proper thread, invoke ourselves
887 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
888 this.Invoke(d, new object[] { aSessionId, aLayout });
892 ClientData client = iClients[aSessionId];
895 client.Layout = aLayout;
896 UpdateTableLayoutPanel(client);
898 UpdateClientTreeViewNode(client);
906 /// <param name="aSessionId"></param>
907 /// <param name="aField"></param>
908 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
910 if (this.InvokeRequired)
912 //Not in the proper thread, invoke ourselves
913 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
914 this.Invoke(d, new object[] { aSessionId, aField });
918 //We are in the proper thread
919 //Call the non-thread-safe variant
920 SetClientField(aSessionId, aField);
927 /// <param name="aSessionId"></param>
928 /// <param name="aField"></param>
929 private void SetClientField(string aSessionId, DataField aField)
931 SetCurrentClient(aSessionId);
932 ClientData client = iClients[aSessionId];
935 bool somethingChanged = false;
937 //Make sure all our fields are in place
938 while (client.Fields.Count < (aField.Index + 1))
940 //Add a text field with proper index
941 client.Fields.Add(new DataField(client.Fields.Count));
942 somethingChanged = true;
945 if (client.Fields[aField.Index].IsSameLayout(aField))
947 //Same layout just update our field
948 client.Fields[aField.Index] = aField;
950 if (aField.IsText && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
952 //Text field control already in place, just change the text
953 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
954 somethingChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
955 label.Text = aField.Text;
956 label.TextAlign = aField.Alignment;
958 else if (aField.IsBitmap && tableLayoutPanel.Controls[aField.Index] is PictureBox)
960 somethingChanged = true; //TODO: Bitmap comp or should we leave that to clients?
961 //Bitmap field control already in place just change the bitmap
962 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
963 pictureBox.Image = aField.Bitmap;
967 somethingChanged = true;
968 //The requested control in our layout it not of the correct type
969 //Wrong control type, re-create them all
970 UpdateTableLayoutPanel(iCurrentClientData);
975 somethingChanged = true;
976 //Different layout, need to rebuild it
977 client.Fields[aField.Index] = aField;
978 UpdateTableLayoutPanel(iCurrentClientData);
982 if (somethingChanged)
984 UpdateClientTreeViewNode(client);
992 /// <param name="aTexts"></param>
993 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
995 if (this.InvokeRequired)
997 //Not in the proper thread, invoke ourselves
998 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
999 this.Invoke(d, new object[] { aSessionId, aFields });
1003 //Put each our text fields in a label control
1004 foreach (DataField field in aFields)
1006 SetClientField(aSessionId, field);
1014 /// <param name="aSessionId"></param>
1015 /// <param name="aName"></param>
1016 public void SetClientNameThreadSafe(string aSessionId, string aName)
1018 if (this.InvokeRequired)
1020 //Not in the proper thread, invoke ourselves
1021 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1022 this.Invoke(d, new object[] { aSessionId, aName });
1026 //We are in the proper thread
1028 ClientData client = iClients[aSessionId];
1032 client.Name = aName;
1033 //Update our tree-view
1034 UpdateClientTreeViewNode(client);
1042 /// <param name="aClient"></param>
1043 private void UpdateClientTreeViewNode(ClientData aClient)
1045 if (aClient == null)
1050 TreeNode node = null;
1051 //Check that our client node already exists
1052 //Get our client root node using its key which is our session ID
1053 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1054 if (nodes.Count()>0)
1056 //We already have a node for that client
1058 //Clear children as we are going to recreate them below
1063 //Client node does not exists create a new one
1064 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1065 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1071 if (aClient.Name != "")
1073 //We have a name, us it as text for our root node
1074 node.Text = aClient.Name;
1075 //Add a child with SessionId
1076 node.Nodes.Add(new TreeNode(aClient.SessionId));
1080 //No name, use session ID instead
1081 node.Text = aClient.SessionId;
1084 if (aClient.Fields.Count > 0)
1086 //Create root node for our texts
1087 TreeNode textsRoot = new TreeNode("Fields");
1088 node.Nodes.Add(textsRoot);
1089 //For each text add a new entry
1090 foreach (DataField field in aClient.Fields)
1092 if (!field.IsBitmap)
1094 DataField textField = (DataField)field;
1095 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1099 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1108 private void buttonAddRow_Click(object sender, EventArgs e)
1110 if (tableLayoutPanel.RowCount < 6)
1112 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount + 1);
1116 private void buttonRemoveRow_Click(object sender, EventArgs e)
1118 if (tableLayoutPanel.RowCount > 1)
1120 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount - 1);
1123 UpdateTableLayoutRowStyles();
1126 private void buttonAddColumn_Click(object sender, EventArgs e)
1128 if (tableLayoutPanel.ColumnCount < 8)
1130 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount + 1, tableLayoutPanel.RowCount);
1134 private void buttonRemoveColumn_Click(object sender, EventArgs e)
1136 if (tableLayoutPanel.ColumnCount > 1)
1138 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount - 1, tableLayoutPanel.RowCount);
1144 /// Update our table layout row styles to make sure each rows have similar height
1146 private void UpdateTableLayoutRowStyles()
1148 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1150 rowStyle.SizeType = SizeType.Percent;
1151 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1157 /// Empty and recreate our table layout with the given number of columns and rows.
1158 /// Sizes of rows and columns are uniform.
1160 /// <param name="aColumn"></param>
1161 /// <param name="aRow"></param>
1162 private void UpdateTableLayoutPanel(int aColumn, int aRow)
1164 tableLayoutPanel.Controls.Clear();
1165 tableLayoutPanel.RowStyles.Clear();
1166 tableLayoutPanel.ColumnStyles.Clear();
1167 tableLayoutPanel.RowCount = 0;
1168 tableLayoutPanel.ColumnCount = 0;
1170 while (tableLayoutPanel.RowCount < aRow)
1172 tableLayoutPanel.RowCount++;
1175 while (tableLayoutPanel.ColumnCount < aColumn)
1177 tableLayoutPanel.ColumnCount++;
1180 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1182 //Create our column styles
1183 this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.ColumnCount));
1185 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1189 //Create our row styles
1190 this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.RowCount));
1193 MarqueeLabel control = new SharpDisplayManager.MarqueeLabel();
1194 control.AutoEllipsis = true;
1195 control.AutoSize = true;
1196 control.BackColor = System.Drawing.Color.Transparent;
1197 control.Dock = System.Windows.Forms.DockStyle.Fill;
1198 control.Location = new System.Drawing.Point(1, 1);
1199 control.Margin = new System.Windows.Forms.Padding(0);
1200 control.Name = "marqueeLabelCol" + aColumn + "Row" + aRow;
1201 control.OwnTimer = false;
1202 control.PixelsPerSecond = 64;
1203 control.Separator = "|";
1204 //control.Size = new System.Drawing.Size(254, 30);
1205 //control.TabIndex = 2;
1206 control.Font = cds.Font;
1207 control.Text = "ABCDEFGHIJKLMNOPQRST-0123456789";
1208 control.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1209 control.UseCompatibleTextRendering = true;
1211 tableLayoutPanel.Controls.Add(control, i, j);
1220 /// Update our display table layout.
1222 /// <param name="aLayout"></param>
1223 private void UpdateTableLayoutPanel(ClientData aClient)
1225 TableLayout layout = aClient.Layout;
1228 tableLayoutPanel.Controls.Clear();
1229 tableLayoutPanel.RowStyles.Clear();
1230 tableLayoutPanel.ColumnStyles.Clear();
1231 tableLayoutPanel.RowCount = 0;
1232 tableLayoutPanel.ColumnCount = 0;
1234 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1236 tableLayoutPanel.RowCount++;
1239 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1241 tableLayoutPanel.ColumnCount++;
1244 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1246 //Create our column styles
1247 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1249 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1253 //Create our row styles
1254 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1257 //Check if we already have a control
1258 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1259 if (existingControl!=null)
1261 //We already have a control in that cell as a results of row/col spanning
1262 //Move on to next cell then
1268 //Check if a client field already exists for that cell
1269 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1271 //No client field specified, create a text field by default
1272 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1275 //Create a control corresponding to the field specified for that cell
1276 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1277 Control control = CreateControlForDataField(field);
1279 //Add newly created control to our table layout at the specified row and column
1280 tableLayoutPanel.Controls.Add(control, i, j);
1281 //Make sure we specify row and column span for that new control
1282 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1283 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1288 while (aClient.Fields.Count > fieldCount)
1290 //We have too much fields for this layout
1291 //Just discard them until we get there
1292 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1299 /// Check our type of data field and create corresponding control
1301 /// <param name="aField"></param>
1302 private Control CreateControlForDataField(DataField aField)
1304 Control control=null;
1305 if (!aField.IsBitmap)
1307 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1308 label.AutoEllipsis = true;
1309 label.AutoSize = true;
1310 label.BackColor = System.Drawing.Color.Transparent;
1311 label.Dock = System.Windows.Forms.DockStyle.Fill;
1312 label.Location = new System.Drawing.Point(1, 1);
1313 label.Margin = new System.Windows.Forms.Padding(0);
1314 label.Name = "marqueeLabel" + aField.Index;
1315 label.OwnTimer = false;
1316 label.PixelsPerSecond = 64;
1317 label.Separator = "|";
1318 //control.Size = new System.Drawing.Size(254, 30);
1319 //control.TabIndex = 2;
1320 label.Font = cds.Font;
1322 label.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1323 label.UseCompatibleTextRendering = true;
1324 label.Text = aField.Text;
1330 //Create picture box
1331 PictureBox picture = new PictureBox();
1332 picture.AutoSize = true;
1333 picture.BackColor = System.Drawing.Color.Transparent;
1334 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1335 picture.Location = new System.Drawing.Point(1, 1);
1336 picture.Margin = new System.Windows.Forms.Padding(0);
1337 picture.Name = "pictureBox" + aField;
1339 picture.Image = aField.Bitmap;
1348 private void buttonAlignLeft_Click(object sender, EventArgs e)
1350 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1352 ctrl.TextAlign = ContentAlignment.MiddleLeft;
1356 private void buttonAlignCenter_Click(object sender, EventArgs e)
1358 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1360 ctrl.TextAlign = ContentAlignment.MiddleCenter;
1364 private void buttonAlignRight_Click(object sender, EventArgs e)
1366 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1368 ctrl.TextAlign = ContentAlignment.MiddleRight;
1372 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1374 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1375 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1376 Properties.Settings.Default.Save();
1377 if (iDisplay.IsOpen())
1379 OpenDisplayConnection();
1388 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1390 if (maskedTextBoxTimerInterval.Text != "")
1392 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1396 timer.Interval = interval;
1397 cds.TimerInterval = timer.Interval;
1398 Properties.Settings.Default.Save();
1403 private void buttonPowerOn_Click(object sender, EventArgs e)
1408 private void buttonPowerOff_Click(object sender, EventArgs e)
1410 iDisplay.PowerOff();
1413 private void buttonShowClock_Click(object sender, EventArgs e)
1415 iDisplay.ShowClock();
1418 private void buttonHideClock_Click(object sender, EventArgs e)
1420 iDisplay.HideClock();
1423 private void buttonUpdate_Click(object sender, EventArgs e)
1425 InstallUpdateSyncWithInfo();
1429 private void InstallUpdateSyncWithInfo()
1431 UpdateCheckInfo info = null;
1433 if (ApplicationDeployment.IsNetworkDeployed)
1435 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
1439 info = ad.CheckForDetailedUpdate();
1442 catch (DeploymentDownloadException dde)
1444 MessageBox.Show("The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " + dde.Message);
1447 catch (InvalidDeploymentException ide)
1449 MessageBox.Show("Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " + ide.Message);
1452 catch (InvalidOperationException ioe)
1454 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
1458 if (info.UpdateAvailable)
1460 Boolean doUpdate = true;
1462 if (!info.IsUpdateRequired)
1464 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
1465 if (!(DialogResult.OK == dr))
1472 // Display a message that the app MUST reboot. Display the minimum required version.
1473 MessageBox.Show("This application has detected a mandatory update from your current " +
1474 "version to version " + info.MinimumRequiredVersion.ToString() +
1475 ". The application will now install the update and restart.",
1476 "Update Available", MessageBoxButtons.OK,
1477 MessageBoxIcon.Information);
1485 MessageBox.Show("The application has been upgraded, and will now restart.");
1486 Application.Restart();
1488 catch (DeploymentDownloadException dde)
1490 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
1497 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
1506 private void SysTrayHideShow()
1512 WindowState = FormWindowState.Normal;
1517 /// Use to handle minimize events.
1519 /// <param name="sender"></param>
1520 /// <param name="e"></param>
1521 private void MainForm_SizeChanged(object sender, EventArgs e)
1523 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
1536 /// A UI thread copy of a client relevant data.
1537 /// Keeping this copy in the UI thread helps us deal with threading issues.
1539 public class ClientData
1541 public ClientData(string aSessionId, ICallback aCallback)
1543 SessionId = aSessionId;
1545 Fields = new List<DataField>();
1546 Layout = new TableLayout(1, 2); //Default to one column and two rows
1547 Callback = aCallback;
1550 public string SessionId { get; set; }
1551 public string Name { get; set; }
1552 public List<DataField> Fields { get; set; }
1553 public TableLayout Layout { get; set; }
1554 public ICallback Callback { get; set; }