Clear the screen on startup if not in debug mode.
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
41 DateTime LastTickTime;
43 System.Drawing.Bitmap iBmp;
44 bool iCreateBitmap; //Workaround render to bitmap issues when minimized
45 ServiceHost iServiceHost;
46 // Our collection of clients sorted by session id.
47 public Dictionary<string, ClientData> iClients;
48 // The name of the client which informations are currently displayed.
49 public string iCurrentClientSessionId;
50 ClientData iCurrentClientData;
53 //Function pointer for pixel color filtering
54 ColorProcessingDelegate iColorFx;
55 //Function pointer for pixel X coordinate intercept
56 CoordinateTranslationDelegate iScreenX;
57 //Function pointer for pixel Y coordinate intercept
58 CoordinateTranslationDelegate iScreenY;
61 /// Manage run when Windows startup option
63 private StartupManager iStartupManager;
68 private NotifyIconAdv iNotifyIcon;
72 iCurrentClientSessionId = "";
73 iCurrentClientData = null;
74 LastTickTime = DateTime.Now;
75 iDisplay = new Display();
76 iClients = new Dictionary<string, ClientData>();
77 iStartupManager = new StartupManager();
78 iNotifyIcon = new NotifyIconAdv();
80 InitializeComponent();
82 //We have a bug when drawing minimized and reusing our bitmap
83 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
84 iCreateBitmap = false;
86 if (Properties.Settings.Default.StartMinimized)
88 WindowState = FormWindowState.Minimized;
96 /// <param name="sender"></param>
97 /// <param name="e"></param>
98 private void MainForm_Load(object sender, EventArgs e)
100 if (ApplicationDeployment.IsNetworkDeployed)
102 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
106 this.Text += " - development";
111 if (Properties.Settings.Default.DisplayConnectOnStartup)
113 OpenDisplayConnection();
116 //Setup notification icon
119 // To make sure start up with minimize to tray works
120 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
126 //When not debugging we want the screen to be empty until a client takes over
127 tableLayoutPanel.Controls.Clear();
128 tableLayoutPanel.RowStyles.Clear();
129 tableLayoutPanel.ColumnStyles.Clear();
136 private void SetupTrayIcon()
138 iNotifyIcon.Icon = GetIcon("vfd.ico");
139 iNotifyIcon.Text = "Sharp Display Manager";
140 iNotifyIcon.Visible = true;
142 //Double click toggles visibility - typically brings up the application
143 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
148 //Adding a context menu, useful to be able to exit the application
149 ContextMenu contextMenu = new ContextMenu();
150 //Context menu item to toggle visibility
151 MenuItem hideShowItem = new MenuItem("Hide/Show");
152 hideShowItem.Click += delegate(object obj, EventArgs args)
156 contextMenu.MenuItems.Add(hideShowItem);
158 //Context menu item separator
159 contextMenu.MenuItems.Add(new MenuItem("-"));
161 //Context menu exit item
162 MenuItem exitItem = new MenuItem("Exit");
163 exitItem.Click += delegate(object obj, EventArgs args)
167 contextMenu.MenuItems.Add(exitItem);
169 iNotifyIcon.ContextMenu = contextMenu;
173 /// Access icons from embedded resources.
175 /// <param name="name"></param>
176 /// <returns></returns>
177 public static Icon GetIcon(string name)
179 name = "SharpDisplayManager.Resources." + name;
182 Assembly.GetExecutingAssembly().GetManifestResourceNames();
183 for (int i = 0; i < names.Length; i++)
185 if (names[i].Replace('\\', '.') == name)
187 using (Stream stream = Assembly.GetExecutingAssembly().
188 GetManifestResourceStream(names[i]))
190 return new Icon(stream);
200 /// Set our current client.
201 /// This will take care of applying our client layout and set data fields.
203 /// <param name="aSessionId"></param>
204 void SetCurrentClient(string aSessionId)
206 if (aSessionId == iCurrentClientSessionId)
208 //Given client is already the current one.
209 //Don't bother changing anything then.
213 //Set current client ID.
214 iCurrentClientSessionId = aSessionId;
215 //Fetch and set current client data.
216 iCurrentClientData = iClients[aSessionId];
217 //Apply layout and set data fields.
218 UpdateTableLayoutPanel(iCurrentClientData);
221 private void buttonFont_Click(object sender, EventArgs e)
223 //fontDialog.ShowColor = true;
224 //fontDialog.ShowApply = true;
225 fontDialog.ShowEffects = true;
226 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[0];
227 fontDialog.Font = label.Font;
229 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
231 //fontDialog.ShowHelp = true;
233 //fontDlg.MaxSize = 40;
234 //fontDlg.MinSize = 22;
236 //fontDialog.Parent = this;
237 //fontDialog.StartPosition = FormStartPosition.CenterParent;
239 //DlgBox.ShowDialog(fontDialog);
241 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
242 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
245 //MsgBox.Show("MessageBox MsgBox", "MsgBox caption");
247 //MessageBox.Show("Ok");
248 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
250 ctrl.Font = fontDialog.Font;
252 cds.Font = fontDialog.Font;
253 Properties.Settings.Default.Save();
262 void CheckFontHeight()
264 //Show font height and width
265 labelFontHeight.Text = "Font height: " + cds.Font.Height;
266 float charWidth = IsFixedWidth(cds.Font);
267 if (charWidth == 0.0f)
269 labelFontWidth.Visible = false;
273 labelFontWidth.Visible = true;
274 labelFontWidth.Text = "Font width: " + charWidth;
277 MarqueeLabel label = null;
278 //Get the first label control we can find
279 foreach (Control ctrl in tableLayoutPanel.Controls)
281 if (ctrl is MarqueeLabel)
283 label = (MarqueeLabel)ctrl;
288 //Now check font height and show a warning if needed.
289 if (label != null && label.Font.Height > label.Height)
291 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
292 labelWarning.Visible = true;
296 labelWarning.Visible = false;
301 private void buttonCapture_Click(object sender, EventArgs e)
303 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
304 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
305 //Bitmap bmpToSave = new Bitmap(bmp);
306 bmp.Save("D:\\capture.png");
308 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
311 string outputFileName = "d:\\capture.png";
312 using (MemoryStream memory = new MemoryStream())
314 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
316 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
317 byte[] bytes = memory.ToArray();
318 fs.Write(bytes, 0, bytes.Length);
325 private void CheckForRequestResults()
327 if (iDisplay.IsRequestPending())
329 switch (iDisplay.AttemptRequestCompletion())
331 case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
332 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
333 //Issue next request then
334 iDisplay.RequestPowerSupplyStatus();
337 case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
338 if (iDisplay.PowerSupplyStatus())
340 toolStripStatusLabelPower.Text = "ON";
344 toolStripStatusLabelPower.Text = "OFF";
346 //Issue next request then
347 iDisplay.RequestDeviceId();
350 case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
351 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
352 //No more request to issue
358 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
360 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
367 public static uint ColorUntouched(int aX, int aY, uint aPixel)
372 public static uint ColorInversed(int aX, int aY, uint aPixel)
377 public static uint ColorChessboard(int aX, int aY, uint aPixel)
379 if ((aX % 2 == 0) && (aY % 2 == 0))
383 else if ((aX % 2 != 0) && (aY % 2 != 0))
391 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
393 return aBmp.Width - aX - 1;
396 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
398 return iBmp.Height - aY - 1;
401 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
406 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
412 /// Select proper pixel delegates according to our current settings.
414 private void SetupPixelDelegates()
416 //Select our pixel processing routine
417 if (cds.InverseColors)
419 //iColorFx = ColorChessboard;
420 iColorFx = ColorInversed;
424 iColorFx = ColorWhiteIsOn;
427 //Select proper coordinate translation functions
428 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
429 if (cds.ReverseScreen)
431 iScreenX = ScreenReversedX;
432 iScreenY = ScreenReversedY;
442 //This is our timer tick responsible to perform our render
443 private void timer_Tick(object sender, EventArgs e)
445 //Update our animations
446 DateTime NewTickTime = DateTime.Now;
448 //Update animation for all our marquees
449 foreach (Control ctrl in tableLayoutPanel.Controls)
451 if (ctrl is MarqueeLabel)
453 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
459 if (iDisplay.IsOpen())
461 CheckForRequestResults();
466 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
468 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
469 //iBmp.Save("D:\\capture.png");
471 //Send it to our display
472 for (int i = 0; i < iBmp.Width; i++)
474 for (int j = 0; j < iBmp.Height; j++)
478 //Get our processed pixel coordinates
479 int x = iScreenX(iBmp, i);
480 int y = iScreenY(iBmp, j);
482 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
483 //Apply color effects
484 color = iColorFx(x,y,color);
486 iDisplay.SetPixel(x, y, color);
491 iDisplay.SwapBuffers();
495 //Compute instant FPS
496 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
498 LastTickTime = NewTickTime;
502 private void OpenDisplayConnection()
504 CloseDisplayConnection();
506 if (iDisplay.Open((Display.TMiniDisplayType)cds.DisplayType))
509 iDisplay.RequestFirmwareRevision();
514 toolStripStatusLabelConnect.Text = "Connection error";
518 private void CloseDisplayConnection()
524 private void buttonOpen_Click(object sender, EventArgs e)
526 OpenDisplayConnection();
529 private void buttonClose_Click(object sender, EventArgs e)
531 CloseDisplayConnection();
534 private void buttonClear_Click(object sender, EventArgs e)
537 iDisplay.SwapBuffers();
540 private void buttonFill_Click(object sender, EventArgs e)
543 iDisplay.SwapBuffers();
546 private void trackBarBrightness_Scroll(object sender, EventArgs e)
548 cds.Brightness = trackBarBrightness.Value;
549 Properties.Settings.Default.Save();
550 iDisplay.SetBrightness(trackBarBrightness.Value);
556 /// CDS stands for Current Display Settings
558 private DisplaySettings cds
562 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
563 if (settings == null)
565 settings = new DisplaysSettings();
567 Properties.Settings.Default.DisplaysSettings = settings;
570 //Make sure all our settings have been created
571 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
573 settings.Displays.Add(new DisplaySettings());
576 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
577 return displaySettings;
582 /// Check if the given font has a fixed character pitch.
584 /// <param name="ft"></param>
585 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
586 public float IsFixedWidth(Font ft)
588 Graphics g = CreateGraphics();
589 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
590 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
592 bool fixedWidth = true;
594 foreach (char c in charSizes)
595 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
606 private void UpdateStatus()
608 //Synchronize UI with settings
610 checkBoxShowBorders.Checked = cds.ShowBorders;
611 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
613 //Set the proper font to each of our labels
614 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
616 ctrl.Font = cds.Font;
620 //Check if "run on Windows startup" is enabled
621 checkBoxAutoStart.Checked = iStartupManager.Startup;
623 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
624 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
625 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
626 checkBoxReverseScreen.Checked = cds.ReverseScreen;
627 checkBoxInverseColors.Checked = cds.InverseColors;
628 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
629 timer.Interval = cds.TimerInterval;
630 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
632 SetupPixelDelegates();
634 if (iDisplay.IsOpen())
636 //Only setup brightness if display is open
637 trackBarBrightness.Minimum = iDisplay.MinBrightness();
638 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
639 trackBarBrightness.Value = cds.Brightness;
640 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
641 trackBarBrightness.SmallChange = 1;
642 iDisplay.SetBrightness(cds.Brightness);
644 buttonFill.Enabled = true;
645 buttonClear.Enabled = true;
646 buttonOpen.Enabled = false;
647 buttonClose.Enabled = true;
648 trackBarBrightness.Enabled = true;
649 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
650 //+ " - " + iDisplay.SerialNumber();
652 if (iDisplay.SupportPowerOnOff())
654 buttonPowerOn.Enabled = true;
655 buttonPowerOff.Enabled = true;
659 buttonPowerOn.Enabled = false;
660 buttonPowerOff.Enabled = false;
663 if (iDisplay.SupportClock())
665 buttonShowClock.Enabled = true;
666 buttonHideClock.Enabled = true;
670 buttonShowClock.Enabled = false;
671 buttonHideClock.Enabled = false;
676 buttonFill.Enabled = false;
677 buttonClear.Enabled = false;
678 buttonOpen.Enabled = true;
679 buttonClose.Enabled = false;
680 trackBarBrightness.Enabled = false;
681 buttonPowerOn.Enabled = false;
682 buttonPowerOff.Enabled = false;
683 buttonShowClock.Enabled = false;
684 buttonHideClock.Enabled = false;
685 toolStripStatusLabelConnect.Text = "Disconnected";
686 toolStripStatusLabelPower.Text = "N/A";
692 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
694 //Save our show borders setting
695 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
696 cds.ShowBorders = checkBoxShowBorders.Checked;
697 Properties.Settings.Default.Save();
701 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
703 //Save our connect on startup setting
704 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
705 Properties.Settings.Default.Save();
708 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
710 //Save our "Minimize to tray" setting
711 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
712 Properties.Settings.Default.Save();
716 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
718 //Save our "Start minimized" setting
719 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
720 Properties.Settings.Default.Save();
723 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
725 iStartupManager.Startup = checkBoxAutoStart.Checked;
729 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
731 //Save our reverse screen setting
732 cds.ReverseScreen = checkBoxReverseScreen.Checked;
733 Properties.Settings.Default.Save();
734 SetupPixelDelegates();
737 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
739 //Save our inverse colors setting
740 cds.InverseColors = checkBoxInverseColors.Checked;
741 Properties.Settings.Default.Save();
742 SetupPixelDelegates();
745 private void MainForm_Resize(object sender, EventArgs e)
747 if (WindowState == FormWindowState.Minimized)
750 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
751 iCreateBitmap = true;
755 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
761 public void StartServer()
763 iServiceHost = new ServiceHost
766 new Uri[] { new Uri("net.tcp://localhost:8001/") }
769 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
773 public void StopServer()
775 if (iClients.Count > 0 && !iClosing)
779 BroadcastCloseEvent();
783 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
785 iClosing = false; //We make sure we force close if asked twice
790 //We removed that as it often lags for some reason
791 //iServiceHost.Close();
795 public void BroadcastCloseEvent()
797 Trace.TraceInformation("BroadcastCloseEvent - start");
799 var inactiveClients = new List<string>();
800 foreach (var client in iClients)
802 //if (client.Key != eventData.ClientName)
806 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
807 client.Value.Callback.OnCloseOrder(/*eventData*/);
811 inactiveClients.Add(client.Key);
816 if (inactiveClients.Count > 0)
818 foreach (var client in inactiveClients)
820 iClients.Remove(client);
821 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
826 private void buttonStartClient_Click(object sender, EventArgs e)
828 Thread clientThread = new Thread(SharpDisplayClient.Program.Main);
829 clientThread.Start();
833 private void buttonSuspend_Click(object sender, EventArgs e)
835 LastTickTime = DateTime.Now; //Reset timer to prevent jump
836 timer.Enabled = !timer.Enabled;
839 buttonSuspend.Text = "Run";
843 buttonSuspend.Text = "Pause";
847 private void buttonCloseClients_Click(object sender, EventArgs e)
849 BroadcastCloseEvent();
852 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
861 /// <param name="aSessionId"></param>
862 /// <param name="aCallback"></param>
863 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
865 if (this.InvokeRequired)
867 //Not in the proper thread, invoke ourselves
868 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
869 this.Invoke(d, new object[] { aSessionId, aCallback });
873 //We are in the proper thread
874 //Add this session to our collection of clients
875 ClientData newClient = new ClientData(aSessionId, aCallback);
876 Program.iMainForm.iClients.Add(aSessionId, newClient);
877 //Add this session to our UI
878 UpdateClientTreeViewNode(newClient);
885 /// <param name="aSessionId"></param>
886 public void RemoveClientThreadSafe(string aSessionId)
888 if (this.InvokeRequired)
890 //Not in the proper thread, invoke ourselves
891 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
892 this.Invoke(d, new object[] { aSessionId });
896 //We are in the proper thread
897 //Remove this session from both client collection and UI tree view
898 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
900 Program.iMainForm.iClients.Remove(aSessionId);
901 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
904 if (iClosing && iClients.Count == 0)
906 //We were closing our form
907 //All clients are now closed
908 //Just resume our close operation
918 /// <param name="aSessionId"></param>
919 /// <param name="aLayout"></param>
920 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
922 if (this.InvokeRequired)
924 //Not in the proper thread, invoke ourselves
925 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
926 this.Invoke(d, new object[] { aSessionId, aLayout });
930 ClientData client = iClients[aSessionId];
933 client.Layout = aLayout;
934 UpdateTableLayoutPanel(client);
936 UpdateClientTreeViewNode(client);
944 /// <param name="aSessionId"></param>
945 /// <param name="aField"></param>
946 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
948 if (this.InvokeRequired)
950 //Not in the proper thread, invoke ourselves
951 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
952 this.Invoke(d, new object[] { aSessionId, aField });
956 //We are in the proper thread
957 //Call the non-thread-safe variant
958 SetClientField(aSessionId, aField);
965 /// <param name="aSessionId"></param>
966 /// <param name="aField"></param>
967 private void SetClientField(string aSessionId, DataField aField)
969 SetCurrentClient(aSessionId);
970 ClientData client = iClients[aSessionId];
973 bool somethingChanged = false;
975 //Make sure all our fields are in place
976 while (client.Fields.Count < (aField.Index + 1))
978 //Add a text field with proper index
979 client.Fields.Add(new DataField(client.Fields.Count));
980 somethingChanged = true;
983 if (client.Fields[aField.Index].IsSameLayout(aField))
985 //Same layout just update our field
986 client.Fields[aField.Index] = aField;
988 if (aField.IsText && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
990 //Text field control already in place, just change the text
991 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
992 somethingChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
993 label.Text = aField.Text;
994 label.TextAlign = aField.Alignment;
996 else if (aField.IsBitmap && tableLayoutPanel.Controls[aField.Index] is PictureBox)
998 somethingChanged = true; //TODO: Bitmap comp or should we leave that to clients?
999 //Bitmap field control already in place just change the bitmap
1000 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
1001 pictureBox.Image = aField.Bitmap;
1005 somethingChanged = true;
1006 //The requested control in our layout it not of the correct type
1007 //Wrong control type, re-create them all
1008 UpdateTableLayoutPanel(iCurrentClientData);
1013 somethingChanged = true;
1014 //Different layout, need to rebuild it
1015 client.Fields[aField.Index] = aField;
1016 UpdateTableLayoutPanel(iCurrentClientData);
1020 if (somethingChanged)
1022 UpdateClientTreeViewNode(client);
1030 /// <param name="aTexts"></param>
1031 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1033 if (this.InvokeRequired)
1035 //Not in the proper thread, invoke ourselves
1036 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1037 this.Invoke(d, new object[] { aSessionId, aFields });
1041 //Put each our text fields in a label control
1042 foreach (DataField field in aFields)
1044 SetClientField(aSessionId, field);
1052 /// <param name="aSessionId"></param>
1053 /// <param name="aName"></param>
1054 public void SetClientNameThreadSafe(string aSessionId, string aName)
1056 if (this.InvokeRequired)
1058 //Not in the proper thread, invoke ourselves
1059 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1060 this.Invoke(d, new object[] { aSessionId, aName });
1064 //We are in the proper thread
1066 ClientData client = iClients[aSessionId];
1070 client.Name = aName;
1071 //Update our tree-view
1072 UpdateClientTreeViewNode(client);
1080 /// <param name="aClient"></param>
1081 private void UpdateClientTreeViewNode(ClientData aClient)
1083 if (aClient == null)
1088 TreeNode node = null;
1089 //Check that our client node already exists
1090 //Get our client root node using its key which is our session ID
1091 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1092 if (nodes.Count()>0)
1094 //We already have a node for that client
1096 //Clear children as we are going to recreate them below
1101 //Client node does not exists create a new one
1102 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1103 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1109 if (aClient.Name != "")
1111 //We have a name, us it as text for our root node
1112 node.Text = aClient.Name;
1113 //Add a child with SessionId
1114 node.Nodes.Add(new TreeNode(aClient.SessionId));
1118 //No name, use session ID instead
1119 node.Text = aClient.SessionId;
1122 if (aClient.Fields.Count > 0)
1124 //Create root node for our texts
1125 TreeNode textsRoot = new TreeNode("Fields");
1126 node.Nodes.Add(textsRoot);
1127 //For each text add a new entry
1128 foreach (DataField field in aClient.Fields)
1130 if (!field.IsBitmap)
1132 DataField textField = (DataField)field;
1133 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1137 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1146 private void buttonAddRow_Click(object sender, EventArgs e)
1148 if (tableLayoutPanel.RowCount < 6)
1150 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount + 1);
1154 private void buttonRemoveRow_Click(object sender, EventArgs e)
1156 if (tableLayoutPanel.RowCount > 1)
1158 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount - 1);
1161 UpdateTableLayoutRowStyles();
1164 private void buttonAddColumn_Click(object sender, EventArgs e)
1166 if (tableLayoutPanel.ColumnCount < 8)
1168 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount + 1, tableLayoutPanel.RowCount);
1172 private void buttonRemoveColumn_Click(object sender, EventArgs e)
1174 if (tableLayoutPanel.ColumnCount > 1)
1176 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount - 1, tableLayoutPanel.RowCount);
1182 /// Update our table layout row styles to make sure each rows have similar height
1184 private void UpdateTableLayoutRowStyles()
1186 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1188 rowStyle.SizeType = SizeType.Percent;
1189 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1195 /// Empty and recreate our table layout with the given number of columns and rows.
1196 /// Sizes of rows and columns are uniform.
1198 /// <param name="aColumn"></param>
1199 /// <param name="aRow"></param>
1200 private void UpdateTableLayoutPanel(int aColumn, int aRow)
1202 tableLayoutPanel.Controls.Clear();
1203 tableLayoutPanel.RowStyles.Clear();
1204 tableLayoutPanel.ColumnStyles.Clear();
1205 tableLayoutPanel.RowCount = 0;
1206 tableLayoutPanel.ColumnCount = 0;
1208 while (tableLayoutPanel.RowCount < aRow)
1210 tableLayoutPanel.RowCount++;
1213 while (tableLayoutPanel.ColumnCount < aColumn)
1215 tableLayoutPanel.ColumnCount++;
1218 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1220 //Create our column styles
1221 this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.ColumnCount));
1223 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1227 //Create our row styles
1228 this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.RowCount));
1231 MarqueeLabel control = new SharpDisplayManager.MarqueeLabel();
1232 control.AutoEllipsis = true;
1233 control.AutoSize = true;
1234 control.BackColor = System.Drawing.Color.Transparent;
1235 control.Dock = System.Windows.Forms.DockStyle.Fill;
1236 control.Location = new System.Drawing.Point(1, 1);
1237 control.Margin = new System.Windows.Forms.Padding(0);
1238 control.Name = "marqueeLabelCol" + aColumn + "Row" + aRow;
1239 control.OwnTimer = false;
1240 control.PixelsPerSecond = 64;
1241 control.Separator = "|";
1242 //control.Size = new System.Drawing.Size(254, 30);
1243 //control.TabIndex = 2;
1244 control.Font = cds.Font;
1245 control.Text = "ABCDEFGHIJKLMNOPQRST-0123456789";
1246 control.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1247 control.UseCompatibleTextRendering = true;
1249 tableLayoutPanel.Controls.Add(control, i, j);
1258 /// Update our display table layout.
1260 /// <param name="aLayout"></param>
1261 private void UpdateTableLayoutPanel(ClientData aClient)
1263 TableLayout layout = aClient.Layout;
1266 tableLayoutPanel.Controls.Clear();
1267 tableLayoutPanel.RowStyles.Clear();
1268 tableLayoutPanel.ColumnStyles.Clear();
1269 tableLayoutPanel.RowCount = 0;
1270 tableLayoutPanel.ColumnCount = 0;
1272 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1274 tableLayoutPanel.RowCount++;
1277 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1279 tableLayoutPanel.ColumnCount++;
1282 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1284 //Create our column styles
1285 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1287 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1291 //Create our row styles
1292 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1295 //Check if we already have a control
1296 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1297 if (existingControl!=null)
1299 //We already have a control in that cell as a results of row/col spanning
1300 //Move on to next cell then
1306 //Check if a client field already exists for that cell
1307 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1309 //No client field specified, create a text field by default
1310 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1313 //Create a control corresponding to the field specified for that cell
1314 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1315 Control control = CreateControlForDataField(field);
1317 //Add newly created control to our table layout at the specified row and column
1318 tableLayoutPanel.Controls.Add(control, i, j);
1319 //Make sure we specify row and column span for that new control
1320 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1321 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1326 while (aClient.Fields.Count > fieldCount)
1328 //We have too much fields for this layout
1329 //Just discard them until we get there
1330 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1337 /// Check our type of data field and create corresponding control
1339 /// <param name="aField"></param>
1340 private Control CreateControlForDataField(DataField aField)
1342 Control control=null;
1343 if (!aField.IsBitmap)
1345 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1346 label.AutoEllipsis = true;
1347 label.AutoSize = true;
1348 label.BackColor = System.Drawing.Color.Transparent;
1349 label.Dock = System.Windows.Forms.DockStyle.Fill;
1350 label.Location = new System.Drawing.Point(1, 1);
1351 label.Margin = new System.Windows.Forms.Padding(0);
1352 label.Name = "marqueeLabel" + aField.Index;
1353 label.OwnTimer = false;
1354 label.PixelsPerSecond = 64;
1355 label.Separator = "|";
1356 //control.Size = new System.Drawing.Size(254, 30);
1357 //control.TabIndex = 2;
1358 label.Font = cds.Font;
1360 label.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1361 label.UseCompatibleTextRendering = true;
1362 label.Text = aField.Text;
1368 //Create picture box
1369 PictureBox picture = new PictureBox();
1370 picture.AutoSize = true;
1371 picture.BackColor = System.Drawing.Color.Transparent;
1372 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1373 picture.Location = new System.Drawing.Point(1, 1);
1374 picture.Margin = new System.Windows.Forms.Padding(0);
1375 picture.Name = "pictureBox" + aField;
1377 picture.Image = aField.Bitmap;
1386 private void buttonAlignLeft_Click(object sender, EventArgs e)
1388 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1390 ctrl.TextAlign = ContentAlignment.MiddleLeft;
1394 private void buttonAlignCenter_Click(object sender, EventArgs e)
1396 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1398 ctrl.TextAlign = ContentAlignment.MiddleCenter;
1402 private void buttonAlignRight_Click(object sender, EventArgs e)
1404 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1406 ctrl.TextAlign = ContentAlignment.MiddleRight;
1410 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1412 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1413 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1414 Properties.Settings.Default.Save();
1415 if (iDisplay.IsOpen())
1417 OpenDisplayConnection();
1426 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1428 if (maskedTextBoxTimerInterval.Text != "")
1430 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1434 timer.Interval = interval;
1435 cds.TimerInterval = timer.Interval;
1436 Properties.Settings.Default.Save();
1441 private void buttonPowerOn_Click(object sender, EventArgs e)
1446 private void buttonPowerOff_Click(object sender, EventArgs e)
1448 iDisplay.PowerOff();
1451 private void buttonShowClock_Click(object sender, EventArgs e)
1453 iDisplay.ShowClock();
1456 private void buttonHideClock_Click(object sender, EventArgs e)
1458 iDisplay.HideClock();
1461 private void buttonUpdate_Click(object sender, EventArgs e)
1463 InstallUpdateSyncWithInfo();
1467 private void InstallUpdateSyncWithInfo()
1469 UpdateCheckInfo info = null;
1471 if (ApplicationDeployment.IsNetworkDeployed)
1473 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
1477 info = ad.CheckForDetailedUpdate();
1480 catch (DeploymentDownloadException dde)
1482 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);
1485 catch (InvalidDeploymentException ide)
1487 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);
1490 catch (InvalidOperationException ioe)
1492 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
1496 if (info.UpdateAvailable)
1498 Boolean doUpdate = true;
1500 if (!info.IsUpdateRequired)
1502 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
1503 if (!(DialogResult.OK == dr))
1510 // Display a message that the app MUST reboot. Display the minimum required version.
1511 MessageBox.Show("This application has detected a mandatory update from your current " +
1512 "version to version " + info.MinimumRequiredVersion.ToString() +
1513 ". The application will now install the update and restart.",
1514 "Update Available", MessageBoxButtons.OK,
1515 MessageBoxIcon.Information);
1523 MessageBox.Show("The application has been upgraded, and will now restart.");
1524 Application.Restart();
1526 catch (DeploymentDownloadException dde)
1528 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
1535 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
1544 private void SysTrayHideShow()
1550 WindowState = FormWindowState.Normal;
1555 /// Use to handle minimize events.
1557 /// <param name="sender"></param>
1558 /// <param name="e"></param>
1559 private void MainForm_SizeChanged(object sender, EventArgs e)
1561 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
1574 /// A UI thread copy of a client relevant data.
1575 /// Keeping this copy in the UI thread helps us deal with threading issues.
1577 public class ClientData
1579 public ClientData(string aSessionId, ICallback aCallback)
1581 SessionId = aSessionId;
1583 Fields = new List<DataField>();
1584 Layout = new TableLayout(1, 2); //Default to one column and two rows
1585 Callback = aCallback;
1588 public string SessionId { get; set; }
1589 public string Name { get; set; }
1590 public List<DataField> Fields { get; set; }
1591 public TableLayout Layout { get; set; }
1592 public ICallback Callback { get; set; }