MiniDisplay: Improved icon APIs.
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(1);
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]"));
1286 private void buttonAddRow_Click(object sender, EventArgs e)
1288 if (tableLayoutPanel.RowCount < 6)
1290 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount + 1);
1294 private void buttonRemoveRow_Click(object sender, EventArgs e)
1296 if (tableLayoutPanel.RowCount > 1)
1298 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount - 1);
1301 UpdateTableLayoutRowStyles();
1304 private void buttonAddColumn_Click(object sender, EventArgs e)
1306 if (tableLayoutPanel.ColumnCount < 8)
1308 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount + 1, tableLayoutPanel.RowCount);
1312 private void buttonRemoveColumn_Click(object sender, EventArgs e)
1314 if (tableLayoutPanel.ColumnCount > 1)
1316 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount - 1, tableLayoutPanel.RowCount);
1322 /// Update our table layout row styles to make sure each rows have similar height
1324 private void UpdateTableLayoutRowStyles()
1326 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1328 rowStyle.SizeType = SizeType.Percent;
1329 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1335 /// Empty and recreate our table layout with the given number of columns and rows.
1336 /// Sizes of rows and columns are uniform.
1338 /// <param name="aColumn"></param>
1339 /// <param name="aRow"></param>
1340 private void UpdateTableLayoutPanel(int aColumn, int aRow)
1342 tableLayoutPanel.Controls.Clear();
1343 tableLayoutPanel.RowStyles.Clear();
1344 tableLayoutPanel.ColumnStyles.Clear();
1345 tableLayoutPanel.RowCount = 0;
1346 tableLayoutPanel.ColumnCount = 0;
1348 while (tableLayoutPanel.RowCount < aRow)
1350 tableLayoutPanel.RowCount++;
1353 while (tableLayoutPanel.ColumnCount < aColumn)
1355 tableLayoutPanel.ColumnCount++;
1358 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1360 //Create our column styles
1361 this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.ColumnCount));
1363 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1367 //Create our row styles
1368 this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.RowCount));
1371 MarqueeLabel control = new SharpDisplayManager.MarqueeLabel();
1372 control.AutoEllipsis = true;
1373 control.AutoSize = true;
1374 control.BackColor = System.Drawing.Color.Transparent;
1375 control.Dock = System.Windows.Forms.DockStyle.Fill;
1376 control.Location = new System.Drawing.Point(1, 1);
1377 control.Margin = new System.Windows.Forms.Padding(0);
1378 control.Name = "marqueeLabelCol" + aColumn + "Row" + aRow;
1379 control.OwnTimer = false;
1380 control.PixelsPerSecond = 64;
1381 control.Separator = cds.Separator;
1382 control.MinFontSize = cds.MinFontSize;
1383 control.ScaleToFit = cds.ScaleToFit;
1384 //control.Size = new System.Drawing.Size(254, 30);
1385 //control.TabIndex = 2;
1386 control.Font = cds.Font;
1387 control.Text = "ABCDEFGHIJKLMNOPQRST-0123456789";
1388 control.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1389 control.UseCompatibleTextRendering = true;
1391 tableLayoutPanel.Controls.Add(control, i, j);
1400 /// Update our display table layout.
1402 /// <param name="aLayout"></param>
1403 private void UpdateTableLayoutPanel(ClientData aClient)
1405 if (aClient == null)
1412 TableLayout layout = aClient.Layout;
1415 tableLayoutPanel.Controls.Clear();
1416 tableLayoutPanel.RowStyles.Clear();
1417 tableLayoutPanel.ColumnStyles.Clear();
1418 tableLayoutPanel.RowCount = 0;
1419 tableLayoutPanel.ColumnCount = 0;
1421 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1423 tableLayoutPanel.RowCount++;
1426 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1428 tableLayoutPanel.ColumnCount++;
1431 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1433 //Create our column styles
1434 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1436 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1440 //Create our row styles
1441 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1444 //Check if we already have a control
1445 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1446 if (existingControl!=null)
1448 //We already have a control in that cell as a results of row/col spanning
1449 //Move on to next cell then
1455 //Check if a client field already exists for that cell
1456 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1458 //No client field specified, create a text field by default
1459 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1462 //Create a control corresponding to the field specified for that cell
1463 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1464 Control control = CreateControlForDataField(field);
1466 //Add newly created control to our table layout at the specified row and column
1467 tableLayoutPanel.Controls.Add(control, i, j);
1468 //Make sure we specify row and column span for that new control
1469 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1470 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1475 while (aClient.Fields.Count > fieldCount)
1477 //We have too much fields for this layout
1478 //Just discard them until we get there
1479 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1486 /// Check our type of data field and create corresponding control
1488 /// <param name="aField"></param>
1489 private Control CreateControlForDataField(DataField aField)
1491 Control control=null;
1492 if (!aField.IsBitmap)
1494 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1495 label.AutoEllipsis = true;
1496 label.AutoSize = true;
1497 label.BackColor = System.Drawing.Color.Transparent;
1498 label.Dock = System.Windows.Forms.DockStyle.Fill;
1499 label.Location = new System.Drawing.Point(1, 1);
1500 label.Margin = new System.Windows.Forms.Padding(0);
1501 label.Name = "marqueeLabel" + aField.Index;
1502 label.OwnTimer = false;
1503 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
1504 label.Separator = cds.Separator;
1505 label.MinFontSize = cds.MinFontSize;
1506 label.ScaleToFit = cds.ScaleToFit;
1507 //control.Size = new System.Drawing.Size(254, 30);
1508 //control.TabIndex = 2;
1509 label.Font = cds.Font;
1511 label.TextAlign = aField.Alignment;
1512 label.UseCompatibleTextRendering = true;
1513 label.Text = aField.Text;
1519 //Create picture box
1520 PictureBox picture = new PictureBox();
1521 picture.AutoSize = true;
1522 picture.BackColor = System.Drawing.Color.Transparent;
1523 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1524 picture.Location = new System.Drawing.Point(1, 1);
1525 picture.Margin = new System.Windows.Forms.Padding(0);
1526 picture.Name = "pictureBox" + aField;
1528 picture.Image = aField.Bitmap;
1537 private void buttonAlignLeft_Click(object sender, EventArgs e)
1539 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1541 ctrl.TextAlign = ContentAlignment.MiddleLeft;
1545 private void buttonAlignCenter_Click(object sender, EventArgs e)
1547 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1549 ctrl.TextAlign = ContentAlignment.MiddleCenter;
1553 private void buttonAlignRight_Click(object sender, EventArgs e)
1555 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1557 ctrl.TextAlign = ContentAlignment.MiddleRight;
1562 /// Called when the user selected a new display type.
1564 /// <param name="sender"></param>
1565 /// <param name="e"></param>
1566 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1568 //Store the selected display type in our settings
1569 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1570 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1571 Properties.Settings.Default.Save();
1573 //Try re-opening the display connection if we were already connected.
1574 //Otherwise just update our status to reflect display type change.
1575 if (iDisplay.IsOpen())
1577 OpenDisplayConnection();
1585 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1587 if (maskedTextBoxTimerInterval.Text != "")
1589 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1593 timer.Interval = interval;
1594 cds.TimerInterval = timer.Interval;
1595 Properties.Settings.Default.Save();
1600 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
1602 if (maskedTextBoxMinFontSize.Text != "")
1604 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
1606 if (minFontSize > 0)
1608 cds.MinFontSize = minFontSize;
1609 Properties.Settings.Default.Save();
1610 //We need to recreate our layout for that change to take effect
1611 UpdateTableLayoutPanel(iCurrentClientData);
1617 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
1619 if (maskedTextBoxScrollingSpeed.Text != "")
1621 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
1623 if (scrollingSpeed > 0)
1625 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
1626 Properties.Settings.Default.Save();
1627 //We need to recreate our layout for that change to take effect
1628 UpdateTableLayoutPanel(iCurrentClientData);
1633 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
1635 //TODO: re-create layout? update our fields?
1636 cds.Separator = textBoxScrollLoopSeparator.Text;
1637 Properties.Settings.Default.Save();
1640 private void buttonPowerOn_Click(object sender, EventArgs e)
1645 private void buttonPowerOff_Click(object sender, EventArgs e)
1647 iDisplay.PowerOff();
1650 private void buttonShowClock_Click(object sender, EventArgs e)
1652 iDisplay.ShowClock();
1655 private void buttonHideClock_Click(object sender, EventArgs e)
1657 iDisplay.HideClock();
1660 private void buttonUpdate_Click(object sender, EventArgs e)
1662 InstallUpdateSyncWithInfo();
1666 private void InstallUpdateSyncWithInfo()
1668 UpdateCheckInfo info = null;
1670 if (ApplicationDeployment.IsNetworkDeployed)
1672 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
1676 info = ad.CheckForDetailedUpdate();
1679 catch (DeploymentDownloadException dde)
1681 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);
1684 catch (InvalidDeploymentException ide)
1686 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);
1689 catch (InvalidOperationException ioe)
1691 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
1695 if (info.UpdateAvailable)
1697 Boolean doUpdate = true;
1699 if (!info.IsUpdateRequired)
1701 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
1702 if (!(DialogResult.OK == dr))
1709 // Display a message that the app MUST reboot. Display the minimum required version.
1710 MessageBox.Show("This application has detected a mandatory update from your current " +
1711 "version to version " + info.MinimumRequiredVersion.ToString() +
1712 ". The application will now install the update and restart.",
1713 "Update Available", MessageBoxButtons.OK,
1714 MessageBoxIcon.Information);
1722 MessageBox.Show("The application has been upgraded, and will now restart.");
1723 Application.Restart();
1725 catch (DeploymentDownloadException dde)
1727 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
1734 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
1743 private void SysTrayHideShow()
1749 WindowState = FormWindowState.Normal;
1754 /// Use to handle minimize events.
1756 /// <param name="sender"></param>
1757 /// <param name="e"></param>
1758 private void MainForm_SizeChanged(object sender, EventArgs e)
1760 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
1773 /// <param name="sender"></param>
1774 /// <param name="e"></param>
1775 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
1777 //Our table layout size has changed which means our display size has changed.
1778 //We need to re-create our bitmap.
1779 iCreateBitmap = true;
1785 /// A UI thread copy of a client relevant data.
1786 /// Keeping this copy in the UI thread helps us deal with threading issues.
1788 public class ClientData
1790 public ClientData(string aSessionId, ICallback aCallback)
1792 SessionId = aSessionId;
1794 Fields = new List<DataField>();
1795 Layout = new TableLayout(1, 2); //Default to one column and two rows
1796 Callback = aCallback;
1799 public string SessionId { get; set; }
1800 public string Name { get; set; }
1801 public List<DataField> Fields { get; set; }
1802 public TableLayout Layout { get; set; }
1803 public ICallback Callback { get; set; }