Making display resize work.
Clearing the display before when closing.
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 //Instantiate our display and register for events notifications
76 iDisplay = new Display();
77 iDisplay.OnOpened += OnDisplayOpened;
78 iDisplay.OnClosed += OnDisplayClosed;
80 iClients = new Dictionary<string, ClientData>();
81 iStartupManager = new StartupManager();
82 iNotifyIcon = new NotifyIconAdv();
84 //Have our designer initialize its controls
85 InitializeComponent();
87 //Populate device types
88 PopulateDeviceTypes();
90 //Initial status update
93 //We have a bug when drawing minimized and reusing our bitmap
94 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
95 iCreateBitmap = false;
97 //Minimize our window if desired
98 if (Properties.Settings.Default.StartMinimized)
100 WindowState = FormWindowState.Minimized;
106 /// Called when our display is opened.
108 /// <param name="aDisplay"></param>
109 private void OnDisplayOpened(Display aDisplay)
111 //Set our screen size now that our display is connected
112 //Our panelDisplay is the container of our tableLayoutPanel
113 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
114 //panelDisplay needs an extra 2 pixels for borders on each sides
115 //tableLayoutPanel will eventually be the exact size of our display
116 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
117 panelDisplay.Size = size;
119 //Our display was just opened, update our UI
121 //Initiate asynchronous request
122 iDisplay.RequestFirmwareRevision();
126 /// Called when our display is closed.
128 /// <param name="aDisplay"></param>
129 private void OnDisplayClosed(Display aDisplay)
131 //Our display was just closed, update our UI consequently
138 private void PopulateDeviceTypes()
140 int count = Display.TypeCount();
142 for (int i=0; i<count; i++)
144 comboBoxDisplayType.Items.Add(Display.TypeName((Display.TMiniDisplayType)i));
151 /// <param name="sender"></param>
152 /// <param name="e"></param>
153 private void MainForm_Load(object sender, EventArgs e)
155 //Check if we are running a Click Once deployed application
156 if (ApplicationDeployment.IsNetworkDeployed)
158 //This is a proper Click Once installation, fetch and show our version number
159 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
163 //Not a proper Click Once installation, assuming development build then
164 this.Text += " - development";
167 //Setup notification icon
170 // To make sure start up with minimize to tray works
171 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
177 //When not debugging we want the screen to be empty until a client takes over
181 //Open display connection on start-up if needed
182 if (Properties.Settings.Default.DisplayConnectOnStartup)
184 OpenDisplayConnection();
187 //Start our server so that we can get client requests
194 private void SetupTrayIcon()
196 iNotifyIcon.Icon = GetIcon("vfd.ico");
197 iNotifyIcon.Text = "Sharp Display Manager";
198 iNotifyIcon.Visible = true;
200 //Double click toggles visibility - typically brings up the application
201 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
206 //Adding a context menu, useful to be able to exit the application
207 ContextMenu contextMenu = new ContextMenu();
208 //Context menu item to toggle visibility
209 MenuItem hideShowItem = new MenuItem("Hide/Show");
210 hideShowItem.Click += delegate(object obj, EventArgs args)
214 contextMenu.MenuItems.Add(hideShowItem);
216 //Context menu item separator
217 contextMenu.MenuItems.Add(new MenuItem("-"));
219 //Context menu exit item
220 MenuItem exitItem = new MenuItem("Exit");
221 exitItem.Click += delegate(object obj, EventArgs args)
225 contextMenu.MenuItems.Add(exitItem);
227 iNotifyIcon.ContextMenu = contextMenu;
231 /// Access icons from embedded resources.
233 /// <param name="name"></param>
234 /// <returns></returns>
235 public static Icon GetIcon(string name)
237 name = "SharpDisplayManager.Resources." + name;
240 Assembly.GetExecutingAssembly().GetManifestResourceNames();
241 for (int i = 0; i < names.Length; i++)
243 if (names[i].Replace('\\', '.') == name)
245 using (Stream stream = Assembly.GetExecutingAssembly().
246 GetManifestResourceStream(names[i]))
248 return new Icon(stream);
258 /// Set our current client.
259 /// This will take care of applying our client layout and set data fields.
261 /// <param name="aSessionId"></param>
262 void SetCurrentClient(string aSessionId)
264 if (aSessionId == iCurrentClientSessionId)
266 //Given client is already the current one.
267 //Don't bother changing anything then.
271 //Set current client ID.
272 iCurrentClientSessionId = aSessionId;
273 //Fetch and set current client data.
274 iCurrentClientData = iClients[aSessionId];
275 //Apply layout and set data fields.
276 UpdateTableLayoutPanel(iCurrentClientData);
279 private void buttonFont_Click(object sender, EventArgs e)
281 //fontDialog.ShowColor = true;
282 //fontDialog.ShowApply = true;
283 fontDialog.ShowEffects = true;
284 fontDialog.Font = cds.Font;
286 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
288 //fontDialog.ShowHelp = true;
290 //fontDlg.MaxSize = 40;
291 //fontDlg.MinSize = 22;
293 //fontDialog.Parent = this;
294 //fontDialog.StartPosition = FormStartPosition.CenterParent;
296 //DlgBox.ShowDialog(fontDialog);
298 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
299 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
301 //Set the fonts to all our labels in our layout
302 foreach (Control ctrl in tableLayoutPanel.Controls)
304 if (ctrl is MarqueeLabel)
306 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
311 cds.Font = fontDialog.Font;
312 Properties.Settings.Default.Save();
321 void CheckFontHeight()
323 //Show font height and width
324 labelFontHeight.Text = "Font height: " + cds.Font.Height;
325 float charWidth = IsFixedWidth(cds.Font);
326 if (charWidth == 0.0f)
328 labelFontWidth.Visible = false;
332 labelFontWidth.Visible = true;
333 labelFontWidth.Text = "Font width: " + charWidth;
336 MarqueeLabel label = null;
337 //Get the first label control we can find
338 foreach (Control ctrl in tableLayoutPanel.Controls)
340 if (ctrl is MarqueeLabel)
342 label = (MarqueeLabel)ctrl;
347 //Now check font height and show a warning if needed.
348 if (label != null && label.Font.Height > label.Height)
350 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
351 labelWarning.Visible = true;
355 labelWarning.Visible = false;
360 private void buttonCapture_Click(object sender, EventArgs e)
362 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
363 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
364 //Bitmap bmpToSave = new Bitmap(bmp);
365 bmp.Save("D:\\capture.png");
367 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
370 string outputFileName = "d:\\capture.png";
371 using (MemoryStream memory = new MemoryStream())
373 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
375 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
376 byte[] bytes = memory.ToArray();
377 fs.Write(bytes, 0, bytes.Length);
384 private void CheckForRequestResults()
386 if (iDisplay.IsRequestPending())
388 switch (iDisplay.AttemptRequestCompletion())
390 case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
391 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
392 //Issue next request then
393 iDisplay.RequestPowerSupplyStatus();
396 case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
397 if (iDisplay.PowerSupplyStatus())
399 toolStripStatusLabelPower.Text = "ON";
403 toolStripStatusLabelPower.Text = "OFF";
405 //Issue next request then
406 iDisplay.RequestDeviceId();
409 case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
410 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
411 //No more request to issue
417 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
419 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
426 public static uint ColorUntouched(int aX, int aY, uint aPixel)
431 public static uint ColorInversed(int aX, int aY, uint aPixel)
436 public static uint ColorChessboard(int aX, int aY, uint aPixel)
438 if ((aX % 2 == 0) && (aY % 2 == 0))
442 else if ((aX % 2 != 0) && (aY % 2 != 0))
450 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
452 return aBmp.Width - aX - 1;
455 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
457 return iBmp.Height - aY - 1;
460 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
465 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
471 /// Select proper pixel delegates according to our current settings.
473 private void SetupPixelDelegates()
475 //Select our pixel processing routine
476 if (cds.InverseColors)
478 //iColorFx = ColorChessboard;
479 iColorFx = ColorInversed;
483 iColorFx = ColorWhiteIsOn;
486 //Select proper coordinate translation functions
487 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
488 if (cds.ReverseScreen)
490 iScreenX = ScreenReversedX;
491 iScreenY = ScreenReversedY;
501 //This is our timer tick responsible to perform our render
502 private void timer_Tick(object sender, EventArgs e)
504 //Update our animations
505 DateTime NewTickTime = DateTime.Now;
507 //Update animation for all our marquees
508 foreach (Control ctrl in tableLayoutPanel.Controls)
510 if (ctrl is MarqueeLabel)
512 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
518 if (iDisplay.IsOpen())
520 CheckForRequestResults();
525 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
527 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
528 //iBmp.Save("D:\\capture.png");
530 //Send it to our display
531 for (int i = 0; i < iBmp.Width; i++)
533 for (int j = 0; j < iBmp.Height; j++)
537 //Get our processed pixel coordinates
538 int x = iScreenX(iBmp, i);
539 int y = iScreenY(iBmp, j);
541 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
542 //Apply color effects
543 color = iColorFx(x,y,color);
545 iDisplay.SetPixel(x, y, color);
550 iDisplay.SwapBuffers();
554 //Compute instant FPS
555 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
557 LastTickTime = NewTickTime;
562 /// Attempt to establish connection with our display hardware.
564 private void OpenDisplayConnection()
566 CloseDisplayConnection();
568 if (!iDisplay.Open((Display.TMiniDisplayType)cds.DisplayType))
571 toolStripStatusLabelConnect.Text = "Connection error";
575 private void CloseDisplayConnection()
577 //Status will be updated upon receiving the closed event
581 private void buttonOpen_Click(object sender, EventArgs e)
583 OpenDisplayConnection();
586 private void buttonClose_Click(object sender, EventArgs e)
588 CloseDisplayConnection();
591 private void buttonClear_Click(object sender, EventArgs e)
594 iDisplay.SwapBuffers();
597 private void buttonFill_Click(object sender, EventArgs e)
600 iDisplay.SwapBuffers();
603 private void trackBarBrightness_Scroll(object sender, EventArgs e)
605 cds.Brightness = trackBarBrightness.Value;
606 Properties.Settings.Default.Save();
607 iDisplay.SetBrightness(trackBarBrightness.Value);
613 /// CDS stands for Current Display Settings
615 private DisplaySettings cds
619 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
620 if (settings == null)
622 settings = new DisplaysSettings();
624 Properties.Settings.Default.DisplaysSettings = settings;
627 //Make sure all our settings have been created
628 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
630 settings.Displays.Add(new DisplaySettings());
633 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
634 return displaySettings;
639 /// Check if the given font has a fixed character pitch.
641 /// <param name="ft"></param>
642 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
643 public float IsFixedWidth(Font ft)
645 Graphics g = CreateGraphics();
646 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
647 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
649 bool fixedWidth = true;
651 foreach (char c in charSizes)
652 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
664 /// Synchronize UI with settings
666 private void UpdateStatus()
669 checkBoxShowBorders.Checked = cds.ShowBorders;
670 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
672 //Set the proper font to each of our labels
673 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
675 ctrl.Font = cds.Font;
679 //Check if "run on Windows startup" is enabled
680 checkBoxAutoStart.Checked = iStartupManager.Startup;
682 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
683 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
684 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
685 checkBoxReverseScreen.Checked = cds.ReverseScreen;
686 checkBoxInverseColors.Checked = cds.InverseColors;
687 checkBoxScaleToFit.Checked = cds.ScaleToFit;
688 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
689 labelMinFontSize.Enabled = cds.ScaleToFit;
690 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
691 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
692 timer.Interval = cds.TimerInterval;
693 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
694 textBoxScrollLoopSeparator.Text = cds.Separator;
696 SetupPixelDelegates();
698 if (iDisplay.IsOpen())
700 //We have a display connection
701 //Reflect that in our UI
703 tableLayoutPanel.Enabled = true;
704 panelDisplay.Enabled = true;
706 //Only setup brightness if display is open
707 trackBarBrightness.Minimum = iDisplay.MinBrightness();
708 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
709 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
711 //Brightness out of range, this can occur when using auto-detect
712 //Use max brightness instead
713 trackBarBrightness.Value = iDisplay.MaxBrightness();
714 iDisplay.SetBrightness(iDisplay.MaxBrightness());
718 trackBarBrightness.Value = cds.Brightness;
719 iDisplay.SetBrightness(cds.Brightness);
722 //Try compute the steps to something that makes sense
723 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
724 trackBarBrightness.SmallChange = 1;
727 buttonFill.Enabled = true;
728 buttonClear.Enabled = true;
729 buttonOpen.Enabled = false;
730 buttonClose.Enabled = true;
731 trackBarBrightness.Enabled = true;
732 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
733 //+ " - " + iDisplay.SerialNumber();
735 if (iDisplay.SupportPowerOnOff())
737 buttonPowerOn.Enabled = true;
738 buttonPowerOff.Enabled = true;
742 buttonPowerOn.Enabled = false;
743 buttonPowerOff.Enabled = false;
746 if (iDisplay.SupportClock())
748 buttonShowClock.Enabled = true;
749 buttonHideClock.Enabled = true;
753 buttonShowClock.Enabled = false;
754 buttonHideClock.Enabled = false;
759 //Display is connection not available
760 //Reflect that in our UI
761 tableLayoutPanel.Enabled = false;
762 panelDisplay.Enabled = false;
763 buttonFill.Enabled = false;
764 buttonClear.Enabled = false;
765 buttonOpen.Enabled = true;
766 buttonClose.Enabled = false;
767 trackBarBrightness.Enabled = false;
768 buttonPowerOn.Enabled = false;
769 buttonPowerOff.Enabled = false;
770 buttonShowClock.Enabled = false;
771 buttonHideClock.Enabled = false;
772 toolStripStatusLabelConnect.Text = "Disconnected";
773 toolStripStatusLabelPower.Text = "N/A";
779 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
781 //Save our show borders setting
782 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
783 cds.ShowBorders = checkBoxShowBorders.Checked;
784 Properties.Settings.Default.Save();
788 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
790 //Save our connect on startup setting
791 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
792 Properties.Settings.Default.Save();
795 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
797 //Save our "Minimize to tray" setting
798 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
799 Properties.Settings.Default.Save();
803 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
805 //Save our "Start minimized" setting
806 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
807 Properties.Settings.Default.Save();
810 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
812 iStartupManager.Startup = checkBoxAutoStart.Checked;
816 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
818 //Save our reverse screen setting
819 cds.ReverseScreen = checkBoxReverseScreen.Checked;
820 Properties.Settings.Default.Save();
821 SetupPixelDelegates();
824 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
826 //Save our inverse colors setting
827 cds.InverseColors = checkBoxInverseColors.Checked;
828 Properties.Settings.Default.Save();
829 SetupPixelDelegates();
832 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
834 //Save our scale to fit setting
835 cds.ScaleToFit = checkBoxScaleToFit.Checked;
836 Properties.Settings.Default.Save();
838 labelMinFontSize.Enabled = cds.ScaleToFit;
839 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
842 private void MainForm_Resize(object sender, EventArgs e)
844 if (WindowState == FormWindowState.Minimized)
847 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
848 iCreateBitmap = true;
852 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
854 CloseDisplayConnection();
859 public void StartServer()
861 iServiceHost = new ServiceHost
864 new Uri[] { new Uri("net.tcp://localhost:8001/") }
867 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
871 public void StopServer()
873 if (iClients.Count > 0 && !iClosing)
877 BroadcastCloseEvent();
881 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
883 iClosing = false; //We make sure we force close if asked twice
888 //We removed that as it often lags for some reason
889 //iServiceHost.Close();
893 public void BroadcastCloseEvent()
895 Trace.TraceInformation("BroadcastCloseEvent - start");
897 var inactiveClients = new List<string>();
898 foreach (var client in iClients)
900 //if (client.Key != eventData.ClientName)
904 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
905 client.Value.Callback.OnCloseOrder(/*eventData*/);
909 inactiveClients.Add(client.Key);
914 if (inactiveClients.Count > 0)
916 foreach (var client in inactiveClients)
918 iClients.Remove(client);
919 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
923 if (iClients.Count==0)
930 /// Just remove all our fields.
932 private void ClearLayout()
934 tableLayoutPanel.Controls.Clear();
935 tableLayoutPanel.RowStyles.Clear();
936 tableLayoutPanel.ColumnStyles.Clear();
939 private void buttonStartClient_Click(object sender, EventArgs e)
941 Thread clientThread = new Thread(SharpDisplayClient.Program.Main);
942 clientThread.Start();
946 private void buttonSuspend_Click(object sender, EventArgs e)
948 LastTickTime = DateTime.Now; //Reset timer to prevent jump
949 timer.Enabled = !timer.Enabled;
952 buttonSuspend.Text = "Run";
956 buttonSuspend.Text = "Pause";
960 private void buttonCloseClients_Click(object sender, EventArgs e)
962 BroadcastCloseEvent();
965 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
974 /// <param name="aSessionId"></param>
975 /// <param name="aCallback"></param>
976 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
978 if (this.InvokeRequired)
980 //Not in the proper thread, invoke ourselves
981 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
982 this.Invoke(d, new object[] { aSessionId, aCallback });
986 //We are in the proper thread
987 //Add this session to our collection of clients
988 ClientData newClient = new ClientData(aSessionId, aCallback);
989 Program.iMainForm.iClients.Add(aSessionId, newClient);
990 //Add this session to our UI
991 UpdateClientTreeViewNode(newClient);
998 /// <param name="aSessionId"></param>
999 public void RemoveClientThreadSafe(string aSessionId)
1001 if (this.InvokeRequired)
1003 //Not in the proper thread, invoke ourselves
1004 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1005 this.Invoke(d, new object[] { aSessionId });
1009 //We are in the proper thread
1010 //Remove this session from both client collection and UI tree view
1011 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1013 Program.iMainForm.iClients.Remove(aSessionId);
1014 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
1017 if (iClients.Count == 0)
1019 //Clear our screen when last client disconnects
1024 //We were closing our form
1025 //All clients are now closed
1026 //Just resume our close operation
1037 /// <param name="aSessionId"></param>
1038 /// <param name="aLayout"></param>
1039 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1041 if (this.InvokeRequired)
1043 //Not in the proper thread, invoke ourselves
1044 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1045 this.Invoke(d, new object[] { aSessionId, aLayout });
1049 ClientData client = iClients[aSessionId];
1052 client.Layout = aLayout;
1053 UpdateTableLayoutPanel(client);
1055 UpdateClientTreeViewNode(client);
1063 /// <param name="aSessionId"></param>
1064 /// <param name="aField"></param>
1065 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1067 if (this.InvokeRequired)
1069 //Not in the proper thread, invoke ourselves
1070 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1071 this.Invoke(d, new object[] { aSessionId, aField });
1075 //We are in the proper thread
1076 //Call the non-thread-safe variant
1077 SetClientField(aSessionId, aField);
1084 /// <param name="aSessionId"></param>
1085 /// <param name="aField"></param>
1086 private void SetClientField(string aSessionId, DataField aField)
1088 SetCurrentClient(aSessionId);
1089 ClientData client = iClients[aSessionId];
1092 bool somethingChanged = false;
1094 //Make sure all our fields are in place
1095 while (client.Fields.Count < (aField.Index + 1))
1097 //Add a text field with proper index
1098 client.Fields.Add(new DataField(client.Fields.Count));
1099 somethingChanged = true;
1102 if (client.Fields[aField.Index].IsSameLayout(aField))
1104 //Same layout just update our field
1105 client.Fields[aField.Index] = aField;
1107 if (aField.IsText && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
1109 //Text field control already in place, just change the text
1110 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
1111 somethingChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
1112 label.Text = aField.Text;
1113 label.TextAlign = aField.Alignment;
1115 else if (aField.IsBitmap && tableLayoutPanel.Controls[aField.Index] is PictureBox)
1117 somethingChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1118 //Bitmap field control already in place just change the bitmap
1119 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
1120 pictureBox.Image = aField.Bitmap;
1124 somethingChanged = true;
1125 //The requested control in our layout it not of the correct type
1126 //Wrong control type, re-create them all
1127 UpdateTableLayoutPanel(iCurrentClientData);
1132 somethingChanged = true;
1133 //Different layout, need to rebuild it
1134 client.Fields[aField.Index] = aField;
1135 UpdateTableLayoutPanel(iCurrentClientData);
1139 if (somethingChanged)
1141 UpdateClientTreeViewNode(client);
1149 /// <param name="aTexts"></param>
1150 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1152 if (this.InvokeRequired)
1154 //Not in the proper thread, invoke ourselves
1155 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1156 this.Invoke(d, new object[] { aSessionId, aFields });
1160 //Put each our text fields in a label control
1161 foreach (DataField field in aFields)
1163 SetClientField(aSessionId, field);
1171 /// <param name="aSessionId"></param>
1172 /// <param name="aName"></param>
1173 public void SetClientNameThreadSafe(string aSessionId, string aName)
1175 if (this.InvokeRequired)
1177 //Not in the proper thread, invoke ourselves
1178 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1179 this.Invoke(d, new object[] { aSessionId, aName });
1183 //We are in the proper thread
1185 ClientData client = iClients[aSessionId];
1189 client.Name = aName;
1190 //Update our tree-view
1191 UpdateClientTreeViewNode(client);
1199 /// <param name="aClient"></param>
1200 private void UpdateClientTreeViewNode(ClientData aClient)
1202 if (aClient == null)
1207 TreeNode node = null;
1208 //Check that our client node already exists
1209 //Get our client root node using its key which is our session ID
1210 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1211 if (nodes.Count()>0)
1213 //We already have a node for that client
1215 //Clear children as we are going to recreate them below
1220 //Client node does not exists create a new one
1221 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1222 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1228 if (aClient.Name != "")
1230 //We have a name, us it as text for our root node
1231 node.Text = aClient.Name;
1232 //Add a child with SessionId
1233 node.Nodes.Add(new TreeNode(aClient.SessionId));
1237 //No name, use session ID instead
1238 node.Text = aClient.SessionId;
1241 if (aClient.Fields.Count > 0)
1243 //Create root node for our texts
1244 TreeNode textsRoot = new TreeNode("Fields");
1245 node.Nodes.Add(textsRoot);
1246 //For each text add a new entry
1247 foreach (DataField field in aClient.Fields)
1249 if (!field.IsBitmap)
1251 DataField textField = (DataField)field;
1252 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1256 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1265 private void buttonAddRow_Click(object sender, EventArgs e)
1267 if (tableLayoutPanel.RowCount < 6)
1269 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount + 1);
1273 private void buttonRemoveRow_Click(object sender, EventArgs e)
1275 if (tableLayoutPanel.RowCount > 1)
1277 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount - 1);
1280 UpdateTableLayoutRowStyles();
1283 private void buttonAddColumn_Click(object sender, EventArgs e)
1285 if (tableLayoutPanel.ColumnCount < 8)
1287 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount + 1, tableLayoutPanel.RowCount);
1291 private void buttonRemoveColumn_Click(object sender, EventArgs e)
1293 if (tableLayoutPanel.ColumnCount > 1)
1295 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount - 1, tableLayoutPanel.RowCount);
1301 /// Update our table layout row styles to make sure each rows have similar height
1303 private void UpdateTableLayoutRowStyles()
1305 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1307 rowStyle.SizeType = SizeType.Percent;
1308 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1314 /// Empty and recreate our table layout with the given number of columns and rows.
1315 /// Sizes of rows and columns are uniform.
1317 /// <param name="aColumn"></param>
1318 /// <param name="aRow"></param>
1319 private void UpdateTableLayoutPanel(int aColumn, int aRow)
1321 tableLayoutPanel.Controls.Clear();
1322 tableLayoutPanel.RowStyles.Clear();
1323 tableLayoutPanel.ColumnStyles.Clear();
1324 tableLayoutPanel.RowCount = 0;
1325 tableLayoutPanel.ColumnCount = 0;
1327 while (tableLayoutPanel.RowCount < aRow)
1329 tableLayoutPanel.RowCount++;
1332 while (tableLayoutPanel.ColumnCount < aColumn)
1334 tableLayoutPanel.ColumnCount++;
1337 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1339 //Create our column styles
1340 this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.ColumnCount));
1342 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1346 //Create our row styles
1347 this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.RowCount));
1350 MarqueeLabel control = new SharpDisplayManager.MarqueeLabel();
1351 control.AutoEllipsis = true;
1352 control.AutoSize = true;
1353 control.BackColor = System.Drawing.Color.Transparent;
1354 control.Dock = System.Windows.Forms.DockStyle.Fill;
1355 control.Location = new System.Drawing.Point(1, 1);
1356 control.Margin = new System.Windows.Forms.Padding(0);
1357 control.Name = "marqueeLabelCol" + aColumn + "Row" + aRow;
1358 control.OwnTimer = false;
1359 control.PixelsPerSecond = 64;
1360 control.Separator = cds.Separator;
1361 control.MinFontSize = cds.MinFontSize;
1362 control.ScaleToFit = cds.ScaleToFit;
1363 //control.Size = new System.Drawing.Size(254, 30);
1364 //control.TabIndex = 2;
1365 control.Font = cds.Font;
1366 control.Text = "ABCDEFGHIJKLMNOPQRST-0123456789";
1367 control.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1368 control.UseCompatibleTextRendering = true;
1370 tableLayoutPanel.Controls.Add(control, i, j);
1379 /// Update our display table layout.
1381 /// <param name="aLayout"></param>
1382 private void UpdateTableLayoutPanel(ClientData aClient)
1384 TableLayout layout = aClient.Layout;
1387 tableLayoutPanel.Controls.Clear();
1388 tableLayoutPanel.RowStyles.Clear();
1389 tableLayoutPanel.ColumnStyles.Clear();
1390 tableLayoutPanel.RowCount = 0;
1391 tableLayoutPanel.ColumnCount = 0;
1393 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1395 tableLayoutPanel.RowCount++;
1398 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1400 tableLayoutPanel.ColumnCount++;
1403 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1405 //Create our column styles
1406 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1408 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1412 //Create our row styles
1413 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1416 //Check if we already have a control
1417 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1418 if (existingControl!=null)
1420 //We already have a control in that cell as a results of row/col spanning
1421 //Move on to next cell then
1427 //Check if a client field already exists for that cell
1428 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1430 //No client field specified, create a text field by default
1431 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1434 //Create a control corresponding to the field specified for that cell
1435 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1436 Control control = CreateControlForDataField(field);
1438 //Add newly created control to our table layout at the specified row and column
1439 tableLayoutPanel.Controls.Add(control, i, j);
1440 //Make sure we specify row and column span for that new control
1441 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1442 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1447 while (aClient.Fields.Count > fieldCount)
1449 //We have too much fields for this layout
1450 //Just discard them until we get there
1451 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1458 /// Check our type of data field and create corresponding control
1460 /// <param name="aField"></param>
1461 private Control CreateControlForDataField(DataField aField)
1463 Control control=null;
1464 if (!aField.IsBitmap)
1466 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1467 label.AutoEllipsis = true;
1468 label.AutoSize = true;
1469 label.BackColor = System.Drawing.Color.Transparent;
1470 label.Dock = System.Windows.Forms.DockStyle.Fill;
1471 label.Location = new System.Drawing.Point(1, 1);
1472 label.Margin = new System.Windows.Forms.Padding(0);
1473 label.Name = "marqueeLabel" + aField.Index;
1474 label.OwnTimer = false;
1475 label.PixelsPerSecond = 64;
1476 label.Separator = cds.Separator;
1477 label.MinFontSize = cds.MinFontSize;
1478 label.ScaleToFit = cds.ScaleToFit;
1479 //control.Size = new System.Drawing.Size(254, 30);
1480 //control.TabIndex = 2;
1481 label.Font = cds.Font;
1483 label.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1484 label.UseCompatibleTextRendering = true;
1485 label.Text = aField.Text;
1491 //Create picture box
1492 PictureBox picture = new PictureBox();
1493 picture.AutoSize = true;
1494 picture.BackColor = System.Drawing.Color.Transparent;
1495 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1496 picture.Location = new System.Drawing.Point(1, 1);
1497 picture.Margin = new System.Windows.Forms.Padding(0);
1498 picture.Name = "pictureBox" + aField;
1500 picture.Image = aField.Bitmap;
1509 private void buttonAlignLeft_Click(object sender, EventArgs e)
1511 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1513 ctrl.TextAlign = ContentAlignment.MiddleLeft;
1517 private void buttonAlignCenter_Click(object sender, EventArgs e)
1519 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1521 ctrl.TextAlign = ContentAlignment.MiddleCenter;
1525 private void buttonAlignRight_Click(object sender, EventArgs e)
1527 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1529 ctrl.TextAlign = ContentAlignment.MiddleRight;
1534 /// Called when the user selected a new display type.
1536 /// <param name="sender"></param>
1537 /// <param name="e"></param>
1538 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1540 //Store the selected display type in our settings
1541 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1542 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1543 Properties.Settings.Default.Save();
1545 //Try re-opening the display connection if we were already connected.
1546 //Otherwise just update our status to reflect display type change.
1547 if (iDisplay.IsOpen())
1549 OpenDisplayConnection();
1557 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1559 if (maskedTextBoxTimerInterval.Text != "")
1561 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1565 timer.Interval = interval;
1566 cds.TimerInterval = timer.Interval;
1567 Properties.Settings.Default.Save();
1572 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
1574 if (maskedTextBoxMinFontSize.Text != "")
1576 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
1578 if (minFontSize > 0)
1580 //TODO: re-create layout? update our fields?
1581 cds.MinFontSize = minFontSize;
1582 Properties.Settings.Default.Save();
1587 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
1589 //TODO: re-create layout? update our fields?
1590 cds.Separator = textBoxScrollLoopSeparator.Text;
1591 Properties.Settings.Default.Save();
1594 private void buttonPowerOn_Click(object sender, EventArgs e)
1599 private void buttonPowerOff_Click(object sender, EventArgs e)
1601 iDisplay.PowerOff();
1604 private void buttonShowClock_Click(object sender, EventArgs e)
1606 iDisplay.ShowClock();
1609 private void buttonHideClock_Click(object sender, EventArgs e)
1611 iDisplay.HideClock();
1614 private void buttonUpdate_Click(object sender, EventArgs e)
1616 InstallUpdateSyncWithInfo();
1620 private void InstallUpdateSyncWithInfo()
1622 UpdateCheckInfo info = null;
1624 if (ApplicationDeployment.IsNetworkDeployed)
1626 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
1630 info = ad.CheckForDetailedUpdate();
1633 catch (DeploymentDownloadException dde)
1635 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);
1638 catch (InvalidDeploymentException ide)
1640 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);
1643 catch (InvalidOperationException ioe)
1645 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
1649 if (info.UpdateAvailable)
1651 Boolean doUpdate = true;
1653 if (!info.IsUpdateRequired)
1655 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
1656 if (!(DialogResult.OK == dr))
1663 // Display a message that the app MUST reboot. Display the minimum required version.
1664 MessageBox.Show("This application has detected a mandatory update from your current " +
1665 "version to version " + info.MinimumRequiredVersion.ToString() +
1666 ". The application will now install the update and restart.",
1667 "Update Available", MessageBoxButtons.OK,
1668 MessageBoxIcon.Information);
1676 MessageBox.Show("The application has been upgraded, and will now restart.");
1677 Application.Restart();
1679 catch (DeploymentDownloadException dde)
1681 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
1688 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
1697 private void SysTrayHideShow()
1703 WindowState = FormWindowState.Normal;
1708 /// Use to handle minimize events.
1710 /// <param name="sender"></param>
1711 /// <param name="e"></param>
1712 private void MainForm_SizeChanged(object sender, EventArgs e)
1714 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
1727 /// <param name="sender"></param>
1728 /// <param name="e"></param>
1729 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
1731 //Our table layout size has changed which means our display size has changed.
1732 //We need to re-create our bitmap.
1733 iCreateBitmap = true;
1739 /// A UI thread copy of a client relevant data.
1740 /// Keeping this copy in the UI thread helps us deal with threading issues.
1742 public class ClientData
1744 public ClientData(string aSessionId, ICallback aCallback)
1746 SessionId = aSessionId;
1748 Fields = new List<DataField>();
1749 Layout = new TableLayout(1, 2); //Default to one column and two rows
1750 Callback = aCallback;
1753 public string SessionId { get; set; }
1754 public string Name { get; set; }
1755 public List<DataField> Fields { get; set; }
1756 public TableLayout Layout { get; set; }
1757 public ICallback Callback { get; set; }