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.SetIconRecording(0, 1);
178 /// Called when our display is closed.
180 /// <param name="aDisplay"></param>
181 private void OnDisplayClosed(Display aDisplay)
183 //Our display was just closed, update our UI consequently
190 private void PopulateDeviceTypes()
192 int count = Display.TypeCount();
194 for (int i = 0; i < count; i++)
196 comboBoxDisplayType.Items.Add(Display.TypeName((Display.TMiniDisplayType)i));
203 private void SetupTrayIcon()
205 iNotifyIcon.Icon = GetIcon("vfd.ico");
206 iNotifyIcon.Text = "Sharp Display Manager";
207 iNotifyIcon.Visible = true;
209 //Double click toggles visibility - typically brings up the application
210 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
215 //Adding a context menu, useful to be able to exit the application
216 ContextMenu contextMenu = new ContextMenu();
217 //Context menu item to toggle visibility
218 MenuItem hideShowItem = new MenuItem("Hide/Show");
219 hideShowItem.Click += delegate(object obj, EventArgs args)
223 contextMenu.MenuItems.Add(hideShowItem);
225 //Context menu item separator
226 contextMenu.MenuItems.Add(new MenuItem("-"));
228 //Context menu exit item
229 MenuItem exitItem = new MenuItem("Exit");
230 exitItem.Click += delegate(object obj, EventArgs args)
234 contextMenu.MenuItems.Add(exitItem);
236 iNotifyIcon.ContextMenu = contextMenu;
240 /// Access icons from embedded resources.
242 /// <param name="name"></param>
243 /// <returns></returns>
244 public static Icon GetIcon(string name)
246 name = "SharpDisplayManager.Resources." + name;
249 Assembly.GetExecutingAssembly().GetManifestResourceNames();
250 for (int i = 0; i < names.Length; i++)
252 if (names[i].Replace('\\', '.') == name)
254 using (Stream stream = Assembly.GetExecutingAssembly().
255 GetManifestResourceStream(names[i]))
257 return new Icon(stream);
267 /// Set our current client.
268 /// This will take care of applying our client layout and set data fields.
270 /// <param name="aSessionId"></param>
271 void SetCurrentClient(string aSessionId)
273 if (aSessionId == iCurrentClientSessionId)
275 //Given client is already the current one.
276 //Don't bother changing anything then.
280 //Set current client ID.
281 iCurrentClientSessionId = aSessionId;
282 //Fetch and set current client data.
283 iCurrentClientData = iClients[aSessionId];
284 //Apply layout and set data fields.
285 UpdateTableLayoutPanel(iCurrentClientData);
288 private void buttonFont_Click(object sender, EventArgs e)
290 //fontDialog.ShowColor = true;
291 //fontDialog.ShowApply = true;
292 fontDialog.ShowEffects = true;
293 fontDialog.Font = cds.Font;
295 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
297 //fontDialog.ShowHelp = true;
299 //fontDlg.MaxSize = 40;
300 //fontDlg.MinSize = 22;
302 //fontDialog.Parent = this;
303 //fontDialog.StartPosition = FormStartPosition.CenterParent;
305 //DlgBox.ShowDialog(fontDialog);
307 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
308 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
310 //Set the fonts to all our labels in our layout
311 foreach (Control ctrl in tableLayoutPanel.Controls)
313 if (ctrl is MarqueeLabel)
315 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
320 cds.Font = fontDialog.Font;
321 Properties.Settings.Default.Save();
330 void CheckFontHeight()
332 //Show font height and width
333 labelFontHeight.Text = "Font height: " + cds.Font.Height;
334 float charWidth = IsFixedWidth(cds.Font);
335 if (charWidth == 0.0f)
337 labelFontWidth.Visible = false;
341 labelFontWidth.Visible = true;
342 labelFontWidth.Text = "Font width: " + charWidth;
345 MarqueeLabel label = null;
346 //Get the first label control we can find
347 foreach (Control ctrl in tableLayoutPanel.Controls)
349 if (ctrl is MarqueeLabel)
351 label = (MarqueeLabel)ctrl;
356 //Now check font height and show a warning if needed.
357 if (label != null && label.Font.Height > label.Height)
359 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
360 labelWarning.Visible = true;
364 labelWarning.Visible = false;
369 private void buttonCapture_Click(object sender, EventArgs e)
371 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
372 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
373 //Bitmap bmpToSave = new Bitmap(bmp);
374 bmp.Save("D:\\capture.png");
376 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
379 string outputFileName = "d:\\capture.png";
380 using (MemoryStream memory = new MemoryStream())
382 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
384 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
385 byte[] bytes = memory.ToArray();
386 fs.Write(bytes, 0, bytes.Length);
393 private void CheckForRequestResults()
395 if (iDisplay.IsRequestPending())
397 switch (iDisplay.AttemptRequestCompletion())
399 case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
400 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
401 //Issue next request then
402 iDisplay.RequestPowerSupplyStatus();
405 case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
406 if (iDisplay.PowerSupplyStatus())
408 toolStripStatusLabelPower.Text = "ON";
412 toolStripStatusLabelPower.Text = "OFF";
414 //Issue next request then
415 iDisplay.RequestDeviceId();
418 case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
419 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
420 //No more request to issue
426 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
428 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
435 public static uint ColorUntouched(int aX, int aY, uint aPixel)
440 public static uint ColorInversed(int aX, int aY, uint aPixel)
445 public static uint ColorChessboard(int aX, int aY, uint aPixel)
447 if ((aX % 2 == 0) && (aY % 2 == 0))
451 else if ((aX % 2 != 0) && (aY % 2 != 0))
459 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
461 return aBmp.Width - aX - 1;
464 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
466 return iBmp.Height - aY - 1;
469 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
474 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
480 /// Select proper pixel delegates according to our current settings.
482 private void SetupPixelDelegates()
484 //Select our pixel processing routine
485 if (cds.InverseColors)
487 //iColorFx = ColorChessboard;
488 iColorFx = ColorInversed;
492 iColorFx = ColorWhiteIsOn;
495 //Select proper coordinate translation functions
496 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
497 if (cds.ReverseScreen)
499 iScreenX = ScreenReversedX;
500 iScreenY = ScreenReversedY;
510 //This is our timer tick responsible to perform our render
511 private void timer_Tick(object sender, EventArgs e)
513 //Update our animations
514 DateTime NewTickTime = DateTime.Now;
516 //Update animation for all our marquees
517 foreach (Control ctrl in tableLayoutPanel.Controls)
519 if (ctrl is MarqueeLabel)
521 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
527 if (iDisplay.IsOpen())
529 CheckForRequestResults();
534 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
536 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
537 //iBmp.Save("D:\\capture.png");
539 //Send it to our display
540 for (int i = 0; i < iBmp.Width; i++)
542 for (int j = 0; j < iBmp.Height; j++)
546 //Get our processed pixel coordinates
547 int x = iScreenX(iBmp, i);
548 int y = iScreenY(iBmp, j);
550 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
551 //Apply color effects
552 color = iColorFx(x,y,color);
554 iDisplay.SetPixel(x, y, color);
559 iDisplay.SwapBuffers();
563 //Compute instant FPS
564 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
566 LastTickTime = NewTickTime;
571 /// Attempt to establish connection with our display hardware.
573 private void OpenDisplayConnection()
575 CloseDisplayConnection();
577 if (!iDisplay.Open((Display.TMiniDisplayType)cds.DisplayType))
580 toolStripStatusLabelConnect.Text = "Connection error";
584 private void CloseDisplayConnection()
586 //Status will be updated upon receiving the closed event
590 private void buttonOpen_Click(object sender, EventArgs e)
592 OpenDisplayConnection();
595 private void buttonClose_Click(object sender, EventArgs e)
597 CloseDisplayConnection();
600 private void buttonClear_Click(object sender, EventArgs e)
603 iDisplay.SwapBuffers();
606 private void buttonFill_Click(object sender, EventArgs e)
609 iDisplay.SwapBuffers();
612 private void trackBarBrightness_Scroll(object sender, EventArgs e)
614 cds.Brightness = trackBarBrightness.Value;
615 Properties.Settings.Default.Save();
616 iDisplay.SetBrightness(trackBarBrightness.Value);
622 /// CDS stands for Current Display Settings
624 private DisplaySettings cds
628 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
629 if (settings == null)
631 settings = new DisplaysSettings();
633 Properties.Settings.Default.DisplaysSettings = settings;
636 //Make sure all our settings have been created
637 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
639 settings.Displays.Add(new DisplaySettings());
642 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
643 return displaySettings;
648 /// Check if the given font has a fixed character pitch.
650 /// <param name="ft"></param>
651 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
652 public float IsFixedWidth(Font ft)
654 Graphics g = CreateGraphics();
655 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
656 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
658 bool fixedWidth = true;
660 foreach (char c in charSizes)
661 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
673 /// Synchronize UI with settings
675 private void UpdateStatus()
678 checkBoxShowBorders.Checked = cds.ShowBorders;
679 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
681 //Set the proper font to each of our labels
682 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
684 ctrl.Font = cds.Font;
688 //Check if "run on Windows startup" is enabled
689 checkBoxAutoStart.Checked = iStartupManager.Startup;
691 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
692 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
693 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
694 checkBoxReverseScreen.Checked = cds.ReverseScreen;
695 checkBoxInverseColors.Checked = cds.InverseColors;
696 checkBoxScaleToFit.Checked = cds.ScaleToFit;
697 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
698 labelMinFontSize.Enabled = cds.ScaleToFit;
699 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
700 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
701 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
702 timer.Interval = cds.TimerInterval;
703 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
704 textBoxScrollLoopSeparator.Text = cds.Separator;
706 SetupPixelDelegates();
708 if (iDisplay.IsOpen())
710 //We have a display connection
711 //Reflect that in our UI
713 tableLayoutPanel.Enabled = true;
714 panelDisplay.Enabled = true;
716 //Only setup brightness if display is open
717 trackBarBrightness.Minimum = iDisplay.MinBrightness();
718 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
719 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
721 //Brightness out of range, this can occur when using auto-detect
722 //Use max brightness instead
723 trackBarBrightness.Value = iDisplay.MaxBrightness();
724 iDisplay.SetBrightness(iDisplay.MaxBrightness());
728 trackBarBrightness.Value = cds.Brightness;
729 iDisplay.SetBrightness(cds.Brightness);
732 //Try compute the steps to something that makes sense
733 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
734 trackBarBrightness.SmallChange = 1;
737 buttonFill.Enabled = true;
738 buttonClear.Enabled = true;
739 buttonOpen.Enabled = false;
740 buttonClose.Enabled = true;
741 trackBarBrightness.Enabled = true;
742 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
743 //+ " - " + iDisplay.SerialNumber();
745 if (iDisplay.SupportPowerOnOff())
747 buttonPowerOn.Enabled = true;
748 buttonPowerOff.Enabled = true;
752 buttonPowerOn.Enabled = false;
753 buttonPowerOff.Enabled = false;
756 if (iDisplay.SupportClock())
758 buttonShowClock.Enabled = true;
759 buttonHideClock.Enabled = true;
763 buttonShowClock.Enabled = false;
764 buttonHideClock.Enabled = false;
769 //Display is connection not available
770 //Reflect that in our UI
771 tableLayoutPanel.Enabled = false;
772 panelDisplay.Enabled = false;
773 buttonFill.Enabled = false;
774 buttonClear.Enabled = false;
775 buttonOpen.Enabled = true;
776 buttonClose.Enabled = false;
777 trackBarBrightness.Enabled = false;
778 buttonPowerOn.Enabled = false;
779 buttonPowerOff.Enabled = false;
780 buttonShowClock.Enabled = false;
781 buttonHideClock.Enabled = false;
782 toolStripStatusLabelConnect.Text = "Disconnected";
783 toolStripStatusLabelPower.Text = "N/A";
790 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
792 //Save our show borders setting
793 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
794 cds.ShowBorders = checkBoxShowBorders.Checked;
795 Properties.Settings.Default.Save();
799 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
801 //Save our connect on startup setting
802 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
803 Properties.Settings.Default.Save();
806 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
808 //Save our "Minimize to tray" setting
809 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
810 Properties.Settings.Default.Save();
814 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
816 //Save our "Start minimized" setting
817 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
818 Properties.Settings.Default.Save();
821 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
823 iStartupManager.Startup = checkBoxAutoStart.Checked;
827 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
829 //Save our reverse screen setting
830 cds.ReverseScreen = checkBoxReverseScreen.Checked;
831 Properties.Settings.Default.Save();
832 SetupPixelDelegates();
835 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
837 //Save our inverse colors setting
838 cds.InverseColors = checkBoxInverseColors.Checked;
839 Properties.Settings.Default.Save();
840 SetupPixelDelegates();
843 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
845 //Save our scale to fit setting
846 cds.ScaleToFit = checkBoxScaleToFit.Checked;
847 Properties.Settings.Default.Save();
849 labelMinFontSize.Enabled = cds.ScaleToFit;
850 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
853 private void MainForm_Resize(object sender, EventArgs e)
855 if (WindowState == FormWindowState.Minimized)
858 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
859 iCreateBitmap = true;
863 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
865 CloseDisplayConnection();
870 public void StartServer()
872 iServiceHost = new ServiceHost
875 new Uri[] { new Uri("net.tcp://localhost:8001/") }
878 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
882 public void StopServer()
884 if (iClients.Count > 0 && !iClosing)
888 BroadcastCloseEvent();
892 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
894 iClosing = false; //We make sure we force close if asked twice
899 //We removed that as it often lags for some reason
900 //iServiceHost.Close();
904 public void BroadcastCloseEvent()
906 Trace.TraceInformation("BroadcastCloseEvent - start");
908 var inactiveClients = new List<string>();
909 foreach (var client in iClients)
911 //if (client.Key != eventData.ClientName)
915 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
916 client.Value.Callback.OnCloseOrder(/*eventData*/);
920 inactiveClients.Add(client.Key);
925 if (inactiveClients.Count > 0)
927 foreach (var client in inactiveClients)
929 iClients.Remove(client);
930 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
934 if (iClients.Count==0)
941 /// Just remove all our fields.
943 private void ClearLayout()
945 tableLayoutPanel.Controls.Clear();
946 tableLayoutPanel.RowStyles.Clear();
947 tableLayoutPanel.ColumnStyles.Clear();
951 /// Just launch a demo client.
953 private void StartNewClient(string aTopText = "", string aBottomText = "")
955 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
956 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
957 clientThread.Start(myParams);
961 private void buttonStartClient_Click(object sender, EventArgs e)
966 private void buttonSuspend_Click(object sender, EventArgs e)
968 LastTickTime = DateTime.Now; //Reset timer to prevent jump
969 timer.Enabled = !timer.Enabled;
972 buttonSuspend.Text = "Run";
976 buttonSuspend.Text = "Pause";
980 private void buttonCloseClients_Click(object sender, EventArgs e)
982 BroadcastCloseEvent();
985 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
994 /// <param name="aSessionId"></param>
995 /// <param name="aCallback"></param>
996 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
998 if (this.InvokeRequired)
1000 //Not in the proper thread, invoke ourselves
1001 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1002 this.Invoke(d, new object[] { aSessionId, aCallback });
1006 //We are in the proper thread
1007 //Add this session to our collection of clients
1008 ClientData newClient = new ClientData(aSessionId, aCallback);
1009 Program.iMainForm.iClients.Add(aSessionId, newClient);
1010 //Add this session to our UI
1011 UpdateClientTreeViewNode(newClient);
1018 /// <param name="aSessionId"></param>
1019 public void RemoveClientThreadSafe(string aSessionId)
1021 if (this.InvokeRequired)
1023 //Not in the proper thread, invoke ourselves
1024 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1025 this.Invoke(d, new object[] { aSessionId });
1029 //We are in the proper thread
1030 //Remove this session from both client collection and UI tree view
1031 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1033 Program.iMainForm.iClients.Remove(aSessionId);
1034 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
1037 if (iClients.Count == 0)
1039 //Clear our screen when last client disconnects
1044 //We were closing our form
1045 //All clients are now closed
1046 //Just resume our close operation
1057 /// <param name="aSessionId"></param>
1058 /// <param name="aLayout"></param>
1059 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1061 if (this.InvokeRequired)
1063 //Not in the proper thread, invoke ourselves
1064 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1065 this.Invoke(d, new object[] { aSessionId, aLayout });
1069 ClientData client = iClients[aSessionId];
1072 client.Layout = aLayout;
1073 UpdateTableLayoutPanel(client);
1075 UpdateClientTreeViewNode(client);
1083 /// <param name="aSessionId"></param>
1084 /// <param name="aField"></param>
1085 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1087 if (this.InvokeRequired)
1089 //Not in the proper thread, invoke ourselves
1090 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1091 this.Invoke(d, new object[] { aSessionId, aField });
1095 //We are in the proper thread
1096 //Call the non-thread-safe variant
1097 SetClientField(aSessionId, aField);
1104 /// <param name="aSessionId"></param>
1105 /// <param name="aField"></param>
1106 private void SetClientField(string aSessionId, DataField aField)
1108 SetCurrentClient(aSessionId);
1109 ClientData client = iClients[aSessionId];
1112 bool somethingChanged = false;
1114 //Make sure all our fields are in place
1115 while (client.Fields.Count < (aField.Index + 1))
1117 //Add a text field with proper index
1118 client.Fields.Add(new DataField(client.Fields.Count));
1119 somethingChanged = true;
1122 if (client.Fields[aField.Index].IsSameLayout(aField))
1124 //Same layout just update our field
1125 client.Fields[aField.Index] = aField;
1127 if (aField.IsText && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
1129 //Text field control already in place, just change the text
1130 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
1131 somethingChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
1132 label.Text = aField.Text;
1133 label.TextAlign = aField.Alignment;
1135 else if (aField.IsBitmap && tableLayoutPanel.Controls[aField.Index] is PictureBox)
1137 somethingChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1138 //Bitmap field control already in place just change the bitmap
1139 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
1140 pictureBox.Image = aField.Bitmap;
1144 somethingChanged = true;
1145 //The requested control in our layout it not of the correct type
1146 //Wrong control type, re-create them all
1147 UpdateTableLayoutPanel(iCurrentClientData);
1152 somethingChanged = true;
1153 //Different layout, need to rebuild it
1154 client.Fields[aField.Index] = aField;
1155 UpdateTableLayoutPanel(iCurrentClientData);
1159 if (somethingChanged)
1161 UpdateClientTreeViewNode(client);
1169 /// <param name="aTexts"></param>
1170 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1172 if (this.InvokeRequired)
1174 //Not in the proper thread, invoke ourselves
1175 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1176 this.Invoke(d, new object[] { aSessionId, aFields });
1180 //Put each our text fields in a label control
1181 foreach (DataField field in aFields)
1183 SetClientField(aSessionId, field);
1191 /// <param name="aSessionId"></param>
1192 /// <param name="aName"></param>
1193 public void SetClientNameThreadSafe(string aSessionId, string aName)
1195 if (this.InvokeRequired)
1197 //Not in the proper thread, invoke ourselves
1198 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1199 this.Invoke(d, new object[] { aSessionId, aName });
1203 //We are in the proper thread
1205 ClientData client = iClients[aSessionId];
1209 client.Name = aName;
1210 //Update our tree-view
1211 UpdateClientTreeViewNode(client);
1219 /// <param name="aClient"></param>
1220 private void UpdateClientTreeViewNode(ClientData aClient)
1222 if (aClient == null)
1227 TreeNode node = null;
1228 //Check that our client node already exists
1229 //Get our client root node using its key which is our session ID
1230 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1231 if (nodes.Count()>0)
1233 //We already have a node for that client
1235 //Clear children as we are going to recreate them below
1240 //Client node does not exists create a new one
1241 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1242 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1248 if (aClient.Name != "")
1250 //We have a name, us it as text for our root node
1251 node.Text = aClient.Name;
1252 //Add a child with SessionId
1253 node.Nodes.Add(new TreeNode(aClient.SessionId));
1257 //No name, use session ID instead
1258 node.Text = aClient.SessionId;
1261 if (aClient.Fields.Count > 0)
1263 //Create root node for our texts
1264 TreeNode textsRoot = new TreeNode("Fields");
1265 node.Nodes.Add(textsRoot);
1266 //For each text add a new entry
1267 foreach (DataField field in aClient.Fields)
1269 if (!field.IsBitmap)
1271 DataField textField = (DataField)field;
1272 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1276 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1285 private void buttonAddRow_Click(object sender, EventArgs e)
1287 if (tableLayoutPanel.RowCount < 6)
1289 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount + 1);
1293 private void buttonRemoveRow_Click(object sender, EventArgs e)
1295 if (tableLayoutPanel.RowCount > 1)
1297 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount - 1);
1300 UpdateTableLayoutRowStyles();
1303 private void buttonAddColumn_Click(object sender, EventArgs e)
1305 if (tableLayoutPanel.ColumnCount < 8)
1307 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount + 1, tableLayoutPanel.RowCount);
1311 private void buttonRemoveColumn_Click(object sender, EventArgs e)
1313 if (tableLayoutPanel.ColumnCount > 1)
1315 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount - 1, tableLayoutPanel.RowCount);
1321 /// Update our table layout row styles to make sure each rows have similar height
1323 private void UpdateTableLayoutRowStyles()
1325 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1327 rowStyle.SizeType = SizeType.Percent;
1328 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1334 /// Empty and recreate our table layout with the given number of columns and rows.
1335 /// Sizes of rows and columns are uniform.
1337 /// <param name="aColumn"></param>
1338 /// <param name="aRow"></param>
1339 private void UpdateTableLayoutPanel(int aColumn, int aRow)
1341 tableLayoutPanel.Controls.Clear();
1342 tableLayoutPanel.RowStyles.Clear();
1343 tableLayoutPanel.ColumnStyles.Clear();
1344 tableLayoutPanel.RowCount = 0;
1345 tableLayoutPanel.ColumnCount = 0;
1347 while (tableLayoutPanel.RowCount < aRow)
1349 tableLayoutPanel.RowCount++;
1352 while (tableLayoutPanel.ColumnCount < aColumn)
1354 tableLayoutPanel.ColumnCount++;
1357 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1359 //Create our column styles
1360 this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.ColumnCount));
1362 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1366 //Create our row styles
1367 this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.RowCount));
1370 MarqueeLabel control = new SharpDisplayManager.MarqueeLabel();
1371 control.AutoEllipsis = true;
1372 control.AutoSize = true;
1373 control.BackColor = System.Drawing.Color.Transparent;
1374 control.Dock = System.Windows.Forms.DockStyle.Fill;
1375 control.Location = new System.Drawing.Point(1, 1);
1376 control.Margin = new System.Windows.Forms.Padding(0);
1377 control.Name = "marqueeLabelCol" + aColumn + "Row" + aRow;
1378 control.OwnTimer = false;
1379 control.PixelsPerSecond = 64;
1380 control.Separator = cds.Separator;
1381 control.MinFontSize = cds.MinFontSize;
1382 control.ScaleToFit = cds.ScaleToFit;
1383 //control.Size = new System.Drawing.Size(254, 30);
1384 //control.TabIndex = 2;
1385 control.Font = cds.Font;
1386 control.Text = "ABCDEFGHIJKLMNOPQRST-0123456789";
1387 control.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1388 control.UseCompatibleTextRendering = true;
1390 tableLayoutPanel.Controls.Add(control, i, j);
1399 /// Update our display table layout.
1401 /// <param name="aLayout"></param>
1402 private void UpdateTableLayoutPanel(ClientData aClient)
1404 if (aClient == null)
1411 TableLayout layout = aClient.Layout;
1414 tableLayoutPanel.Controls.Clear();
1415 tableLayoutPanel.RowStyles.Clear();
1416 tableLayoutPanel.ColumnStyles.Clear();
1417 tableLayoutPanel.RowCount = 0;
1418 tableLayoutPanel.ColumnCount = 0;
1420 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1422 tableLayoutPanel.RowCount++;
1425 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1427 tableLayoutPanel.ColumnCount++;
1430 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1432 //Create our column styles
1433 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1435 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1439 //Create our row styles
1440 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1443 //Check if we already have a control
1444 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1445 if (existingControl!=null)
1447 //We already have a control in that cell as a results of row/col spanning
1448 //Move on to next cell then
1454 //Check if a client field already exists for that cell
1455 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1457 //No client field specified, create a text field by default
1458 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1461 //Create a control corresponding to the field specified for that cell
1462 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1463 Control control = CreateControlForDataField(field);
1465 //Add newly created control to our table layout at the specified row and column
1466 tableLayoutPanel.Controls.Add(control, i, j);
1467 //Make sure we specify row and column span for that new control
1468 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1469 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1474 while (aClient.Fields.Count > fieldCount)
1476 //We have too much fields for this layout
1477 //Just discard them until we get there
1478 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1485 /// Check our type of data field and create corresponding control
1487 /// <param name="aField"></param>
1488 private Control CreateControlForDataField(DataField aField)
1490 Control control=null;
1491 if (!aField.IsBitmap)
1493 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1494 label.AutoEllipsis = true;
1495 label.AutoSize = true;
1496 label.BackColor = System.Drawing.Color.Transparent;
1497 label.Dock = System.Windows.Forms.DockStyle.Fill;
1498 label.Location = new System.Drawing.Point(1, 1);
1499 label.Margin = new System.Windows.Forms.Padding(0);
1500 label.Name = "marqueeLabel" + aField.Index;
1501 label.OwnTimer = false;
1502 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
1503 label.Separator = cds.Separator;
1504 label.MinFontSize = cds.MinFontSize;
1505 label.ScaleToFit = cds.ScaleToFit;
1506 //control.Size = new System.Drawing.Size(254, 30);
1507 //control.TabIndex = 2;
1508 label.Font = cds.Font;
1510 label.TextAlign = aField.Alignment;
1511 label.UseCompatibleTextRendering = true;
1512 label.Text = aField.Text;
1518 //Create picture box
1519 PictureBox picture = new PictureBox();
1520 picture.AutoSize = true;
1521 picture.BackColor = System.Drawing.Color.Transparent;
1522 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1523 picture.Location = new System.Drawing.Point(1, 1);
1524 picture.Margin = new System.Windows.Forms.Padding(0);
1525 picture.Name = "pictureBox" + aField;
1527 picture.Image = aField.Bitmap;
1536 private void buttonAlignLeft_Click(object sender, EventArgs e)
1538 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1540 ctrl.TextAlign = ContentAlignment.MiddleLeft;
1544 private void buttonAlignCenter_Click(object sender, EventArgs e)
1546 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1548 ctrl.TextAlign = ContentAlignment.MiddleCenter;
1552 private void buttonAlignRight_Click(object sender, EventArgs e)
1554 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1556 ctrl.TextAlign = ContentAlignment.MiddleRight;
1561 /// Called when the user selected a new display type.
1563 /// <param name="sender"></param>
1564 /// <param name="e"></param>
1565 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1567 //Store the selected display type in our settings
1568 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1569 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1570 Properties.Settings.Default.Save();
1572 //Try re-opening the display connection if we were already connected.
1573 //Otherwise just update our status to reflect display type change.
1574 if (iDisplay.IsOpen())
1576 OpenDisplayConnection();
1584 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1586 if (maskedTextBoxTimerInterval.Text != "")
1588 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1592 timer.Interval = interval;
1593 cds.TimerInterval = timer.Interval;
1594 Properties.Settings.Default.Save();
1599 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
1601 if (maskedTextBoxMinFontSize.Text != "")
1603 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
1605 if (minFontSize > 0)
1607 cds.MinFontSize = minFontSize;
1608 Properties.Settings.Default.Save();
1609 //We need to recreate our layout for that change to take effect
1610 UpdateTableLayoutPanel(iCurrentClientData);
1616 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
1618 if (maskedTextBoxScrollingSpeed.Text != "")
1620 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
1622 if (scrollingSpeed > 0)
1624 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
1625 Properties.Settings.Default.Save();
1626 //We need to recreate our layout for that change to take effect
1627 UpdateTableLayoutPanel(iCurrentClientData);
1632 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
1634 //TODO: re-create layout? update our fields?
1635 cds.Separator = textBoxScrollLoopSeparator.Text;
1636 Properties.Settings.Default.Save();
1639 private void buttonPowerOn_Click(object sender, EventArgs e)
1644 private void buttonPowerOff_Click(object sender, EventArgs e)
1646 iDisplay.PowerOff();
1649 private void buttonShowClock_Click(object sender, EventArgs e)
1651 iDisplay.ShowClock();
1654 private void buttonHideClock_Click(object sender, EventArgs e)
1656 iDisplay.HideClock();
1659 private void buttonUpdate_Click(object sender, EventArgs e)
1661 InstallUpdateSyncWithInfo();
1665 private void InstallUpdateSyncWithInfo()
1667 UpdateCheckInfo info = null;
1669 if (ApplicationDeployment.IsNetworkDeployed)
1671 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
1675 info = ad.CheckForDetailedUpdate();
1678 catch (DeploymentDownloadException dde)
1680 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);
1683 catch (InvalidDeploymentException ide)
1685 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);
1688 catch (InvalidOperationException ioe)
1690 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
1694 if (info.UpdateAvailable)
1696 Boolean doUpdate = true;
1698 if (!info.IsUpdateRequired)
1700 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
1701 if (!(DialogResult.OK == dr))
1708 // Display a message that the app MUST reboot. Display the minimum required version.
1709 MessageBox.Show("This application has detected a mandatory update from your current " +
1710 "version to version " + info.MinimumRequiredVersion.ToString() +
1711 ". The application will now install the update and restart.",
1712 "Update Available", MessageBoxButtons.OK,
1713 MessageBoxIcon.Information);
1721 MessageBox.Show("The application has been upgraded, and will now restart.");
1722 Application.Restart();
1724 catch (DeploymentDownloadException dde)
1726 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
1733 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
1742 private void SysTrayHideShow()
1748 WindowState = FormWindowState.Normal;
1753 /// Use to handle minimize events.
1755 /// <param name="sender"></param>
1756 /// <param name="e"></param>
1757 private void MainForm_SizeChanged(object sender, EventArgs e)
1759 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
1772 /// <param name="sender"></param>
1773 /// <param name="e"></param>
1774 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
1776 //Our table layout size has changed which means our display size has changed.
1777 //We need to re-create our bitmap.
1778 iCreateBitmap = true;
1784 /// A UI thread copy of a client relevant data.
1785 /// Keeping this copy in the UI thread helps us deal with threading issues.
1787 public class ClientData
1789 public ClientData(string aSessionId, ICallback aCallback)
1791 SessionId = aSessionId;
1793 Fields = new List<DataField>();
1794 Layout = new TableLayout(1, 2); //Default to one column and two rows
1795 Callback = aCallback;
1798 public string SessionId { get; set; }
1799 public string Name { get; set; }
1800 public List<DataField> Fields { get; set; }
1801 public TableLayout Layout { get; set; }
1802 public ICallback Callback { get; set; }