Cleaning up design tab by removing layout debug buttons.
Separator changes now reflected instantly.
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;
108 /// <param name="sender"></param>
109 /// <param name="e"></param>
110 private void MainForm_Load(object sender, EventArgs e)
112 //Check if we are running a Click Once deployed application
113 if (ApplicationDeployment.IsNetworkDeployed)
115 //This is a proper Click Once installation, fetch and show our version number
116 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
120 //Not a proper Click Once installation, assuming development build then
121 this.Text += " - development";
124 //Setup notification icon
127 // To make sure start up with minimize to tray works
128 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
134 //When not debugging we want the screen to be empty until a client takes over
137 //When developing we want at least one client for testing
138 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
141 //Open display connection on start-up if needed
142 if (Properties.Settings.Default.DisplayConnectOnStartup)
144 OpenDisplayConnection();
147 //Start our server so that we can get client requests
152 /// Called when our display is opened.
154 /// <param name="aDisplay"></param>
155 private void OnDisplayOpened(Display aDisplay)
157 //Set our screen size now that our display is connected
158 //Our panelDisplay is the container of our tableLayoutPanel
159 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
160 //panelDisplay needs an extra 2 pixels for borders on each sides
161 //tableLayoutPanel will eventually be the exact size of our display
162 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
163 panelDisplay.Size = size;
165 //Our display was just opened, update our UI
167 //Initiate asynchronous request
168 iDisplay.RequestFirmwareRevision();
171 //Testing icon in debug, no arm done if icon not supported
172 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
173 iDisplay.SetAllIconsStatus(2);
179 /// Called when our display is closed.
181 /// <param name="aDisplay"></param>
182 private void OnDisplayClosed(Display aDisplay)
184 //Our display was just closed, update our UI consequently
191 private void PopulateDeviceTypes()
193 int count = Display.TypeCount();
195 for (int i = 0; i < count; i++)
197 comboBoxDisplayType.Items.Add(Display.TypeName((Display.TMiniDisplayType)i));
204 private void SetupTrayIcon()
206 iNotifyIcon.Icon = GetIcon("vfd.ico");
207 iNotifyIcon.Text = "Sharp Display Manager";
208 iNotifyIcon.Visible = true;
210 //Double click toggles visibility - typically brings up the application
211 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
216 //Adding a context menu, useful to be able to exit the application
217 ContextMenu contextMenu = new ContextMenu();
218 //Context menu item to toggle visibility
219 MenuItem hideShowItem = new MenuItem("Hide/Show");
220 hideShowItem.Click += delegate(object obj, EventArgs args)
224 contextMenu.MenuItems.Add(hideShowItem);
226 //Context menu item separator
227 contextMenu.MenuItems.Add(new MenuItem("-"));
229 //Context menu exit item
230 MenuItem exitItem = new MenuItem("Exit");
231 exitItem.Click += delegate(object obj, EventArgs args)
235 contextMenu.MenuItems.Add(exitItem);
237 iNotifyIcon.ContextMenu = contextMenu;
241 /// Access icons from embedded resources.
243 /// <param name="name"></param>
244 /// <returns></returns>
245 public static Icon GetIcon(string name)
247 name = "SharpDisplayManager.Resources." + name;
250 Assembly.GetExecutingAssembly().GetManifestResourceNames();
251 for (int i = 0; i < names.Length; i++)
253 if (names[i].Replace('\\', '.') == name)
255 using (Stream stream = Assembly.GetExecutingAssembly().
256 GetManifestResourceStream(names[i]))
258 return new Icon(stream);
268 /// Set our current client.
269 /// This will take care of applying our client layout and set data fields.
271 /// <param name="aSessionId"></param>
272 void SetCurrentClient(string aSessionId)
274 if (aSessionId == iCurrentClientSessionId)
276 //Given client is already the current one.
277 //Don't bother changing anything then.
281 //Set current client ID.
282 iCurrentClientSessionId = aSessionId;
283 //Fetch and set current client data.
284 iCurrentClientData = iClients[aSessionId];
285 //Apply layout and set data fields.
286 UpdateTableLayoutPanel(iCurrentClientData);
289 private void buttonFont_Click(object sender, EventArgs e)
291 //fontDialog.ShowColor = true;
292 //fontDialog.ShowApply = true;
293 fontDialog.ShowEffects = true;
294 fontDialog.Font = cds.Font;
296 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
298 //fontDialog.ShowHelp = true;
300 //fontDlg.MaxSize = 40;
301 //fontDlg.MinSize = 22;
303 //fontDialog.Parent = this;
304 //fontDialog.StartPosition = FormStartPosition.CenterParent;
306 //DlgBox.ShowDialog(fontDialog);
308 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
309 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
311 //Set the fonts to all our labels in our layout
312 foreach (Control ctrl in tableLayoutPanel.Controls)
314 if (ctrl is MarqueeLabel)
316 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
321 cds.Font = fontDialog.Font;
322 Properties.Settings.Default.Save();
331 void CheckFontHeight()
333 //Show font height and width
334 labelFontHeight.Text = "Font height: " + cds.Font.Height;
335 float charWidth = IsFixedWidth(cds.Font);
336 if (charWidth == 0.0f)
338 labelFontWidth.Visible = false;
342 labelFontWidth.Visible = true;
343 labelFontWidth.Text = "Font width: " + charWidth;
346 MarqueeLabel label = null;
347 //Get the first label control we can find
348 foreach (Control ctrl in tableLayoutPanel.Controls)
350 if (ctrl is MarqueeLabel)
352 label = (MarqueeLabel)ctrl;
357 //Now check font height and show a warning if needed.
358 if (label != null && label.Font.Height > label.Height)
360 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
361 labelWarning.Visible = true;
365 labelWarning.Visible = false;
370 private void buttonCapture_Click(object sender, EventArgs e)
372 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
373 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
374 //Bitmap bmpToSave = new Bitmap(bmp);
375 bmp.Save("D:\\capture.png");
377 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
380 string outputFileName = "d:\\capture.png";
381 using (MemoryStream memory = new MemoryStream())
383 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
385 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
386 byte[] bytes = memory.ToArray();
387 fs.Write(bytes, 0, bytes.Length);
394 private void CheckForRequestResults()
396 if (iDisplay.IsRequestPending())
398 switch (iDisplay.AttemptRequestCompletion())
400 case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
401 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
402 //Issue next request then
403 iDisplay.RequestPowerSupplyStatus();
406 case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
407 if (iDisplay.PowerSupplyStatus())
409 toolStripStatusLabelPower.Text = "ON";
413 toolStripStatusLabelPower.Text = "OFF";
415 //Issue next request then
416 iDisplay.RequestDeviceId();
419 case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
420 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
421 //No more request to issue
427 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
429 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
436 public static uint ColorUntouched(int aX, int aY, uint aPixel)
441 public static uint ColorInversed(int aX, int aY, uint aPixel)
446 public static uint ColorChessboard(int aX, int aY, uint aPixel)
448 if ((aX % 2 == 0) && (aY % 2 == 0))
452 else if ((aX % 2 != 0) && (aY % 2 != 0))
460 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
462 return aBmp.Width - aX - 1;
465 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
467 return iBmp.Height - aY - 1;
470 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
475 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
481 /// Select proper pixel delegates according to our current settings.
483 private void SetupPixelDelegates()
485 //Select our pixel processing routine
486 if (cds.InverseColors)
488 //iColorFx = ColorChessboard;
489 iColorFx = ColorInversed;
493 iColorFx = ColorWhiteIsOn;
496 //Select proper coordinate translation functions
497 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
498 if (cds.ReverseScreen)
500 iScreenX = ScreenReversedX;
501 iScreenY = ScreenReversedY;
511 //This is our timer tick responsible to perform our render
512 private void timer_Tick(object sender, EventArgs e)
514 //Update our animations
515 DateTime NewTickTime = DateTime.Now;
517 //Update animation for all our marquees
518 foreach (Control ctrl in tableLayoutPanel.Controls)
520 if (ctrl is MarqueeLabel)
522 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
528 if (iDisplay.IsOpen())
530 CheckForRequestResults();
535 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
537 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
538 //iBmp.Save("D:\\capture.png");
540 //Send it to our display
541 for (int i = 0; i < iBmp.Width; i++)
543 for (int j = 0; j < iBmp.Height; j++)
547 //Get our processed pixel coordinates
548 int x = iScreenX(iBmp, i);
549 int y = iScreenY(iBmp, j);
551 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
552 //Apply color effects
553 color = iColorFx(x,y,color);
555 iDisplay.SetPixel(x, y, color);
560 iDisplay.SwapBuffers();
564 //Compute instant FPS
565 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
567 LastTickTime = NewTickTime;
572 /// Attempt to establish connection with our display hardware.
574 private void OpenDisplayConnection()
576 CloseDisplayConnection();
578 if (!iDisplay.Open((Display.TMiniDisplayType)cds.DisplayType))
581 toolStripStatusLabelConnect.Text = "Connection error";
585 private void CloseDisplayConnection()
587 //Status will be updated upon receiving the closed event
591 private void buttonOpen_Click(object sender, EventArgs e)
593 OpenDisplayConnection();
596 private void buttonClose_Click(object sender, EventArgs e)
598 CloseDisplayConnection();
601 private void buttonClear_Click(object sender, EventArgs e)
604 iDisplay.SwapBuffers();
607 private void buttonFill_Click(object sender, EventArgs e)
610 iDisplay.SwapBuffers();
613 private void trackBarBrightness_Scroll(object sender, EventArgs e)
615 cds.Brightness = trackBarBrightness.Value;
616 Properties.Settings.Default.Save();
617 iDisplay.SetBrightness(trackBarBrightness.Value);
623 /// CDS stands for Current Display Settings
625 private DisplaySettings cds
629 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
630 if (settings == null)
632 settings = new DisplaysSettings();
634 Properties.Settings.Default.DisplaysSettings = settings;
637 //Make sure all our settings have been created
638 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
640 settings.Displays.Add(new DisplaySettings());
643 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
644 return displaySettings;
649 /// Check if the given font has a fixed character pitch.
651 /// <param name="ft"></param>
652 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
653 public float IsFixedWidth(Font ft)
655 Graphics g = CreateGraphics();
656 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
657 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
659 bool fixedWidth = true;
661 foreach (char c in charSizes)
662 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
674 /// Synchronize UI with settings
676 private void UpdateStatus()
679 checkBoxShowBorders.Checked = cds.ShowBorders;
680 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
682 //Set the proper font to each of our labels
683 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
685 ctrl.Font = cds.Font;
689 //Check if "run on Windows startup" is enabled
690 checkBoxAutoStart.Checked = iStartupManager.Startup;
692 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
693 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
694 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
695 checkBoxReverseScreen.Checked = cds.ReverseScreen;
696 checkBoxInverseColors.Checked = cds.InverseColors;
697 checkBoxScaleToFit.Checked = cds.ScaleToFit;
698 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
699 labelMinFontSize.Enabled = cds.ScaleToFit;
700 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
701 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
702 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
703 timer.Interval = cds.TimerInterval;
704 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
705 textBoxScrollLoopSeparator.Text = cds.Separator;
707 SetupPixelDelegates();
709 if (iDisplay.IsOpen())
711 //We have a display connection
712 //Reflect that in our UI
714 tableLayoutPanel.Enabled = true;
715 panelDisplay.Enabled = true;
717 //Only setup brightness if display is open
718 trackBarBrightness.Minimum = iDisplay.MinBrightness();
719 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
720 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
722 //Brightness out of range, this can occur when using auto-detect
723 //Use max brightness instead
724 trackBarBrightness.Value = iDisplay.MaxBrightness();
725 iDisplay.SetBrightness(iDisplay.MaxBrightness());
729 trackBarBrightness.Value = cds.Brightness;
730 iDisplay.SetBrightness(cds.Brightness);
733 //Try compute the steps to something that makes sense
734 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
735 trackBarBrightness.SmallChange = 1;
738 buttonFill.Enabled = true;
739 buttonClear.Enabled = true;
740 buttonOpen.Enabled = false;
741 buttonClose.Enabled = true;
742 trackBarBrightness.Enabled = true;
743 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
744 //+ " - " + iDisplay.SerialNumber();
746 if (iDisplay.SupportPowerOnOff())
748 buttonPowerOn.Enabled = true;
749 buttonPowerOff.Enabled = true;
753 buttonPowerOn.Enabled = false;
754 buttonPowerOff.Enabled = false;
757 if (iDisplay.SupportClock())
759 buttonShowClock.Enabled = true;
760 buttonHideClock.Enabled = true;
764 buttonShowClock.Enabled = false;
765 buttonHideClock.Enabled = false;
770 //Display is connection not available
771 //Reflect that in our UI
772 tableLayoutPanel.Enabled = false;
773 panelDisplay.Enabled = false;
774 buttonFill.Enabled = false;
775 buttonClear.Enabled = false;
776 buttonOpen.Enabled = true;
777 buttonClose.Enabled = false;
778 trackBarBrightness.Enabled = false;
779 buttonPowerOn.Enabled = false;
780 buttonPowerOff.Enabled = false;
781 buttonShowClock.Enabled = false;
782 buttonHideClock.Enabled = false;
783 toolStripStatusLabelConnect.Text = "Disconnected";
784 toolStripStatusLabelPower.Text = "N/A";
791 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
793 //Save our show borders setting
794 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
795 cds.ShowBorders = checkBoxShowBorders.Checked;
796 Properties.Settings.Default.Save();
800 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
802 //Save our connect on startup setting
803 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
804 Properties.Settings.Default.Save();
807 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
809 //Save our "Minimize to tray" setting
810 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
811 Properties.Settings.Default.Save();
815 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
817 //Save our "Start minimized" setting
818 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
819 Properties.Settings.Default.Save();
822 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
824 iStartupManager.Startup = checkBoxAutoStart.Checked;
828 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
830 //Save our reverse screen setting
831 cds.ReverseScreen = checkBoxReverseScreen.Checked;
832 Properties.Settings.Default.Save();
833 SetupPixelDelegates();
836 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
838 //Save our inverse colors setting
839 cds.InverseColors = checkBoxInverseColors.Checked;
840 Properties.Settings.Default.Save();
841 SetupPixelDelegates();
844 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
846 //Save our scale to fit setting
847 cds.ScaleToFit = checkBoxScaleToFit.Checked;
848 Properties.Settings.Default.Save();
850 labelMinFontSize.Enabled = cds.ScaleToFit;
851 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
854 private void MainForm_Resize(object sender, EventArgs e)
856 if (WindowState == FormWindowState.Minimized)
859 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
860 iCreateBitmap = true;
864 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
866 CloseDisplayConnection();
871 public void StartServer()
873 iServiceHost = new ServiceHost
876 new Uri[] { new Uri("net.tcp://localhost:8001/") }
879 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
883 public void StopServer()
885 if (iClients.Count > 0 && !iClosing)
889 BroadcastCloseEvent();
893 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
895 iClosing = false; //We make sure we force close if asked twice
900 //We removed that as it often lags for some reason
901 //iServiceHost.Close();
905 public void BroadcastCloseEvent()
907 Trace.TraceInformation("BroadcastCloseEvent - start");
909 var inactiveClients = new List<string>();
910 foreach (var client in iClients)
912 //if (client.Key != eventData.ClientName)
916 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
917 client.Value.Callback.OnCloseOrder(/*eventData*/);
921 inactiveClients.Add(client.Key);
926 if (inactiveClients.Count > 0)
928 foreach (var client in inactiveClients)
930 iClients.Remove(client);
931 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
935 if (iClients.Count==0)
942 /// Just remove all our fields.
944 private void ClearLayout()
946 tableLayoutPanel.Controls.Clear();
947 tableLayoutPanel.RowStyles.Clear();
948 tableLayoutPanel.ColumnStyles.Clear();
952 /// Just launch a demo client.
954 private void StartNewClient(string aTopText = "", string aBottomText = "")
956 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
957 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
958 clientThread.Start(myParams);
962 private void buttonStartClient_Click(object sender, EventArgs e)
967 private void buttonSuspend_Click(object sender, EventArgs e)
969 LastTickTime = DateTime.Now; //Reset timer to prevent jump
970 timer.Enabled = !timer.Enabled;
973 buttonSuspend.Text = "Run";
977 buttonSuspend.Text = "Pause";
981 private void buttonCloseClients_Click(object sender, EventArgs e)
983 BroadcastCloseEvent();
986 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
995 /// <param name="aSessionId"></param>
996 /// <param name="aCallback"></param>
997 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
999 if (this.InvokeRequired)
1001 //Not in the proper thread, invoke ourselves
1002 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1003 this.Invoke(d, new object[] { aSessionId, aCallback });
1007 //We are in the proper thread
1008 //Add this session to our collection of clients
1009 ClientData newClient = new ClientData(aSessionId, aCallback);
1010 Program.iMainForm.iClients.Add(aSessionId, newClient);
1011 //Add this session to our UI
1012 UpdateClientTreeViewNode(newClient);
1019 /// <param name="aSessionId"></param>
1020 public void RemoveClientThreadSafe(string aSessionId)
1022 if (this.InvokeRequired)
1024 //Not in the proper thread, invoke ourselves
1025 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1026 this.Invoke(d, new object[] { aSessionId });
1030 //We are in the proper thread
1031 //Remove this session from both client collection and UI tree view
1032 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1034 Program.iMainForm.iClients.Remove(aSessionId);
1035 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
1038 if (iClients.Count == 0)
1040 //Clear our screen when last client disconnects
1045 //We were closing our form
1046 //All clients are now closed
1047 //Just resume our close operation
1058 /// <param name="aSessionId"></param>
1059 /// <param name="aLayout"></param>
1060 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1062 if (this.InvokeRequired)
1064 //Not in the proper thread, invoke ourselves
1065 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1066 this.Invoke(d, new object[] { aSessionId, aLayout });
1070 ClientData client = iClients[aSessionId];
1073 client.Layout = aLayout;
1074 UpdateTableLayoutPanel(client);
1076 UpdateClientTreeViewNode(client);
1084 /// <param name="aSessionId"></param>
1085 /// <param name="aField"></param>
1086 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1088 if (this.InvokeRequired)
1090 //Not in the proper thread, invoke ourselves
1091 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1092 this.Invoke(d, new object[] { aSessionId, aField });
1096 //We are in the proper thread
1097 //Call the non-thread-safe variant
1098 SetClientField(aSessionId, aField);
1105 /// <param name="aSessionId"></param>
1106 /// <param name="aField"></param>
1107 private void SetClientField(string aSessionId, DataField aField)
1109 SetCurrentClient(aSessionId);
1110 ClientData client = iClients[aSessionId];
1113 bool somethingChanged = false;
1115 //Make sure all our fields are in place
1116 while (client.Fields.Count < (aField.Index + 1))
1118 //Add a text field with proper index
1119 client.Fields.Add(new DataField(client.Fields.Count));
1120 somethingChanged = true;
1123 if (client.Fields[aField.Index].IsSameLayout(aField))
1125 //Same layout just update our field
1126 client.Fields[aField.Index] = aField;
1128 if (aField.IsText && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
1130 //Text field control already in place, just change the text
1131 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
1132 somethingChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
1133 label.Text = aField.Text;
1134 label.TextAlign = aField.Alignment;
1136 else if (aField.IsBitmap && tableLayoutPanel.Controls[aField.Index] is PictureBox)
1138 somethingChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1139 //Bitmap field control already in place just change the bitmap
1140 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
1141 pictureBox.Image = aField.Bitmap;
1145 somethingChanged = true;
1146 //The requested control in our layout it not of the correct type
1147 //Wrong control type, re-create them all
1148 UpdateTableLayoutPanel(iCurrentClientData);
1153 somethingChanged = true;
1154 //Different layout, need to rebuild it
1155 client.Fields[aField.Index] = aField;
1156 UpdateTableLayoutPanel(iCurrentClientData);
1160 if (somethingChanged)
1162 UpdateClientTreeViewNode(client);
1170 /// <param name="aTexts"></param>
1171 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1173 if (this.InvokeRequired)
1175 //Not in the proper thread, invoke ourselves
1176 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1177 this.Invoke(d, new object[] { aSessionId, aFields });
1181 //Put each our text fields in a label control
1182 foreach (DataField field in aFields)
1184 SetClientField(aSessionId, field);
1192 /// <param name="aSessionId"></param>
1193 /// <param name="aName"></param>
1194 public void SetClientNameThreadSafe(string aSessionId, string aName)
1196 if (this.InvokeRequired)
1198 //Not in the proper thread, invoke ourselves
1199 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1200 this.Invoke(d, new object[] { aSessionId, aName });
1204 //We are in the proper thread
1206 ClientData client = iClients[aSessionId];
1210 client.Name = aName;
1211 //Update our tree-view
1212 UpdateClientTreeViewNode(client);
1220 /// <param name="aClient"></param>
1221 private void UpdateClientTreeViewNode(ClientData aClient)
1223 if (aClient == null)
1228 TreeNode node = null;
1229 //Check that our client node already exists
1230 //Get our client root node using its key which is our session ID
1231 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1232 if (nodes.Count()>0)
1234 //We already have a node for that client
1236 //Clear children as we are going to recreate them below
1241 //Client node does not exists create a new one
1242 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1243 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1249 if (aClient.Name != "")
1251 //We have a name, us it as text for our root node
1252 node.Text = aClient.Name;
1253 //Add a child with SessionId
1254 node.Nodes.Add(new TreeNode(aClient.SessionId));
1258 //No name, use session ID instead
1259 node.Text = aClient.SessionId;
1262 if (aClient.Fields.Count > 0)
1264 //Create root node for our texts
1265 TreeNode textsRoot = new TreeNode("Fields");
1266 node.Nodes.Add(textsRoot);
1267 //For each text add a new entry
1268 foreach (DataField field in aClient.Fields)
1270 if (!field.IsBitmap)
1272 DataField textField = (DataField)field;
1273 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1277 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1287 /// Update our table layout row styles to make sure each rows have similar height
1289 private void UpdateTableLayoutRowStyles()
1291 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1293 rowStyle.SizeType = SizeType.Percent;
1294 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1300 /// Empty and recreate our table layout with the given number of columns and rows.
1301 /// Sizes of rows and columns are uniform.
1303 /// <param name="aColumn"></param>
1304 /// <param name="aRow"></param>
1305 private void UpdateTableLayoutPanel(int aColumn, int aRow)
1307 tableLayoutPanel.Controls.Clear();
1308 tableLayoutPanel.RowStyles.Clear();
1309 tableLayoutPanel.ColumnStyles.Clear();
1310 tableLayoutPanel.RowCount = 0;
1311 tableLayoutPanel.ColumnCount = 0;
1313 while (tableLayoutPanel.RowCount < aRow)
1315 tableLayoutPanel.RowCount++;
1318 while (tableLayoutPanel.ColumnCount < aColumn)
1320 tableLayoutPanel.ColumnCount++;
1323 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1325 //Create our column styles
1326 this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.ColumnCount));
1328 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1332 //Create our row styles
1333 this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.RowCount));
1336 MarqueeLabel control = new SharpDisplayManager.MarqueeLabel();
1337 control.AutoEllipsis = true;
1338 control.AutoSize = true;
1339 control.BackColor = System.Drawing.Color.Transparent;
1340 control.Dock = System.Windows.Forms.DockStyle.Fill;
1341 control.Location = new System.Drawing.Point(1, 1);
1342 control.Margin = new System.Windows.Forms.Padding(0);
1343 control.Name = "marqueeLabelCol" + aColumn + "Row" + aRow;
1344 control.OwnTimer = false;
1345 control.PixelsPerSecond = 64;
1346 control.Separator = cds.Separator;
1347 control.MinFontSize = cds.MinFontSize;
1348 control.ScaleToFit = cds.ScaleToFit;
1349 //control.Size = new System.Drawing.Size(254, 30);
1350 //control.TabIndex = 2;
1351 control.Font = cds.Font;
1352 control.Text = "ABCDEFGHIJKLMNOPQRST-0123456789";
1353 control.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1354 control.UseCompatibleTextRendering = true;
1356 tableLayoutPanel.Controls.Add(control, i, j);
1365 /// Update our display table layout.
1367 /// <param name="aLayout"></param>
1368 private void UpdateTableLayoutPanel(ClientData aClient)
1370 if (aClient == null)
1377 TableLayout layout = aClient.Layout;
1380 tableLayoutPanel.Controls.Clear();
1381 tableLayoutPanel.RowStyles.Clear();
1382 tableLayoutPanel.ColumnStyles.Clear();
1383 tableLayoutPanel.RowCount = 0;
1384 tableLayoutPanel.ColumnCount = 0;
1386 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1388 tableLayoutPanel.RowCount++;
1391 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1393 tableLayoutPanel.ColumnCount++;
1396 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1398 //Create our column styles
1399 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1401 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1405 //Create our row styles
1406 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1409 //Check if we already have a control
1410 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1411 if (existingControl!=null)
1413 //We already have a control in that cell as a results of row/col spanning
1414 //Move on to next cell then
1420 //Check if a client field already exists for that cell
1421 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1423 //No client field specified, create a text field by default
1424 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1427 //Create a control corresponding to the field specified for that cell
1428 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1429 Control control = CreateControlForDataField(field);
1431 //Add newly created control to our table layout at the specified row and column
1432 tableLayoutPanel.Controls.Add(control, i, j);
1433 //Make sure we specify row and column span for that new control
1434 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1435 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1440 while (aClient.Fields.Count > fieldCount)
1442 //We have too much fields for this layout
1443 //Just discard them until we get there
1444 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1451 /// Check our type of data field and create corresponding control
1453 /// <param name="aField"></param>
1454 private Control CreateControlForDataField(DataField aField)
1456 Control control=null;
1457 if (!aField.IsBitmap)
1459 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1460 label.AutoEllipsis = true;
1461 label.AutoSize = true;
1462 label.BackColor = System.Drawing.Color.Transparent;
1463 label.Dock = System.Windows.Forms.DockStyle.Fill;
1464 label.Location = new System.Drawing.Point(1, 1);
1465 label.Margin = new System.Windows.Forms.Padding(0);
1466 label.Name = "marqueeLabel" + aField.Index;
1467 label.OwnTimer = false;
1468 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
1469 label.Separator = cds.Separator;
1470 label.MinFontSize = cds.MinFontSize;
1471 label.ScaleToFit = cds.ScaleToFit;
1472 //control.Size = new System.Drawing.Size(254, 30);
1473 //control.TabIndex = 2;
1474 label.Font = cds.Font;
1476 label.TextAlign = aField.Alignment;
1477 label.UseCompatibleTextRendering = true;
1478 label.Text = aField.Text;
1484 //Create picture box
1485 PictureBox picture = new PictureBox();
1486 picture.AutoSize = true;
1487 picture.BackColor = System.Drawing.Color.Transparent;
1488 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1489 picture.Location = new System.Drawing.Point(1, 1);
1490 picture.Margin = new System.Windows.Forms.Padding(0);
1491 picture.Name = "pictureBox" + aField;
1493 picture.Image = aField.Bitmap;
1502 /// Called when the user selected a new display type.
1504 /// <param name="sender"></param>
1505 /// <param name="e"></param>
1506 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1508 //Store the selected display type in our settings
1509 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1510 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1511 Properties.Settings.Default.Save();
1513 //Try re-opening the display connection if we were already connected.
1514 //Otherwise just update our status to reflect display type change.
1515 if (iDisplay.IsOpen())
1517 OpenDisplayConnection();
1525 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1527 if (maskedTextBoxTimerInterval.Text != "")
1529 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1533 timer.Interval = interval;
1534 cds.TimerInterval = timer.Interval;
1535 Properties.Settings.Default.Save();
1540 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
1542 if (maskedTextBoxMinFontSize.Text != "")
1544 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
1546 if (minFontSize > 0)
1548 cds.MinFontSize = minFontSize;
1549 Properties.Settings.Default.Save();
1550 //We need to recreate our layout for that change to take effect
1551 UpdateTableLayoutPanel(iCurrentClientData);
1557 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
1559 if (maskedTextBoxScrollingSpeed.Text != "")
1561 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
1563 if (scrollingSpeed > 0)
1565 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
1566 Properties.Settings.Default.Save();
1567 //We need to recreate our layout for that change to take effect
1568 UpdateTableLayoutPanel(iCurrentClientData);
1573 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
1575 cds.Separator = textBoxScrollLoopSeparator.Text;
1576 Properties.Settings.Default.Save();
1578 //Update our text fields
1579 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1581 ctrl.Separator = cds.Separator;
1586 private void buttonPowerOn_Click(object sender, EventArgs e)
1591 private void buttonPowerOff_Click(object sender, EventArgs e)
1593 iDisplay.PowerOff();
1596 private void buttonShowClock_Click(object sender, EventArgs e)
1598 iDisplay.ShowClock();
1601 private void buttonHideClock_Click(object sender, EventArgs e)
1603 iDisplay.HideClock();
1606 private void buttonUpdate_Click(object sender, EventArgs e)
1608 InstallUpdateSyncWithInfo();
1612 private void InstallUpdateSyncWithInfo()
1614 UpdateCheckInfo info = null;
1616 if (ApplicationDeployment.IsNetworkDeployed)
1618 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
1622 info = ad.CheckForDetailedUpdate();
1625 catch (DeploymentDownloadException dde)
1627 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);
1630 catch (InvalidDeploymentException ide)
1632 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);
1635 catch (InvalidOperationException ioe)
1637 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
1641 if (info.UpdateAvailable)
1643 Boolean doUpdate = true;
1645 if (!info.IsUpdateRequired)
1647 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
1648 if (!(DialogResult.OK == dr))
1655 // Display a message that the app MUST reboot. Display the minimum required version.
1656 MessageBox.Show("This application has detected a mandatory update from your current " +
1657 "version to version " + info.MinimumRequiredVersion.ToString() +
1658 ". The application will now install the update and restart.",
1659 "Update Available", MessageBoxButtons.OK,
1660 MessageBoxIcon.Information);
1668 MessageBox.Show("The application has been upgraded, and will now restart.");
1669 Application.Restart();
1671 catch (DeploymentDownloadException dde)
1673 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
1680 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
1689 private void SysTrayHideShow()
1695 WindowState = FormWindowState.Normal;
1700 /// Use to handle minimize events.
1702 /// <param name="sender"></param>
1703 /// <param name="e"></param>
1704 private void MainForm_SizeChanged(object sender, EventArgs e)
1706 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
1719 /// <param name="sender"></param>
1720 /// <param name="e"></param>
1721 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
1723 //Our table layout size has changed which means our display size has changed.
1724 //We need to re-create our bitmap.
1725 iCreateBitmap = true;
1731 /// A UI thread copy of a client relevant data.
1732 /// Keeping this copy in the UI thread helps us deal with threading issues.
1734 public class ClientData
1736 public ClientData(string aSessionId, ICallback aCallback)
1738 SessionId = aSessionId;
1740 Fields = new List<DataField>();
1741 Layout = new TableLayout(1, 2); //Default to one column and two rows
1742 Callback = aCallback;
1745 public string SessionId { get; set; }
1746 public string Name { get; set; }
1747 public List<DataField> Fields { get; set; }
1748 public TableLayout Layout { get; set; }
1749 public ICallback Callback { get; set; }