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
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 fontDialog.Font = cds.Font;
226 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
228 //fontDialog.ShowHelp = true;
230 //fontDlg.MaxSize = 40;
231 //fontDlg.MinSize = 22;
233 //fontDialog.Parent = this;
234 //fontDialog.StartPosition = FormStartPosition.CenterParent;
236 //DlgBox.ShowDialog(fontDialog);
238 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
239 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
241 //Set the fonts to all our labels in our layout
242 foreach (Control ctrl in tableLayoutPanel.Controls)
244 if (ctrl is MarqueeLabel)
246 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
251 cds.Font = fontDialog.Font;
252 Properties.Settings.Default.Save();
261 void CheckFontHeight()
263 //Show font height and width
264 labelFontHeight.Text = "Font height: " + cds.Font.Height;
265 float charWidth = IsFixedWidth(cds.Font);
266 if (charWidth == 0.0f)
268 labelFontWidth.Visible = false;
272 labelFontWidth.Visible = true;
273 labelFontWidth.Text = "Font width: " + charWidth;
276 MarqueeLabel label = null;
277 //Get the first label control we can find
278 foreach (Control ctrl in tableLayoutPanel.Controls)
280 if (ctrl is MarqueeLabel)
282 label = (MarqueeLabel)ctrl;
287 //Now check font height and show a warning if needed.
288 if (label != null && label.Font.Height > label.Height)
290 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
291 labelWarning.Visible = true;
295 labelWarning.Visible = false;
300 private void buttonCapture_Click(object sender, EventArgs e)
302 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
303 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
304 //Bitmap bmpToSave = new Bitmap(bmp);
305 bmp.Save("D:\\capture.png");
307 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
310 string outputFileName = "d:\\capture.png";
311 using (MemoryStream memory = new MemoryStream())
313 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
315 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
316 byte[] bytes = memory.ToArray();
317 fs.Write(bytes, 0, bytes.Length);
324 private void CheckForRequestResults()
326 if (iDisplay.IsRequestPending())
328 switch (iDisplay.AttemptRequestCompletion())
330 case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
331 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
332 //Issue next request then
333 iDisplay.RequestPowerSupplyStatus();
336 case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
337 if (iDisplay.PowerSupplyStatus())
339 toolStripStatusLabelPower.Text = "ON";
343 toolStripStatusLabelPower.Text = "OFF";
345 //Issue next request then
346 iDisplay.RequestDeviceId();
349 case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
350 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
351 //No more request to issue
357 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
359 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
366 public static uint ColorUntouched(int aX, int aY, uint aPixel)
371 public static uint ColorInversed(int aX, int aY, uint aPixel)
376 public static uint ColorChessboard(int aX, int aY, uint aPixel)
378 if ((aX % 2 == 0) && (aY % 2 == 0))
382 else if ((aX % 2 != 0) && (aY % 2 != 0))
390 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
392 return aBmp.Width - aX - 1;
395 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
397 return iBmp.Height - aY - 1;
400 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
405 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
411 /// Select proper pixel delegates according to our current settings.
413 private void SetupPixelDelegates()
415 //Select our pixel processing routine
416 if (cds.InverseColors)
418 //iColorFx = ColorChessboard;
419 iColorFx = ColorInversed;
423 iColorFx = ColorWhiteIsOn;
426 //Select proper coordinate translation functions
427 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
428 if (cds.ReverseScreen)
430 iScreenX = ScreenReversedX;
431 iScreenY = ScreenReversedY;
441 //This is our timer tick responsible to perform our render
442 private void timer_Tick(object sender, EventArgs e)
444 //Update our animations
445 DateTime NewTickTime = DateTime.Now;
447 //Update animation for all our marquees
448 foreach (Control ctrl in tableLayoutPanel.Controls)
450 if (ctrl is MarqueeLabel)
452 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
458 if (iDisplay.IsOpen())
460 CheckForRequestResults();
465 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
467 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
468 //iBmp.Save("D:\\capture.png");
470 //Send it to our display
471 for (int i = 0; i < iBmp.Width; i++)
473 for (int j = 0; j < iBmp.Height; j++)
477 //Get our processed pixel coordinates
478 int x = iScreenX(iBmp, i);
479 int y = iScreenY(iBmp, j);
481 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
482 //Apply color effects
483 color = iColorFx(x,y,color);
485 iDisplay.SetPixel(x, y, color);
490 iDisplay.SwapBuffers();
494 //Compute instant FPS
495 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
497 LastTickTime = NewTickTime;
501 private void OpenDisplayConnection()
503 CloseDisplayConnection();
505 if (iDisplay.Open((Display.TMiniDisplayType)cds.DisplayType))
508 iDisplay.RequestFirmwareRevision();
513 toolStripStatusLabelConnect.Text = "Connection error";
517 private void CloseDisplayConnection()
523 private void buttonOpen_Click(object sender, EventArgs e)
525 OpenDisplayConnection();
528 private void buttonClose_Click(object sender, EventArgs e)
530 CloseDisplayConnection();
533 private void buttonClear_Click(object sender, EventArgs e)
536 iDisplay.SwapBuffers();
539 private void buttonFill_Click(object sender, EventArgs e)
542 iDisplay.SwapBuffers();
545 private void trackBarBrightness_Scroll(object sender, EventArgs e)
547 cds.Brightness = trackBarBrightness.Value;
548 Properties.Settings.Default.Save();
549 iDisplay.SetBrightness(trackBarBrightness.Value);
555 /// CDS stands for Current Display Settings
557 private DisplaySettings cds
561 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
562 if (settings == null)
564 settings = new DisplaysSettings();
566 Properties.Settings.Default.DisplaysSettings = settings;
569 //Make sure all our settings have been created
570 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
572 settings.Displays.Add(new DisplaySettings());
575 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
576 return displaySettings;
581 /// Check if the given font has a fixed character pitch.
583 /// <param name="ft"></param>
584 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
585 public float IsFixedWidth(Font ft)
587 Graphics g = CreateGraphics();
588 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
589 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
591 bool fixedWidth = true;
593 foreach (char c in charSizes)
594 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
605 private void UpdateStatus()
607 //Synchronize UI with settings
609 checkBoxShowBorders.Checked = cds.ShowBorders;
610 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
612 //Set the proper font to each of our labels
613 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
615 ctrl.Font = cds.Font;
619 //Check if "run on Windows startup" is enabled
620 checkBoxAutoStart.Checked = iStartupManager.Startup;
622 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
623 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
624 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
625 checkBoxReverseScreen.Checked = cds.ReverseScreen;
626 checkBoxInverseColors.Checked = cds.InverseColors;
627 checkBoxScaleToFit.Checked = cds.ScaleToFit;
628 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
629 labelMinFontSize.Enabled = cds.ScaleToFit;
630 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
631 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
632 timer.Interval = cds.TimerInterval;
633 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
634 textBoxScrollLoopSeparator.Text = cds.Separator;
636 SetupPixelDelegates();
638 if (iDisplay.IsOpen())
640 //Only setup brightness if display is open
641 trackBarBrightness.Minimum = iDisplay.MinBrightness();
642 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
643 trackBarBrightness.Value = cds.Brightness;
644 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
645 trackBarBrightness.SmallChange = 1;
646 iDisplay.SetBrightness(cds.Brightness);
648 buttonFill.Enabled = true;
649 buttonClear.Enabled = true;
650 buttonOpen.Enabled = false;
651 buttonClose.Enabled = true;
652 trackBarBrightness.Enabled = true;
653 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
654 //+ " - " + iDisplay.SerialNumber();
656 if (iDisplay.SupportPowerOnOff())
658 buttonPowerOn.Enabled = true;
659 buttonPowerOff.Enabled = true;
663 buttonPowerOn.Enabled = false;
664 buttonPowerOff.Enabled = false;
667 if (iDisplay.SupportClock())
669 buttonShowClock.Enabled = true;
670 buttonHideClock.Enabled = true;
674 buttonShowClock.Enabled = false;
675 buttonHideClock.Enabled = false;
680 buttonFill.Enabled = false;
681 buttonClear.Enabled = false;
682 buttonOpen.Enabled = true;
683 buttonClose.Enabled = false;
684 trackBarBrightness.Enabled = false;
685 buttonPowerOn.Enabled = false;
686 buttonPowerOff.Enabled = false;
687 buttonShowClock.Enabled = false;
688 buttonHideClock.Enabled = false;
689 toolStripStatusLabelConnect.Text = "Disconnected";
690 toolStripStatusLabelPower.Text = "N/A";
696 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
698 //Save our show borders setting
699 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
700 cds.ShowBorders = checkBoxShowBorders.Checked;
701 Properties.Settings.Default.Save();
705 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
707 //Save our connect on startup setting
708 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
709 Properties.Settings.Default.Save();
712 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
714 //Save our "Minimize to tray" setting
715 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
716 Properties.Settings.Default.Save();
720 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
722 //Save our "Start minimized" setting
723 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
724 Properties.Settings.Default.Save();
727 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
729 iStartupManager.Startup = checkBoxAutoStart.Checked;
733 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
735 //Save our reverse screen setting
736 cds.ReverseScreen = checkBoxReverseScreen.Checked;
737 Properties.Settings.Default.Save();
738 SetupPixelDelegates();
741 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
743 //Save our inverse colors setting
744 cds.InverseColors = checkBoxInverseColors.Checked;
745 Properties.Settings.Default.Save();
746 SetupPixelDelegates();
749 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
751 //Save our scale to fit setting
752 cds.ScaleToFit = checkBoxScaleToFit.Checked;
753 Properties.Settings.Default.Save();
755 labelMinFontSize.Enabled = cds.ScaleToFit;
756 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
759 private void MainForm_Resize(object sender, EventArgs e)
761 if (WindowState == FormWindowState.Minimized)
764 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
765 iCreateBitmap = true;
769 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
775 public void StartServer()
777 iServiceHost = new ServiceHost
780 new Uri[] { new Uri("net.tcp://localhost:8001/") }
783 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
787 public void StopServer()
789 if (iClients.Count > 0 && !iClosing)
793 BroadcastCloseEvent();
797 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
799 iClosing = false; //We make sure we force close if asked twice
804 //We removed that as it often lags for some reason
805 //iServiceHost.Close();
809 public void BroadcastCloseEvent()
811 Trace.TraceInformation("BroadcastCloseEvent - start");
813 var inactiveClients = new List<string>();
814 foreach (var client in iClients)
816 //if (client.Key != eventData.ClientName)
820 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
821 client.Value.Callback.OnCloseOrder(/*eventData*/);
825 inactiveClients.Add(client.Key);
830 if (inactiveClients.Count > 0)
832 foreach (var client in inactiveClients)
834 iClients.Remove(client);
835 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
839 if (iClients.Count==0)
846 /// Just remove all our fields.
848 private void ClearLayout()
850 tableLayoutPanel.Controls.Clear();
851 tableLayoutPanel.RowStyles.Clear();
852 tableLayoutPanel.ColumnStyles.Clear();
855 private void buttonStartClient_Click(object sender, EventArgs e)
857 Thread clientThread = new Thread(SharpDisplayClient.Program.Main);
858 clientThread.Start();
862 private void buttonSuspend_Click(object sender, EventArgs e)
864 LastTickTime = DateTime.Now; //Reset timer to prevent jump
865 timer.Enabled = !timer.Enabled;
868 buttonSuspend.Text = "Run";
872 buttonSuspend.Text = "Pause";
876 private void buttonCloseClients_Click(object sender, EventArgs e)
878 BroadcastCloseEvent();
881 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
890 /// <param name="aSessionId"></param>
891 /// <param name="aCallback"></param>
892 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
894 if (this.InvokeRequired)
896 //Not in the proper thread, invoke ourselves
897 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
898 this.Invoke(d, new object[] { aSessionId, aCallback });
902 //We are in the proper thread
903 //Add this session to our collection of clients
904 ClientData newClient = new ClientData(aSessionId, aCallback);
905 Program.iMainForm.iClients.Add(aSessionId, newClient);
906 //Add this session to our UI
907 UpdateClientTreeViewNode(newClient);
914 /// <param name="aSessionId"></param>
915 public void RemoveClientThreadSafe(string aSessionId)
917 if (this.InvokeRequired)
919 //Not in the proper thread, invoke ourselves
920 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
921 this.Invoke(d, new object[] { aSessionId });
925 //We are in the proper thread
926 //Remove this session from both client collection and UI tree view
927 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
929 Program.iMainForm.iClients.Remove(aSessionId);
930 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
933 if (iClients.Count == 0)
935 //Clear our screen when last client disconnects
940 //We were closing our form
941 //All clients are now closed
942 //Just resume our close operation
953 /// <param name="aSessionId"></param>
954 /// <param name="aLayout"></param>
955 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
957 if (this.InvokeRequired)
959 //Not in the proper thread, invoke ourselves
960 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
961 this.Invoke(d, new object[] { aSessionId, aLayout });
965 ClientData client = iClients[aSessionId];
968 client.Layout = aLayout;
969 UpdateTableLayoutPanel(client);
971 UpdateClientTreeViewNode(client);
979 /// <param name="aSessionId"></param>
980 /// <param name="aField"></param>
981 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
983 if (this.InvokeRequired)
985 //Not in the proper thread, invoke ourselves
986 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
987 this.Invoke(d, new object[] { aSessionId, aField });
991 //We are in the proper thread
992 //Call the non-thread-safe variant
993 SetClientField(aSessionId, aField);
1000 /// <param name="aSessionId"></param>
1001 /// <param name="aField"></param>
1002 private void SetClientField(string aSessionId, DataField aField)
1004 SetCurrentClient(aSessionId);
1005 ClientData client = iClients[aSessionId];
1008 bool somethingChanged = false;
1010 //Make sure all our fields are in place
1011 while (client.Fields.Count < (aField.Index + 1))
1013 //Add a text field with proper index
1014 client.Fields.Add(new DataField(client.Fields.Count));
1015 somethingChanged = true;
1018 if (client.Fields[aField.Index].IsSameLayout(aField))
1020 //Same layout just update our field
1021 client.Fields[aField.Index] = aField;
1023 if (aField.IsText && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
1025 //Text field control already in place, just change the text
1026 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
1027 somethingChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
1028 label.Text = aField.Text;
1029 label.TextAlign = aField.Alignment;
1031 else if (aField.IsBitmap && tableLayoutPanel.Controls[aField.Index] is PictureBox)
1033 somethingChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1034 //Bitmap field control already in place just change the bitmap
1035 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
1036 pictureBox.Image = aField.Bitmap;
1040 somethingChanged = true;
1041 //The requested control in our layout it not of the correct type
1042 //Wrong control type, re-create them all
1043 UpdateTableLayoutPanel(iCurrentClientData);
1048 somethingChanged = true;
1049 //Different layout, need to rebuild it
1050 client.Fields[aField.Index] = aField;
1051 UpdateTableLayoutPanel(iCurrentClientData);
1055 if (somethingChanged)
1057 UpdateClientTreeViewNode(client);
1065 /// <param name="aTexts"></param>
1066 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1068 if (this.InvokeRequired)
1070 //Not in the proper thread, invoke ourselves
1071 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1072 this.Invoke(d, new object[] { aSessionId, aFields });
1076 //Put each our text fields in a label control
1077 foreach (DataField field in aFields)
1079 SetClientField(aSessionId, field);
1087 /// <param name="aSessionId"></param>
1088 /// <param name="aName"></param>
1089 public void SetClientNameThreadSafe(string aSessionId, string aName)
1091 if (this.InvokeRequired)
1093 //Not in the proper thread, invoke ourselves
1094 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1095 this.Invoke(d, new object[] { aSessionId, aName });
1099 //We are in the proper thread
1101 ClientData client = iClients[aSessionId];
1105 client.Name = aName;
1106 //Update our tree-view
1107 UpdateClientTreeViewNode(client);
1115 /// <param name="aClient"></param>
1116 private void UpdateClientTreeViewNode(ClientData aClient)
1118 if (aClient == null)
1123 TreeNode node = null;
1124 //Check that our client node already exists
1125 //Get our client root node using its key which is our session ID
1126 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1127 if (nodes.Count()>0)
1129 //We already have a node for that client
1131 //Clear children as we are going to recreate them below
1136 //Client node does not exists create a new one
1137 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1138 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1144 if (aClient.Name != "")
1146 //We have a name, us it as text for our root node
1147 node.Text = aClient.Name;
1148 //Add a child with SessionId
1149 node.Nodes.Add(new TreeNode(aClient.SessionId));
1153 //No name, use session ID instead
1154 node.Text = aClient.SessionId;
1157 if (aClient.Fields.Count > 0)
1159 //Create root node for our texts
1160 TreeNode textsRoot = new TreeNode("Fields");
1161 node.Nodes.Add(textsRoot);
1162 //For each text add a new entry
1163 foreach (DataField field in aClient.Fields)
1165 if (!field.IsBitmap)
1167 DataField textField = (DataField)field;
1168 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1172 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1181 private void buttonAddRow_Click(object sender, EventArgs e)
1183 if (tableLayoutPanel.RowCount < 6)
1185 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount + 1);
1189 private void buttonRemoveRow_Click(object sender, EventArgs e)
1191 if (tableLayoutPanel.RowCount > 1)
1193 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount - 1);
1196 UpdateTableLayoutRowStyles();
1199 private void buttonAddColumn_Click(object sender, EventArgs e)
1201 if (tableLayoutPanel.ColumnCount < 8)
1203 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount + 1, tableLayoutPanel.RowCount);
1207 private void buttonRemoveColumn_Click(object sender, EventArgs e)
1209 if (tableLayoutPanel.ColumnCount > 1)
1211 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount - 1, tableLayoutPanel.RowCount);
1217 /// Update our table layout row styles to make sure each rows have similar height
1219 private void UpdateTableLayoutRowStyles()
1221 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1223 rowStyle.SizeType = SizeType.Percent;
1224 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1230 /// Empty and recreate our table layout with the given number of columns and rows.
1231 /// Sizes of rows and columns are uniform.
1233 /// <param name="aColumn"></param>
1234 /// <param name="aRow"></param>
1235 private void UpdateTableLayoutPanel(int aColumn, int aRow)
1237 tableLayoutPanel.Controls.Clear();
1238 tableLayoutPanel.RowStyles.Clear();
1239 tableLayoutPanel.ColumnStyles.Clear();
1240 tableLayoutPanel.RowCount = 0;
1241 tableLayoutPanel.ColumnCount = 0;
1243 while (tableLayoutPanel.RowCount < aRow)
1245 tableLayoutPanel.RowCount++;
1248 while (tableLayoutPanel.ColumnCount < aColumn)
1250 tableLayoutPanel.ColumnCount++;
1253 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1255 //Create our column styles
1256 this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.ColumnCount));
1258 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1262 //Create our row styles
1263 this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.RowCount));
1266 MarqueeLabel control = new SharpDisplayManager.MarqueeLabel();
1267 control.AutoEllipsis = true;
1268 control.AutoSize = true;
1269 control.BackColor = System.Drawing.Color.Transparent;
1270 control.Dock = System.Windows.Forms.DockStyle.Fill;
1271 control.Location = new System.Drawing.Point(1, 1);
1272 control.Margin = new System.Windows.Forms.Padding(0);
1273 control.Name = "marqueeLabelCol" + aColumn + "Row" + aRow;
1274 control.OwnTimer = false;
1275 control.PixelsPerSecond = 64;
1276 control.Separator = cds.Separator;
1277 control.MinFontSize = cds.MinFontSize;
1278 control.ScaleToFit = cds.ScaleToFit;
1279 //control.Size = new System.Drawing.Size(254, 30);
1280 //control.TabIndex = 2;
1281 control.Font = cds.Font;
1282 control.Text = "ABCDEFGHIJKLMNOPQRST-0123456789";
1283 control.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1284 control.UseCompatibleTextRendering = true;
1286 tableLayoutPanel.Controls.Add(control, i, j);
1295 /// Update our display table layout.
1297 /// <param name="aLayout"></param>
1298 private void UpdateTableLayoutPanel(ClientData aClient)
1300 TableLayout layout = aClient.Layout;
1303 tableLayoutPanel.Controls.Clear();
1304 tableLayoutPanel.RowStyles.Clear();
1305 tableLayoutPanel.ColumnStyles.Clear();
1306 tableLayoutPanel.RowCount = 0;
1307 tableLayoutPanel.ColumnCount = 0;
1309 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1311 tableLayoutPanel.RowCount++;
1314 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1316 tableLayoutPanel.ColumnCount++;
1319 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1321 //Create our column styles
1322 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1324 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1328 //Create our row styles
1329 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1332 //Check if we already have a control
1333 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1334 if (existingControl!=null)
1336 //We already have a control in that cell as a results of row/col spanning
1337 //Move on to next cell then
1343 //Check if a client field already exists for that cell
1344 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1346 //No client field specified, create a text field by default
1347 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1350 //Create a control corresponding to the field specified for that cell
1351 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1352 Control control = CreateControlForDataField(field);
1354 //Add newly created control to our table layout at the specified row and column
1355 tableLayoutPanel.Controls.Add(control, i, j);
1356 //Make sure we specify row and column span for that new control
1357 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1358 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1363 while (aClient.Fields.Count > fieldCount)
1365 //We have too much fields for this layout
1366 //Just discard them until we get there
1367 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1374 /// Check our type of data field and create corresponding control
1376 /// <param name="aField"></param>
1377 private Control CreateControlForDataField(DataField aField)
1379 Control control=null;
1380 if (!aField.IsBitmap)
1382 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1383 label.AutoEllipsis = true;
1384 label.AutoSize = true;
1385 label.BackColor = System.Drawing.Color.Transparent;
1386 label.Dock = System.Windows.Forms.DockStyle.Fill;
1387 label.Location = new System.Drawing.Point(1, 1);
1388 label.Margin = new System.Windows.Forms.Padding(0);
1389 label.Name = "marqueeLabel" + aField.Index;
1390 label.OwnTimer = false;
1391 label.PixelsPerSecond = 64;
1392 label.Separator = cds.Separator;
1393 label.MinFontSize = cds.MinFontSize;
1394 label.ScaleToFit = cds.ScaleToFit;
1395 //control.Size = new System.Drawing.Size(254, 30);
1396 //control.TabIndex = 2;
1397 label.Font = cds.Font;
1399 label.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1400 label.UseCompatibleTextRendering = true;
1401 label.Text = aField.Text;
1407 //Create picture box
1408 PictureBox picture = new PictureBox();
1409 picture.AutoSize = true;
1410 picture.BackColor = System.Drawing.Color.Transparent;
1411 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1412 picture.Location = new System.Drawing.Point(1, 1);
1413 picture.Margin = new System.Windows.Forms.Padding(0);
1414 picture.Name = "pictureBox" + aField;
1416 picture.Image = aField.Bitmap;
1425 private void buttonAlignLeft_Click(object sender, EventArgs e)
1427 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1429 ctrl.TextAlign = ContentAlignment.MiddleLeft;
1433 private void buttonAlignCenter_Click(object sender, EventArgs e)
1435 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1437 ctrl.TextAlign = ContentAlignment.MiddleCenter;
1441 private void buttonAlignRight_Click(object sender, EventArgs e)
1443 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1445 ctrl.TextAlign = ContentAlignment.MiddleRight;
1449 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1451 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1452 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1453 Properties.Settings.Default.Save();
1454 if (iDisplay.IsOpen())
1456 OpenDisplayConnection();
1464 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1466 if (maskedTextBoxTimerInterval.Text != "")
1468 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1472 timer.Interval = interval;
1473 cds.TimerInterval = timer.Interval;
1474 Properties.Settings.Default.Save();
1479 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
1481 if (maskedTextBoxMinFontSize.Text != "")
1483 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
1485 if (minFontSize > 0)
1487 //TODO: re-create layout? update our fields?
1488 cds.MinFontSize = minFontSize;
1489 Properties.Settings.Default.Save();
1494 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
1496 //TODO: re-create layout? update our fields?
1497 cds.Separator = textBoxScrollLoopSeparator.Text;
1498 Properties.Settings.Default.Save();
1501 private void buttonPowerOn_Click(object sender, EventArgs e)
1506 private void buttonPowerOff_Click(object sender, EventArgs e)
1508 iDisplay.PowerOff();
1511 private void buttonShowClock_Click(object sender, EventArgs e)
1513 iDisplay.ShowClock();
1516 private void buttonHideClock_Click(object sender, EventArgs e)
1518 iDisplay.HideClock();
1521 private void buttonUpdate_Click(object sender, EventArgs e)
1523 InstallUpdateSyncWithInfo();
1527 private void InstallUpdateSyncWithInfo()
1529 UpdateCheckInfo info = null;
1531 if (ApplicationDeployment.IsNetworkDeployed)
1533 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
1537 info = ad.CheckForDetailedUpdate();
1540 catch (DeploymentDownloadException dde)
1542 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);
1545 catch (InvalidDeploymentException ide)
1547 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);
1550 catch (InvalidOperationException ioe)
1552 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
1556 if (info.UpdateAvailable)
1558 Boolean doUpdate = true;
1560 if (!info.IsUpdateRequired)
1562 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
1563 if (!(DialogResult.OK == dr))
1570 // Display a message that the app MUST reboot. Display the minimum required version.
1571 MessageBox.Show("This application has detected a mandatory update from your current " +
1572 "version to version " + info.MinimumRequiredVersion.ToString() +
1573 ". The application will now install the update and restart.",
1574 "Update Available", MessageBoxButtons.OK,
1575 MessageBoxIcon.Information);
1583 MessageBox.Show("The application has been upgraded, and will now restart.");
1584 Application.Restart();
1586 catch (DeploymentDownloadException dde)
1588 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
1595 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
1604 private void SysTrayHideShow()
1610 WindowState = FormWindowState.Normal;
1615 /// Use to handle minimize events.
1617 /// <param name="sender"></param>
1618 /// <param name="e"></param>
1619 private void MainForm_SizeChanged(object sender, EventArgs e)
1621 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
1637 /// A UI thread copy of a client relevant data.
1638 /// Keeping this copy in the UI thread helps us deal with threading issues.
1640 public class ClientData
1642 public ClientData(string aSessionId, ICallback aCallback)
1644 SessionId = aSessionId;
1646 Fields = new List<DataField>();
1647 Layout = new TableLayout(1, 2); //Default to one column and two rows
1648 Callback = aCallback;
1651 public string SessionId { get; set; }
1652 public string Name { get; set; }
1653 public List<DataField> Fields { get; set; }
1654 public TableLayout Layout { get; set; }
1655 public ICallback Callback { get; set; }