Adding tray icon context menu.
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
124 // To make sure start up with minimize to tray works
125 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
134 private void SetupTrayIcon()
136 iNotifyIcon.Icon = GetIcon("vfd.ico");
137 iNotifyIcon.Text = "Sharp Display Manager";
138 iNotifyIcon.Visible = true;
140 //Double click toggles visibility - typically brings up the application
141 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
146 //Adding a context menu, useful to be able to exit the application
147 ContextMenu contextMenu = new ContextMenu();
148 //Context menu item to toggle visibility
149 MenuItem hideShowItem = new MenuItem("Hide/Show");
150 hideShowItem.Click += delegate(object obj, EventArgs args)
154 contextMenu.MenuItems.Add(hideShowItem);
156 //Context menu item separator
157 contextMenu.MenuItems.Add(new MenuItem("-"));
159 //Context menu exit item
160 MenuItem exitItem = new MenuItem("Exit");
161 exitItem.Click += delegate(object obj, EventArgs args)
165 contextMenu.MenuItems.Add(exitItem);
167 iNotifyIcon.ContextMenu = contextMenu;
171 /// Access icons from embedded resources.
173 /// <param name="name"></param>
174 /// <returns></returns>
175 public static Icon GetIcon(string name)
177 name = "SharpDisplayManager.Resources." + name;
180 Assembly.GetExecutingAssembly().GetManifestResourceNames();
181 for (int i = 0; i < names.Length; i++)
183 if (names[i].Replace('\\', '.') == name)
185 using (Stream stream = Assembly.GetExecutingAssembly().
186 GetManifestResourceStream(names[i]))
188 return new Icon(stream);
198 /// Set our current client.
199 /// This will take care of applying our client layout and set data fields.
201 /// <param name="aSessionId"></param>
202 void SetCurrentClient(string aSessionId)
204 if (aSessionId == iCurrentClientSessionId)
206 //Given client is already the current one.
207 //Don't bother changing anything then.
211 //Set current client ID.
212 iCurrentClientSessionId = aSessionId;
213 //Fetch and set current client data.
214 iCurrentClientData = iClients[aSessionId];
215 //Apply layout and set data fields.
216 UpdateTableLayoutPanel(iCurrentClientData);
219 private void buttonFont_Click(object sender, EventArgs e)
221 //fontDialog.ShowColor = true;
222 //fontDialog.ShowApply = true;
223 fontDialog.ShowEffects = true;
224 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[0];
225 fontDialog.Font = label.Font;
227 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
229 //fontDialog.ShowHelp = true;
231 //fontDlg.MaxSize = 40;
232 //fontDlg.MinSize = 22;
234 //fontDialog.Parent = this;
235 //fontDialog.StartPosition = FormStartPosition.CenterParent;
237 //DlgBox.ShowDialog(fontDialog);
239 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
240 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
243 //MsgBox.Show("MessageBox MsgBox", "MsgBox caption");
245 //MessageBox.Show("Ok");
246 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
248 ctrl.Font = fontDialog.Font;
250 cds.Font = fontDialog.Font;
251 Properties.Settings.Default.Save();
260 void CheckFontHeight()
262 //Show font height and width
263 labelFontHeight.Text = "Font height: " + cds.Font.Height;
264 float charWidth = IsFixedWidth(cds.Font);
265 if (charWidth == 0.0f)
267 labelFontWidth.Visible = false;
271 labelFontWidth.Visible = true;
272 labelFontWidth.Text = "Font width: " + charWidth;
275 MarqueeLabel label = null;
276 //Get the first label control we can find
277 foreach (Control ctrl in tableLayoutPanel.Controls)
279 if (ctrl is MarqueeLabel)
281 label = (MarqueeLabel)ctrl;
286 //Now check font height and show a warning if needed.
287 if (label != null && label.Font.Height > label.Height)
289 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
290 labelWarning.Visible = true;
294 labelWarning.Visible = false;
299 private void buttonCapture_Click(object sender, EventArgs e)
301 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
302 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
303 //Bitmap bmpToSave = new Bitmap(bmp);
304 bmp.Save("D:\\capture.png");
306 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
309 string outputFileName = "d:\\capture.png";
310 using (MemoryStream memory = new MemoryStream())
312 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
314 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
315 byte[] bytes = memory.ToArray();
316 fs.Write(bytes, 0, bytes.Length);
323 private void CheckForRequestResults()
325 if (iDisplay.IsRequestPending())
327 switch (iDisplay.AttemptRequestCompletion())
329 case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
330 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
331 //Issue next request then
332 iDisplay.RequestPowerSupplyStatus();
335 case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
336 if (iDisplay.PowerSupplyStatus())
338 toolStripStatusLabelPower.Text = "ON";
342 toolStripStatusLabelPower.Text = "OFF";
344 //Issue next request then
345 iDisplay.RequestDeviceId();
348 case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
349 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
350 //No more request to issue
356 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
358 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
365 public static uint ColorUntouched(int aX, int aY, uint aPixel)
370 public static uint ColorInversed(int aX, int aY, uint aPixel)
375 public static uint ColorChessboard(int aX, int aY, uint aPixel)
377 if ((aX % 2 == 0) && (aY % 2 == 0))
381 else if ((aX % 2 != 0) && (aY % 2 != 0))
389 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
391 return aBmp.Width - aX - 1;
394 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
396 return iBmp.Height - aY - 1;
399 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
404 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
410 /// Select proper pixel delegates according to our current settings.
412 private void SetupPixelDelegates()
414 //Select our pixel processing routine
415 if (cds.InverseColors)
417 //iColorFx = ColorChessboard;
418 iColorFx = ColorInversed;
422 iColorFx = ColorWhiteIsOn;
425 //Select proper coordinate translation functions
426 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
427 if (cds.ReverseScreen)
429 iScreenX = ScreenReversedX;
430 iScreenY = ScreenReversedY;
440 //This is our timer tick responsible to perform our render
441 private void timer_Tick(object sender, EventArgs e)
443 //Update our animations
444 DateTime NewTickTime = DateTime.Now;
446 //Update animation for all our marquees
447 foreach (Control ctrl in tableLayoutPanel.Controls)
449 if (ctrl is MarqueeLabel)
451 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
457 if (iDisplay.IsOpen())
459 CheckForRequestResults();
464 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
466 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
467 //iBmp.Save("D:\\capture.png");
469 //Send it to our display
470 for (int i = 0; i < iBmp.Width; i++)
472 for (int j = 0; j < iBmp.Height; j++)
476 //Get our processed pixel coordinates
477 int x = iScreenX(iBmp, i);
478 int y = iScreenY(iBmp, j);
480 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
481 //Apply color effects
482 color = iColorFx(x,y,color);
484 iDisplay.SetPixel(x, y, color);
489 iDisplay.SwapBuffers();
493 //Compute instant FPS
494 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
496 LastTickTime = NewTickTime;
500 private void OpenDisplayConnection()
502 CloseDisplayConnection();
504 if (iDisplay.Open((Display.TMiniDisplayType)cds.DisplayType))
507 iDisplay.RequestFirmwareRevision();
512 toolStripStatusLabelConnect.Text = "Connection error";
516 private void CloseDisplayConnection()
522 private void buttonOpen_Click(object sender, EventArgs e)
524 OpenDisplayConnection();
527 private void buttonClose_Click(object sender, EventArgs e)
529 CloseDisplayConnection();
532 private void buttonClear_Click(object sender, EventArgs e)
535 iDisplay.SwapBuffers();
538 private void buttonFill_Click(object sender, EventArgs e)
541 iDisplay.SwapBuffers();
544 private void trackBarBrightness_Scroll(object sender, EventArgs e)
546 cds.Brightness = trackBarBrightness.Value;
547 Properties.Settings.Default.Save();
548 iDisplay.SetBrightness(trackBarBrightness.Value);
554 /// CDS stands for Current Display Settings
556 private DisplaySettings cds
560 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
561 if (settings == null)
563 settings = new DisplaysSettings();
565 Properties.Settings.Default.DisplaysSettings = settings;
568 //Make sure all our settings have been created
569 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
571 settings.Displays.Add(new DisplaySettings());
574 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
575 return displaySettings;
580 /// Check if the given font has a fixed character pitch.
582 /// <param name="ft"></param>
583 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
584 public float IsFixedWidth(Font ft)
586 Graphics g = CreateGraphics();
587 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
588 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
590 bool fixedWidth = true;
592 foreach (char c in charSizes)
593 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
604 private void UpdateStatus()
606 //Synchronize UI with settings
608 checkBoxShowBorders.Checked = cds.ShowBorders;
609 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
611 //Set the proper font to each of our labels
612 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
614 ctrl.Font = cds.Font;
618 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
619 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
620 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
621 checkBoxReverseScreen.Checked = cds.ReverseScreen;
622 checkBoxInverseColors.Checked = cds.InverseColors;
623 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
624 timer.Interval = cds.TimerInterval;
625 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
627 SetupPixelDelegates();
629 if (iDisplay.IsOpen())
631 //Only setup brightness if display is open
632 trackBarBrightness.Minimum = iDisplay.MinBrightness();
633 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
634 trackBarBrightness.Value = cds.Brightness;
635 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
636 trackBarBrightness.SmallChange = 1;
637 iDisplay.SetBrightness(cds.Brightness);
639 buttonFill.Enabled = true;
640 buttonClear.Enabled = true;
641 buttonOpen.Enabled = false;
642 buttonClose.Enabled = true;
643 trackBarBrightness.Enabled = true;
644 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
645 //+ " - " + iDisplay.SerialNumber();
647 if (iDisplay.SupportPowerOnOff())
649 buttonPowerOn.Enabled = true;
650 buttonPowerOff.Enabled = true;
654 buttonPowerOn.Enabled = false;
655 buttonPowerOff.Enabled = false;
658 if (iDisplay.SupportClock())
660 buttonShowClock.Enabled = true;
661 buttonHideClock.Enabled = true;
665 buttonShowClock.Enabled = false;
666 buttonHideClock.Enabled = false;
671 buttonFill.Enabled = false;
672 buttonClear.Enabled = false;
673 buttonOpen.Enabled = true;
674 buttonClose.Enabled = false;
675 trackBarBrightness.Enabled = false;
676 buttonPowerOn.Enabled = false;
677 buttonPowerOff.Enabled = false;
678 buttonShowClock.Enabled = false;
679 buttonHideClock.Enabled = false;
680 toolStripStatusLabelConnect.Text = "Disconnected";
681 toolStripStatusLabelPower.Text = "N/A";
687 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
689 //Save our show borders setting
690 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
691 cds.ShowBorders = checkBoxShowBorders.Checked;
692 Properties.Settings.Default.Save();
696 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
698 //Save our connect on startup setting
699 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
700 Properties.Settings.Default.Save();
703 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
705 //Save our "Minimize to tray" setting
706 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
707 Properties.Settings.Default.Save();
711 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
713 //Save our "Start minimized" setting
714 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
715 Properties.Settings.Default.Save();
718 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
720 iStartupManager.Startup = checkBoxAutoStart.Checked;
724 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
726 //Save our reverse screen setting
727 cds.ReverseScreen = checkBoxReverseScreen.Checked;
728 Properties.Settings.Default.Save();
729 SetupPixelDelegates();
732 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
734 //Save our inverse colors setting
735 cds.InverseColors = checkBoxInverseColors.Checked;
736 Properties.Settings.Default.Save();
737 SetupPixelDelegates();
740 private void MainForm_Resize(object sender, EventArgs e)
742 if (WindowState == FormWindowState.Minimized)
745 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
746 iCreateBitmap = true;
750 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
756 public void StartServer()
758 iServiceHost = new ServiceHost
761 new Uri[] { new Uri("net.tcp://localhost:8001/") }
764 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
768 public void StopServer()
770 if (iClients.Count > 0 && !iClosing)
774 BroadcastCloseEvent();
778 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
780 iClosing = false; //We make sure we force close if asked twice
785 //We removed that as it often lags for some reason
786 //iServiceHost.Close();
790 public void BroadcastCloseEvent()
792 Trace.TraceInformation("BroadcastCloseEvent - start");
794 var inactiveClients = new List<string>();
795 foreach (var client in iClients)
797 //if (client.Key != eventData.ClientName)
801 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
802 client.Value.Callback.OnCloseOrder(/*eventData*/);
806 inactiveClients.Add(client.Key);
811 if (inactiveClients.Count > 0)
813 foreach (var client in inactiveClients)
815 iClients.Remove(client);
816 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
821 private void buttonStartClient_Click(object sender, EventArgs e)
823 Thread clientThread = new Thread(SharpDisplayClient.Program.Main);
824 clientThread.Start();
828 private void buttonSuspend_Click(object sender, EventArgs e)
830 LastTickTime = DateTime.Now; //Reset timer to prevent jump
831 timer.Enabled = !timer.Enabled;
834 buttonSuspend.Text = "Run";
838 buttonSuspend.Text = "Pause";
842 private void buttonCloseClients_Click(object sender, EventArgs e)
844 BroadcastCloseEvent();
847 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
856 /// <param name="aSessionId"></param>
857 /// <param name="aCallback"></param>
858 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
860 if (this.InvokeRequired)
862 //Not in the proper thread, invoke ourselves
863 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
864 this.Invoke(d, new object[] { aSessionId, aCallback });
868 //We are in the proper thread
869 //Add this session to our collection of clients
870 ClientData newClient = new ClientData(aSessionId, aCallback);
871 Program.iMainForm.iClients.Add(aSessionId, newClient);
872 //Add this session to our UI
873 UpdateClientTreeViewNode(newClient);
880 /// <param name="aSessionId"></param>
881 public void RemoveClientThreadSafe(string aSessionId)
883 if (this.InvokeRequired)
885 //Not in the proper thread, invoke ourselves
886 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
887 this.Invoke(d, new object[] { aSessionId });
891 //We are in the proper thread
892 //Remove this session from both client collection and UI tree view
893 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
895 Program.iMainForm.iClients.Remove(aSessionId);
896 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
899 if (iClosing && iClients.Count == 0)
901 //We were closing our form
902 //All clients are now closed
903 //Just resume our close operation
913 /// <param name="aSessionId"></param>
914 /// <param name="aLayout"></param>
915 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
917 if (this.InvokeRequired)
919 //Not in the proper thread, invoke ourselves
920 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
921 this.Invoke(d, new object[] { aSessionId, aLayout });
925 ClientData client = iClients[aSessionId];
928 client.Layout = aLayout;
929 UpdateTableLayoutPanel(client);
931 UpdateClientTreeViewNode(client);
939 /// <param name="aSessionId"></param>
940 /// <param name="aField"></param>
941 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
943 if (this.InvokeRequired)
945 //Not in the proper thread, invoke ourselves
946 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
947 this.Invoke(d, new object[] { aSessionId, aField });
951 //We are in the proper thread
952 //Call the non-thread-safe variant
953 SetClientField(aSessionId, aField);
960 /// <param name="aSessionId"></param>
961 /// <param name="aField"></param>
962 private void SetClientField(string aSessionId, DataField aField)
964 SetCurrentClient(aSessionId);
965 ClientData client = iClients[aSessionId];
968 bool somethingChanged = false;
970 //Make sure all our fields are in place
971 while (client.Fields.Count < (aField.Index + 1))
973 //Add a text field with proper index
974 client.Fields.Add(new DataField(client.Fields.Count));
975 somethingChanged = true;
978 if (client.Fields[aField.Index].IsSameLayout(aField))
980 //Same layout just update our field
981 client.Fields[aField.Index] = aField;
983 if (aField.IsText && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
985 //Text field control already in place, just change the text
986 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
987 somethingChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
988 label.Text = aField.Text;
989 label.TextAlign = aField.Alignment;
991 else if (aField.IsBitmap && tableLayoutPanel.Controls[aField.Index] is PictureBox)
993 somethingChanged = true; //TODO: Bitmap comp or should we leave that to clients?
994 //Bitmap field control already in place just change the bitmap
995 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
996 pictureBox.Image = aField.Bitmap;
1000 somethingChanged = true;
1001 //The requested control in our layout it not of the correct type
1002 //Wrong control type, re-create them all
1003 UpdateTableLayoutPanel(iCurrentClientData);
1008 somethingChanged = true;
1009 //Different layout, need to rebuild it
1010 client.Fields[aField.Index] = aField;
1011 UpdateTableLayoutPanel(iCurrentClientData);
1015 if (somethingChanged)
1017 UpdateClientTreeViewNode(client);
1025 /// <param name="aTexts"></param>
1026 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1028 if (this.InvokeRequired)
1030 //Not in the proper thread, invoke ourselves
1031 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1032 this.Invoke(d, new object[] { aSessionId, aFields });
1036 //Put each our text fields in a label control
1037 foreach (DataField field in aFields)
1039 SetClientField(aSessionId, field);
1047 /// <param name="aSessionId"></param>
1048 /// <param name="aName"></param>
1049 public void SetClientNameThreadSafe(string aSessionId, string aName)
1051 if (this.InvokeRequired)
1053 //Not in the proper thread, invoke ourselves
1054 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1055 this.Invoke(d, new object[] { aSessionId, aName });
1059 //We are in the proper thread
1061 ClientData client = iClients[aSessionId];
1065 client.Name = aName;
1066 //Update our tree-view
1067 UpdateClientTreeViewNode(client);
1075 /// <param name="aClient"></param>
1076 private void UpdateClientTreeViewNode(ClientData aClient)
1078 if (aClient == null)
1083 TreeNode node = null;
1084 //Check that our client node already exists
1085 //Get our client root node using its key which is our session ID
1086 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1087 if (nodes.Count()>0)
1089 //We already have a node for that client
1091 //Clear children as we are going to recreate them below
1096 //Client node does not exists create a new one
1097 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1098 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1104 if (aClient.Name != "")
1106 //We have a name, us it as text for our root node
1107 node.Text = aClient.Name;
1108 //Add a child with SessionId
1109 node.Nodes.Add(new TreeNode(aClient.SessionId));
1113 //No name, use session ID instead
1114 node.Text = aClient.SessionId;
1117 if (aClient.Fields.Count > 0)
1119 //Create root node for our texts
1120 TreeNode textsRoot = new TreeNode("Fields");
1121 node.Nodes.Add(textsRoot);
1122 //For each text add a new entry
1123 foreach (DataField field in aClient.Fields)
1125 if (!field.IsBitmap)
1127 DataField textField = (DataField)field;
1128 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1132 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1141 private void buttonAddRow_Click(object sender, EventArgs e)
1143 if (tableLayoutPanel.RowCount < 6)
1145 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount + 1);
1149 private void buttonRemoveRow_Click(object sender, EventArgs e)
1151 if (tableLayoutPanel.RowCount > 1)
1153 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount - 1);
1156 UpdateTableLayoutRowStyles();
1159 private void buttonAddColumn_Click(object sender, EventArgs e)
1161 if (tableLayoutPanel.ColumnCount < 8)
1163 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount + 1, tableLayoutPanel.RowCount);
1167 private void buttonRemoveColumn_Click(object sender, EventArgs e)
1169 if (tableLayoutPanel.ColumnCount > 1)
1171 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount - 1, tableLayoutPanel.RowCount);
1177 /// Update our table layout row styles to make sure each rows have similar height
1179 private void UpdateTableLayoutRowStyles()
1181 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1183 rowStyle.SizeType = SizeType.Percent;
1184 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1190 /// Empty and recreate our table layout with the given number of columns and rows.
1191 /// Sizes of rows and columns are uniform.
1193 /// <param name="aColumn"></param>
1194 /// <param name="aRow"></param>
1195 private void UpdateTableLayoutPanel(int aColumn, int aRow)
1197 tableLayoutPanel.Controls.Clear();
1198 tableLayoutPanel.RowStyles.Clear();
1199 tableLayoutPanel.ColumnStyles.Clear();
1200 tableLayoutPanel.RowCount = 0;
1201 tableLayoutPanel.ColumnCount = 0;
1203 while (tableLayoutPanel.RowCount < aRow)
1205 tableLayoutPanel.RowCount++;
1208 while (tableLayoutPanel.ColumnCount < aColumn)
1210 tableLayoutPanel.ColumnCount++;
1213 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1215 //Create our column styles
1216 this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.ColumnCount));
1218 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1222 //Create our row styles
1223 this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.RowCount));
1226 MarqueeLabel control = new SharpDisplayManager.MarqueeLabel();
1227 control.AutoEllipsis = true;
1228 control.AutoSize = true;
1229 control.BackColor = System.Drawing.Color.Transparent;
1230 control.Dock = System.Windows.Forms.DockStyle.Fill;
1231 control.Location = new System.Drawing.Point(1, 1);
1232 control.Margin = new System.Windows.Forms.Padding(0);
1233 control.Name = "marqueeLabelCol" + aColumn + "Row" + aRow;
1234 control.OwnTimer = false;
1235 control.PixelsPerSecond = 64;
1236 control.Separator = "|";
1237 //control.Size = new System.Drawing.Size(254, 30);
1238 //control.TabIndex = 2;
1239 control.Font = cds.Font;
1240 control.Text = "ABCDEFGHIJKLMNOPQRST-0123456789";
1241 control.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1242 control.UseCompatibleTextRendering = true;
1244 tableLayoutPanel.Controls.Add(control, i, j);
1253 /// Update our display table layout.
1255 /// <param name="aLayout"></param>
1256 private void UpdateTableLayoutPanel(ClientData aClient)
1258 TableLayout layout = aClient.Layout;
1261 tableLayoutPanel.Controls.Clear();
1262 tableLayoutPanel.RowStyles.Clear();
1263 tableLayoutPanel.ColumnStyles.Clear();
1264 tableLayoutPanel.RowCount = 0;
1265 tableLayoutPanel.ColumnCount = 0;
1267 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1269 tableLayoutPanel.RowCount++;
1272 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1274 tableLayoutPanel.ColumnCount++;
1277 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1279 //Create our column styles
1280 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1282 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1286 //Create our row styles
1287 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1290 //Check if we already have a control
1291 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1292 if (existingControl!=null)
1294 //We already have a control in that cell as a results of row/col spanning
1295 //Move on to next cell then
1301 //Check if a client field already exists for that cell
1302 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1304 //No client field specified, create a text field by default
1305 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1308 //Create a control corresponding to the field specified for that cell
1309 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1310 Control control = CreateControlForDataField(field);
1312 //Add newly created control to our table layout at the specified row and column
1313 tableLayoutPanel.Controls.Add(control, i, j);
1314 //Make sure we specify row and column span for that new control
1315 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1316 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1321 while (aClient.Fields.Count > fieldCount)
1323 //We have too much fields for this layout
1324 //Just discard them until we get there
1325 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1332 /// Check our type of data field and create corresponding control
1334 /// <param name="aField"></param>
1335 private Control CreateControlForDataField(DataField aField)
1337 Control control=null;
1338 if (!aField.IsBitmap)
1340 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1341 label.AutoEllipsis = true;
1342 label.AutoSize = true;
1343 label.BackColor = System.Drawing.Color.Transparent;
1344 label.Dock = System.Windows.Forms.DockStyle.Fill;
1345 label.Location = new System.Drawing.Point(1, 1);
1346 label.Margin = new System.Windows.Forms.Padding(0);
1347 label.Name = "marqueeLabel" + aField.Index;
1348 label.OwnTimer = false;
1349 label.PixelsPerSecond = 64;
1350 label.Separator = "|";
1351 //control.Size = new System.Drawing.Size(254, 30);
1352 //control.TabIndex = 2;
1353 label.Font = cds.Font;
1355 label.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1356 label.UseCompatibleTextRendering = true;
1357 label.Text = aField.Text;
1363 //Create picture box
1364 PictureBox picture = new PictureBox();
1365 picture.AutoSize = true;
1366 picture.BackColor = System.Drawing.Color.Transparent;
1367 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1368 picture.Location = new System.Drawing.Point(1, 1);
1369 picture.Margin = new System.Windows.Forms.Padding(0);
1370 picture.Name = "pictureBox" + aField;
1372 picture.Image = aField.Bitmap;
1381 private void buttonAlignLeft_Click(object sender, EventArgs e)
1383 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1385 ctrl.TextAlign = ContentAlignment.MiddleLeft;
1389 private void buttonAlignCenter_Click(object sender, EventArgs e)
1391 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1393 ctrl.TextAlign = ContentAlignment.MiddleCenter;
1397 private void buttonAlignRight_Click(object sender, EventArgs e)
1399 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1401 ctrl.TextAlign = ContentAlignment.MiddleRight;
1405 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1407 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1408 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1409 Properties.Settings.Default.Save();
1410 if (iDisplay.IsOpen())
1412 OpenDisplayConnection();
1421 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1423 if (maskedTextBoxTimerInterval.Text != "")
1425 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1429 timer.Interval = interval;
1430 cds.TimerInterval = timer.Interval;
1431 Properties.Settings.Default.Save();
1436 private void buttonPowerOn_Click(object sender, EventArgs e)
1441 private void buttonPowerOff_Click(object sender, EventArgs e)
1443 iDisplay.PowerOff();
1446 private void buttonShowClock_Click(object sender, EventArgs e)
1448 iDisplay.ShowClock();
1451 private void buttonHideClock_Click(object sender, EventArgs e)
1453 iDisplay.HideClock();
1456 private void buttonUpdate_Click(object sender, EventArgs e)
1458 InstallUpdateSyncWithInfo();
1462 private void InstallUpdateSyncWithInfo()
1464 UpdateCheckInfo info = null;
1466 if (ApplicationDeployment.IsNetworkDeployed)
1468 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
1472 info = ad.CheckForDetailedUpdate();
1475 catch (DeploymentDownloadException dde)
1477 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);
1480 catch (InvalidDeploymentException ide)
1482 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);
1485 catch (InvalidOperationException ioe)
1487 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
1491 if (info.UpdateAvailable)
1493 Boolean doUpdate = true;
1495 if (!info.IsUpdateRequired)
1497 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
1498 if (!(DialogResult.OK == dr))
1505 // Display a message that the app MUST reboot. Display the minimum required version.
1506 MessageBox.Show("This application has detected a mandatory update from your current " +
1507 "version to version " + info.MinimumRequiredVersion.ToString() +
1508 ". The application will now install the update and restart.",
1509 "Update Available", MessageBoxButtons.OK,
1510 MessageBoxIcon.Information);
1518 MessageBox.Show("The application has been upgraded, and will now restart.");
1519 Application.Restart();
1521 catch (DeploymentDownloadException dde)
1523 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
1530 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
1539 private void SysTrayHideShow()
1545 WindowState = FormWindowState.Normal;
1550 /// Use to handle minimize events.
1552 /// <param name="sender"></param>
1553 /// <param name="e"></param>
1554 private void MainForm_SizeChanged(object sender, EventArgs e)
1556 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
1569 /// A UI thread copy of a client relevant data.
1570 /// Keeping this copy in the UI thread helps us deal with threading issues.
1572 public class ClientData
1574 public ClientData(string aSessionId, ICallback aCallback)
1576 SessionId = aSessionId;
1578 Fields = new List<DataField>();
1579 Layout = new TableLayout(1, 2); //Default to one column and two rows
1580 Callback = aCallback;
1583 public string SessionId { get; set; }
1584 public string Name { get; set; }
1585 public List<DataField> Fields { get; set; }
1586 public TableLayout Layout { get; set; }
1587 public ICallback Callback { get; set; }