Server: Adding scrolling speed setting.
Fixing issue with alignment of newly created text field not being set properly.
Client: Now starting-up first client automatically in debug mode.
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();
172 /// Called when our display is closed.
174 /// <param name="aDisplay"></param>
175 private void OnDisplayClosed(Display aDisplay)
177 //Our display was just closed, update our UI consequently
184 private void PopulateDeviceTypes()
186 int count = Display.TypeCount();
188 for (int i = 0; i < count; i++)
190 comboBoxDisplayType.Items.Add(Display.TypeName((Display.TMiniDisplayType)i));
197 private void SetupTrayIcon()
199 iNotifyIcon.Icon = GetIcon("vfd.ico");
200 iNotifyIcon.Text = "Sharp Display Manager";
201 iNotifyIcon.Visible = true;
203 //Double click toggles visibility - typically brings up the application
204 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
209 //Adding a context menu, useful to be able to exit the application
210 ContextMenu contextMenu = new ContextMenu();
211 //Context menu item to toggle visibility
212 MenuItem hideShowItem = new MenuItem("Hide/Show");
213 hideShowItem.Click += delegate(object obj, EventArgs args)
217 contextMenu.MenuItems.Add(hideShowItem);
219 //Context menu item separator
220 contextMenu.MenuItems.Add(new MenuItem("-"));
222 //Context menu exit item
223 MenuItem exitItem = new MenuItem("Exit");
224 exitItem.Click += delegate(object obj, EventArgs args)
228 contextMenu.MenuItems.Add(exitItem);
230 iNotifyIcon.ContextMenu = contextMenu;
234 /// Access icons from embedded resources.
236 /// <param name="name"></param>
237 /// <returns></returns>
238 public static Icon GetIcon(string name)
240 name = "SharpDisplayManager.Resources." + name;
243 Assembly.GetExecutingAssembly().GetManifestResourceNames();
244 for (int i = 0; i < names.Length; i++)
246 if (names[i].Replace('\\', '.') == name)
248 using (Stream stream = Assembly.GetExecutingAssembly().
249 GetManifestResourceStream(names[i]))
251 return new Icon(stream);
261 /// Set our current client.
262 /// This will take care of applying our client layout and set data fields.
264 /// <param name="aSessionId"></param>
265 void SetCurrentClient(string aSessionId)
267 if (aSessionId == iCurrentClientSessionId)
269 //Given client is already the current one.
270 //Don't bother changing anything then.
274 //Set current client ID.
275 iCurrentClientSessionId = aSessionId;
276 //Fetch and set current client data.
277 iCurrentClientData = iClients[aSessionId];
278 //Apply layout and set data fields.
279 UpdateTableLayoutPanel(iCurrentClientData);
282 private void buttonFont_Click(object sender, EventArgs e)
284 //fontDialog.ShowColor = true;
285 //fontDialog.ShowApply = true;
286 fontDialog.ShowEffects = true;
287 fontDialog.Font = cds.Font;
289 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
291 //fontDialog.ShowHelp = true;
293 //fontDlg.MaxSize = 40;
294 //fontDlg.MinSize = 22;
296 //fontDialog.Parent = this;
297 //fontDialog.StartPosition = FormStartPosition.CenterParent;
299 //DlgBox.ShowDialog(fontDialog);
301 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
302 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
304 //Set the fonts to all our labels in our layout
305 foreach (Control ctrl in tableLayoutPanel.Controls)
307 if (ctrl is MarqueeLabel)
309 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
314 cds.Font = fontDialog.Font;
315 Properties.Settings.Default.Save();
324 void CheckFontHeight()
326 //Show font height and width
327 labelFontHeight.Text = "Font height: " + cds.Font.Height;
328 float charWidth = IsFixedWidth(cds.Font);
329 if (charWidth == 0.0f)
331 labelFontWidth.Visible = false;
335 labelFontWidth.Visible = true;
336 labelFontWidth.Text = "Font width: " + charWidth;
339 MarqueeLabel label = null;
340 //Get the first label control we can find
341 foreach (Control ctrl in tableLayoutPanel.Controls)
343 if (ctrl is MarqueeLabel)
345 label = (MarqueeLabel)ctrl;
350 //Now check font height and show a warning if needed.
351 if (label != null && label.Font.Height > label.Height)
353 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
354 labelWarning.Visible = true;
358 labelWarning.Visible = false;
363 private void buttonCapture_Click(object sender, EventArgs e)
365 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
366 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
367 //Bitmap bmpToSave = new Bitmap(bmp);
368 bmp.Save("D:\\capture.png");
370 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
373 string outputFileName = "d:\\capture.png";
374 using (MemoryStream memory = new MemoryStream())
376 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
378 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
379 byte[] bytes = memory.ToArray();
380 fs.Write(bytes, 0, bytes.Length);
387 private void CheckForRequestResults()
389 if (iDisplay.IsRequestPending())
391 switch (iDisplay.AttemptRequestCompletion())
393 case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
394 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
395 //Issue next request then
396 iDisplay.RequestPowerSupplyStatus();
399 case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
400 if (iDisplay.PowerSupplyStatus())
402 toolStripStatusLabelPower.Text = "ON";
406 toolStripStatusLabelPower.Text = "OFF";
408 //Issue next request then
409 iDisplay.RequestDeviceId();
412 case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
413 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
414 //No more request to issue
420 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
422 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
429 public static uint ColorUntouched(int aX, int aY, uint aPixel)
434 public static uint ColorInversed(int aX, int aY, uint aPixel)
439 public static uint ColorChessboard(int aX, int aY, uint aPixel)
441 if ((aX % 2 == 0) && (aY % 2 == 0))
445 else if ((aX % 2 != 0) && (aY % 2 != 0))
453 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
455 return aBmp.Width - aX - 1;
458 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
460 return iBmp.Height - aY - 1;
463 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
468 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
474 /// Select proper pixel delegates according to our current settings.
476 private void SetupPixelDelegates()
478 //Select our pixel processing routine
479 if (cds.InverseColors)
481 //iColorFx = ColorChessboard;
482 iColorFx = ColorInversed;
486 iColorFx = ColorWhiteIsOn;
489 //Select proper coordinate translation functions
490 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
491 if (cds.ReverseScreen)
493 iScreenX = ScreenReversedX;
494 iScreenY = ScreenReversedY;
504 //This is our timer tick responsible to perform our render
505 private void timer_Tick(object sender, EventArgs e)
507 //Update our animations
508 DateTime NewTickTime = DateTime.Now;
510 //Update animation for all our marquees
511 foreach (Control ctrl in tableLayoutPanel.Controls)
513 if (ctrl is MarqueeLabel)
515 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
521 if (iDisplay.IsOpen())
523 CheckForRequestResults();
528 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
530 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
531 //iBmp.Save("D:\\capture.png");
533 //Send it to our display
534 for (int i = 0; i < iBmp.Width; i++)
536 for (int j = 0; j < iBmp.Height; j++)
540 //Get our processed pixel coordinates
541 int x = iScreenX(iBmp, i);
542 int y = iScreenY(iBmp, j);
544 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
545 //Apply color effects
546 color = iColorFx(x,y,color);
548 iDisplay.SetPixel(x, y, color);
553 iDisplay.SwapBuffers();
557 //Compute instant FPS
558 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
560 LastTickTime = NewTickTime;
565 /// Attempt to establish connection with our display hardware.
567 private void OpenDisplayConnection()
569 CloseDisplayConnection();
571 if (!iDisplay.Open((Display.TMiniDisplayType)cds.DisplayType))
574 toolStripStatusLabelConnect.Text = "Connection error";
578 private void CloseDisplayConnection()
580 //Status will be updated upon receiving the closed event
584 private void buttonOpen_Click(object sender, EventArgs e)
586 OpenDisplayConnection();
589 private void buttonClose_Click(object sender, EventArgs e)
591 CloseDisplayConnection();
594 private void buttonClear_Click(object sender, EventArgs e)
597 iDisplay.SwapBuffers();
600 private void buttonFill_Click(object sender, EventArgs e)
603 iDisplay.SwapBuffers();
606 private void trackBarBrightness_Scroll(object sender, EventArgs e)
608 cds.Brightness = trackBarBrightness.Value;
609 Properties.Settings.Default.Save();
610 iDisplay.SetBrightness(trackBarBrightness.Value);
616 /// CDS stands for Current Display Settings
618 private DisplaySettings cds
622 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
623 if (settings == null)
625 settings = new DisplaysSettings();
627 Properties.Settings.Default.DisplaysSettings = settings;
630 //Make sure all our settings have been created
631 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
633 settings.Displays.Add(new DisplaySettings());
636 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
637 return displaySettings;
642 /// Check if the given font has a fixed character pitch.
644 /// <param name="ft"></param>
645 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
646 public float IsFixedWidth(Font ft)
648 Graphics g = CreateGraphics();
649 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
650 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
652 bool fixedWidth = true;
654 foreach (char c in charSizes)
655 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
667 /// Synchronize UI with settings
669 private void UpdateStatus()
672 checkBoxShowBorders.Checked = cds.ShowBorders;
673 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
675 //Set the proper font to each of our labels
676 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
678 ctrl.Font = cds.Font;
682 //Check if "run on Windows startup" is enabled
683 checkBoxAutoStart.Checked = iStartupManager.Startup;
685 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
686 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
687 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
688 checkBoxReverseScreen.Checked = cds.ReverseScreen;
689 checkBoxInverseColors.Checked = cds.InverseColors;
690 checkBoxScaleToFit.Checked = cds.ScaleToFit;
691 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
692 labelMinFontSize.Enabled = cds.ScaleToFit;
693 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
694 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
695 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
696 timer.Interval = cds.TimerInterval;
697 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
698 textBoxScrollLoopSeparator.Text = cds.Separator;
700 SetupPixelDelegates();
702 if (iDisplay.IsOpen())
704 //We have a display connection
705 //Reflect that in our UI
707 tableLayoutPanel.Enabled = true;
708 panelDisplay.Enabled = true;
710 //Only setup brightness if display is open
711 trackBarBrightness.Minimum = iDisplay.MinBrightness();
712 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
713 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
715 //Brightness out of range, this can occur when using auto-detect
716 //Use max brightness instead
717 trackBarBrightness.Value = iDisplay.MaxBrightness();
718 iDisplay.SetBrightness(iDisplay.MaxBrightness());
722 trackBarBrightness.Value = cds.Brightness;
723 iDisplay.SetBrightness(cds.Brightness);
726 //Try compute the steps to something that makes sense
727 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
728 trackBarBrightness.SmallChange = 1;
731 buttonFill.Enabled = true;
732 buttonClear.Enabled = true;
733 buttonOpen.Enabled = false;
734 buttonClose.Enabled = true;
735 trackBarBrightness.Enabled = true;
736 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
737 //+ " - " + iDisplay.SerialNumber();
739 if (iDisplay.SupportPowerOnOff())
741 buttonPowerOn.Enabled = true;
742 buttonPowerOff.Enabled = true;
746 buttonPowerOn.Enabled = false;
747 buttonPowerOff.Enabled = false;
750 if (iDisplay.SupportClock())
752 buttonShowClock.Enabled = true;
753 buttonHideClock.Enabled = true;
757 buttonShowClock.Enabled = false;
758 buttonHideClock.Enabled = false;
763 //Display is connection not available
764 //Reflect that in our UI
765 tableLayoutPanel.Enabled = false;
766 panelDisplay.Enabled = false;
767 buttonFill.Enabled = false;
768 buttonClear.Enabled = false;
769 buttonOpen.Enabled = true;
770 buttonClose.Enabled = false;
771 trackBarBrightness.Enabled = false;
772 buttonPowerOn.Enabled = false;
773 buttonPowerOff.Enabled = false;
774 buttonShowClock.Enabled = false;
775 buttonHideClock.Enabled = false;
776 toolStripStatusLabelConnect.Text = "Disconnected";
777 toolStripStatusLabelPower.Text = "N/A";
784 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
786 //Save our show borders setting
787 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
788 cds.ShowBorders = checkBoxShowBorders.Checked;
789 Properties.Settings.Default.Save();
793 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
795 //Save our connect on startup setting
796 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
797 Properties.Settings.Default.Save();
800 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
802 //Save our "Minimize to tray" setting
803 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
804 Properties.Settings.Default.Save();
808 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
810 //Save our "Start minimized" setting
811 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
812 Properties.Settings.Default.Save();
815 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
817 iStartupManager.Startup = checkBoxAutoStart.Checked;
821 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
823 //Save our reverse screen setting
824 cds.ReverseScreen = checkBoxReverseScreen.Checked;
825 Properties.Settings.Default.Save();
826 SetupPixelDelegates();
829 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
831 //Save our inverse colors setting
832 cds.InverseColors = checkBoxInverseColors.Checked;
833 Properties.Settings.Default.Save();
834 SetupPixelDelegates();
837 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
839 //Save our scale to fit setting
840 cds.ScaleToFit = checkBoxScaleToFit.Checked;
841 Properties.Settings.Default.Save();
843 labelMinFontSize.Enabled = cds.ScaleToFit;
844 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
847 private void MainForm_Resize(object sender, EventArgs e)
849 if (WindowState == FormWindowState.Minimized)
852 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
853 iCreateBitmap = true;
857 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
859 CloseDisplayConnection();
864 public void StartServer()
866 iServiceHost = new ServiceHost
869 new Uri[] { new Uri("net.tcp://localhost:8001/") }
872 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
876 public void StopServer()
878 if (iClients.Count > 0 && !iClosing)
882 BroadcastCloseEvent();
886 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
888 iClosing = false; //We make sure we force close if asked twice
893 //We removed that as it often lags for some reason
894 //iServiceHost.Close();
898 public void BroadcastCloseEvent()
900 Trace.TraceInformation("BroadcastCloseEvent - start");
902 var inactiveClients = new List<string>();
903 foreach (var client in iClients)
905 //if (client.Key != eventData.ClientName)
909 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
910 client.Value.Callback.OnCloseOrder(/*eventData*/);
914 inactiveClients.Add(client.Key);
919 if (inactiveClients.Count > 0)
921 foreach (var client in inactiveClients)
923 iClients.Remove(client);
924 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
928 if (iClients.Count==0)
935 /// Just remove all our fields.
937 private void ClearLayout()
939 tableLayoutPanel.Controls.Clear();
940 tableLayoutPanel.RowStyles.Clear();
941 tableLayoutPanel.ColumnStyles.Clear();
945 /// Just launch a demo client.
947 private void StartNewClient(string aTopText = "", string aBottomText = "")
949 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
950 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
951 clientThread.Start(myParams);
955 private void buttonStartClient_Click(object sender, EventArgs e)
960 private void buttonSuspend_Click(object sender, EventArgs e)
962 LastTickTime = DateTime.Now; //Reset timer to prevent jump
963 timer.Enabled = !timer.Enabled;
966 buttonSuspend.Text = "Run";
970 buttonSuspend.Text = "Pause";
974 private void buttonCloseClients_Click(object sender, EventArgs e)
976 BroadcastCloseEvent();
979 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
988 /// <param name="aSessionId"></param>
989 /// <param name="aCallback"></param>
990 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
992 if (this.InvokeRequired)
994 //Not in the proper thread, invoke ourselves
995 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
996 this.Invoke(d, new object[] { aSessionId, aCallback });
1000 //We are in the proper thread
1001 //Add this session to our collection of clients
1002 ClientData newClient = new ClientData(aSessionId, aCallback);
1003 Program.iMainForm.iClients.Add(aSessionId, newClient);
1004 //Add this session to our UI
1005 UpdateClientTreeViewNode(newClient);
1012 /// <param name="aSessionId"></param>
1013 public void RemoveClientThreadSafe(string aSessionId)
1015 if (this.InvokeRequired)
1017 //Not in the proper thread, invoke ourselves
1018 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1019 this.Invoke(d, new object[] { aSessionId });
1023 //We are in the proper thread
1024 //Remove this session from both client collection and UI tree view
1025 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1027 Program.iMainForm.iClients.Remove(aSessionId);
1028 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
1031 if (iClients.Count == 0)
1033 //Clear our screen when last client disconnects
1038 //We were closing our form
1039 //All clients are now closed
1040 //Just resume our close operation
1051 /// <param name="aSessionId"></param>
1052 /// <param name="aLayout"></param>
1053 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1055 if (this.InvokeRequired)
1057 //Not in the proper thread, invoke ourselves
1058 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1059 this.Invoke(d, new object[] { aSessionId, aLayout });
1063 ClientData client = iClients[aSessionId];
1066 client.Layout = aLayout;
1067 UpdateTableLayoutPanel(client);
1069 UpdateClientTreeViewNode(client);
1077 /// <param name="aSessionId"></param>
1078 /// <param name="aField"></param>
1079 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1081 if (this.InvokeRequired)
1083 //Not in the proper thread, invoke ourselves
1084 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1085 this.Invoke(d, new object[] { aSessionId, aField });
1089 //We are in the proper thread
1090 //Call the non-thread-safe variant
1091 SetClientField(aSessionId, aField);
1098 /// <param name="aSessionId"></param>
1099 /// <param name="aField"></param>
1100 private void SetClientField(string aSessionId, DataField aField)
1102 SetCurrentClient(aSessionId);
1103 ClientData client = iClients[aSessionId];
1106 bool somethingChanged = false;
1108 //Make sure all our fields are in place
1109 while (client.Fields.Count < (aField.Index + 1))
1111 //Add a text field with proper index
1112 client.Fields.Add(new DataField(client.Fields.Count));
1113 somethingChanged = true;
1116 if (client.Fields[aField.Index].IsSameLayout(aField))
1118 //Same layout just update our field
1119 client.Fields[aField.Index] = aField;
1121 if (aField.IsText && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
1123 //Text field control already in place, just change the text
1124 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
1125 somethingChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
1126 label.Text = aField.Text;
1127 label.TextAlign = aField.Alignment;
1129 else if (aField.IsBitmap && tableLayoutPanel.Controls[aField.Index] is PictureBox)
1131 somethingChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1132 //Bitmap field control already in place just change the bitmap
1133 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
1134 pictureBox.Image = aField.Bitmap;
1138 somethingChanged = true;
1139 //The requested control in our layout it not of the correct type
1140 //Wrong control type, re-create them all
1141 UpdateTableLayoutPanel(iCurrentClientData);
1146 somethingChanged = true;
1147 //Different layout, need to rebuild it
1148 client.Fields[aField.Index] = aField;
1149 UpdateTableLayoutPanel(iCurrentClientData);
1153 if (somethingChanged)
1155 UpdateClientTreeViewNode(client);
1163 /// <param name="aTexts"></param>
1164 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1166 if (this.InvokeRequired)
1168 //Not in the proper thread, invoke ourselves
1169 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1170 this.Invoke(d, new object[] { aSessionId, aFields });
1174 //Put each our text fields in a label control
1175 foreach (DataField field in aFields)
1177 SetClientField(aSessionId, field);
1185 /// <param name="aSessionId"></param>
1186 /// <param name="aName"></param>
1187 public void SetClientNameThreadSafe(string aSessionId, string aName)
1189 if (this.InvokeRequired)
1191 //Not in the proper thread, invoke ourselves
1192 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1193 this.Invoke(d, new object[] { aSessionId, aName });
1197 //We are in the proper thread
1199 ClientData client = iClients[aSessionId];
1203 client.Name = aName;
1204 //Update our tree-view
1205 UpdateClientTreeViewNode(client);
1213 /// <param name="aClient"></param>
1214 private void UpdateClientTreeViewNode(ClientData aClient)
1216 if (aClient == null)
1221 TreeNode node = null;
1222 //Check that our client node already exists
1223 //Get our client root node using its key which is our session ID
1224 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1225 if (nodes.Count()>0)
1227 //We already have a node for that client
1229 //Clear children as we are going to recreate them below
1234 //Client node does not exists create a new one
1235 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1236 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1242 if (aClient.Name != "")
1244 //We have a name, us it as text for our root node
1245 node.Text = aClient.Name;
1246 //Add a child with SessionId
1247 node.Nodes.Add(new TreeNode(aClient.SessionId));
1251 //No name, use session ID instead
1252 node.Text = aClient.SessionId;
1255 if (aClient.Fields.Count > 0)
1257 //Create root node for our texts
1258 TreeNode textsRoot = new TreeNode("Fields");
1259 node.Nodes.Add(textsRoot);
1260 //For each text add a new entry
1261 foreach (DataField field in aClient.Fields)
1263 if (!field.IsBitmap)
1265 DataField textField = (DataField)field;
1266 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1270 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1279 private void buttonAddRow_Click(object sender, EventArgs e)
1281 if (tableLayoutPanel.RowCount < 6)
1283 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount + 1);
1287 private void buttonRemoveRow_Click(object sender, EventArgs e)
1289 if (tableLayoutPanel.RowCount > 1)
1291 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount, tableLayoutPanel.RowCount - 1);
1294 UpdateTableLayoutRowStyles();
1297 private void buttonAddColumn_Click(object sender, EventArgs e)
1299 if (tableLayoutPanel.ColumnCount < 8)
1301 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount + 1, tableLayoutPanel.RowCount);
1305 private void buttonRemoveColumn_Click(object sender, EventArgs e)
1307 if (tableLayoutPanel.ColumnCount > 1)
1309 UpdateTableLayoutPanel(tableLayoutPanel.ColumnCount - 1, tableLayoutPanel.RowCount);
1315 /// Update our table layout row styles to make sure each rows have similar height
1317 private void UpdateTableLayoutRowStyles()
1319 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1321 rowStyle.SizeType = SizeType.Percent;
1322 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1328 /// Empty and recreate our table layout with the given number of columns and rows.
1329 /// Sizes of rows and columns are uniform.
1331 /// <param name="aColumn"></param>
1332 /// <param name="aRow"></param>
1333 private void UpdateTableLayoutPanel(int aColumn, int aRow)
1335 tableLayoutPanel.Controls.Clear();
1336 tableLayoutPanel.RowStyles.Clear();
1337 tableLayoutPanel.ColumnStyles.Clear();
1338 tableLayoutPanel.RowCount = 0;
1339 tableLayoutPanel.ColumnCount = 0;
1341 while (tableLayoutPanel.RowCount < aRow)
1343 tableLayoutPanel.RowCount++;
1346 while (tableLayoutPanel.ColumnCount < aColumn)
1348 tableLayoutPanel.ColumnCount++;
1351 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1353 //Create our column styles
1354 this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.ColumnCount));
1356 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1360 //Create our row styles
1361 this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / tableLayoutPanel.RowCount));
1364 MarqueeLabel control = new SharpDisplayManager.MarqueeLabel();
1365 control.AutoEllipsis = true;
1366 control.AutoSize = true;
1367 control.BackColor = System.Drawing.Color.Transparent;
1368 control.Dock = System.Windows.Forms.DockStyle.Fill;
1369 control.Location = new System.Drawing.Point(1, 1);
1370 control.Margin = new System.Windows.Forms.Padding(0);
1371 control.Name = "marqueeLabelCol" + aColumn + "Row" + aRow;
1372 control.OwnTimer = false;
1373 control.PixelsPerSecond = 64;
1374 control.Separator = cds.Separator;
1375 control.MinFontSize = cds.MinFontSize;
1376 control.ScaleToFit = cds.ScaleToFit;
1377 //control.Size = new System.Drawing.Size(254, 30);
1378 //control.TabIndex = 2;
1379 control.Font = cds.Font;
1380 control.Text = "ABCDEFGHIJKLMNOPQRST-0123456789";
1381 control.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
1382 control.UseCompatibleTextRendering = true;
1384 tableLayoutPanel.Controls.Add(control, i, j);
1393 /// Update our display table layout.
1395 /// <param name="aLayout"></param>
1396 private void UpdateTableLayoutPanel(ClientData aClient)
1398 if (aClient == null)
1405 TableLayout layout = aClient.Layout;
1408 tableLayoutPanel.Controls.Clear();
1409 tableLayoutPanel.RowStyles.Clear();
1410 tableLayoutPanel.ColumnStyles.Clear();
1411 tableLayoutPanel.RowCount = 0;
1412 tableLayoutPanel.ColumnCount = 0;
1414 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1416 tableLayoutPanel.RowCount++;
1419 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1421 tableLayoutPanel.ColumnCount++;
1424 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1426 //Create our column styles
1427 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1429 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1433 //Create our row styles
1434 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1437 //Check if we already have a control
1438 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1439 if (existingControl!=null)
1441 //We already have a control in that cell as a results of row/col spanning
1442 //Move on to next cell then
1448 //Check if a client field already exists for that cell
1449 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1451 //No client field specified, create a text field by default
1452 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1455 //Create a control corresponding to the field specified for that cell
1456 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1457 Control control = CreateControlForDataField(field);
1459 //Add newly created control to our table layout at the specified row and column
1460 tableLayoutPanel.Controls.Add(control, i, j);
1461 //Make sure we specify row and column span for that new control
1462 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1463 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1468 while (aClient.Fields.Count > fieldCount)
1470 //We have too much fields for this layout
1471 //Just discard them until we get there
1472 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1479 /// Check our type of data field and create corresponding control
1481 /// <param name="aField"></param>
1482 private Control CreateControlForDataField(DataField aField)
1484 Control control=null;
1485 if (!aField.IsBitmap)
1487 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1488 label.AutoEllipsis = true;
1489 label.AutoSize = true;
1490 label.BackColor = System.Drawing.Color.Transparent;
1491 label.Dock = System.Windows.Forms.DockStyle.Fill;
1492 label.Location = new System.Drawing.Point(1, 1);
1493 label.Margin = new System.Windows.Forms.Padding(0);
1494 label.Name = "marqueeLabel" + aField.Index;
1495 label.OwnTimer = false;
1496 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
1497 label.Separator = cds.Separator;
1498 label.MinFontSize = cds.MinFontSize;
1499 label.ScaleToFit = cds.ScaleToFit;
1500 //control.Size = new System.Drawing.Size(254, 30);
1501 //control.TabIndex = 2;
1502 label.Font = cds.Font;
1504 label.TextAlign = aField.Alignment;
1505 label.UseCompatibleTextRendering = true;
1506 label.Text = aField.Text;
1512 //Create picture box
1513 PictureBox picture = new PictureBox();
1514 picture.AutoSize = true;
1515 picture.BackColor = System.Drawing.Color.Transparent;
1516 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1517 picture.Location = new System.Drawing.Point(1, 1);
1518 picture.Margin = new System.Windows.Forms.Padding(0);
1519 picture.Name = "pictureBox" + aField;
1521 picture.Image = aField.Bitmap;
1530 private void buttonAlignLeft_Click(object sender, EventArgs e)
1532 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1534 ctrl.TextAlign = ContentAlignment.MiddleLeft;
1538 private void buttonAlignCenter_Click(object sender, EventArgs e)
1540 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1542 ctrl.TextAlign = ContentAlignment.MiddleCenter;
1546 private void buttonAlignRight_Click(object sender, EventArgs e)
1548 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1550 ctrl.TextAlign = ContentAlignment.MiddleRight;
1555 /// Called when the user selected a new display type.
1557 /// <param name="sender"></param>
1558 /// <param name="e"></param>
1559 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1561 //Store the selected display type in our settings
1562 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1563 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1564 Properties.Settings.Default.Save();
1566 //Try re-opening the display connection if we were already connected.
1567 //Otherwise just update our status to reflect display type change.
1568 if (iDisplay.IsOpen())
1570 OpenDisplayConnection();
1578 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1580 if (maskedTextBoxTimerInterval.Text != "")
1582 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1586 timer.Interval = interval;
1587 cds.TimerInterval = timer.Interval;
1588 Properties.Settings.Default.Save();
1593 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
1595 if (maskedTextBoxMinFontSize.Text != "")
1597 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
1599 if (minFontSize > 0)
1601 cds.MinFontSize = minFontSize;
1602 Properties.Settings.Default.Save();
1603 //We need to recreate our layout for that change to take effect
1604 UpdateTableLayoutPanel(iCurrentClientData);
1610 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
1612 if (maskedTextBoxScrollingSpeed.Text != "")
1614 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
1616 if (scrollingSpeed > 0)
1618 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
1619 Properties.Settings.Default.Save();
1620 //We need to recreate our layout for that change to take effect
1621 UpdateTableLayoutPanel(iCurrentClientData);
1626 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
1628 //TODO: re-create layout? update our fields?
1629 cds.Separator = textBoxScrollLoopSeparator.Text;
1630 Properties.Settings.Default.Save();
1633 private void buttonPowerOn_Click(object sender, EventArgs e)
1638 private void buttonPowerOff_Click(object sender, EventArgs e)
1640 iDisplay.PowerOff();
1643 private void buttonShowClock_Click(object sender, EventArgs e)
1645 iDisplay.ShowClock();
1648 private void buttonHideClock_Click(object sender, EventArgs e)
1650 iDisplay.HideClock();
1653 private void buttonUpdate_Click(object sender, EventArgs e)
1655 InstallUpdateSyncWithInfo();
1659 private void InstallUpdateSyncWithInfo()
1661 UpdateCheckInfo info = null;
1663 if (ApplicationDeployment.IsNetworkDeployed)
1665 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
1669 info = ad.CheckForDetailedUpdate();
1672 catch (DeploymentDownloadException dde)
1674 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);
1677 catch (InvalidDeploymentException ide)
1679 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);
1682 catch (InvalidOperationException ioe)
1684 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
1688 if (info.UpdateAvailable)
1690 Boolean doUpdate = true;
1692 if (!info.IsUpdateRequired)
1694 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
1695 if (!(DialogResult.OK == dr))
1702 // Display a message that the app MUST reboot. Display the minimum required version.
1703 MessageBox.Show("This application has detected a mandatory update from your current " +
1704 "version to version " + info.MinimumRequiredVersion.ToString() +
1705 ". The application will now install the update and restart.",
1706 "Update Available", MessageBoxButtons.OK,
1707 MessageBoxIcon.Information);
1715 MessageBox.Show("The application has been upgraded, and will now restart.");
1716 Application.Restart();
1718 catch (DeploymentDownloadException dde)
1720 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
1727 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
1736 private void SysTrayHideShow()
1742 WindowState = FormWindowState.Normal;
1747 /// Use to handle minimize events.
1749 /// <param name="sender"></param>
1750 /// <param name="e"></param>
1751 private void MainForm_SizeChanged(object sender, EventArgs e)
1753 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
1766 /// <param name="sender"></param>
1767 /// <param name="e"></param>
1768 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
1770 //Our table layout size has changed which means our display size has changed.
1771 //We need to re-create our bitmap.
1772 iCreateBitmap = true;
1778 /// A UI thread copy of a client relevant data.
1779 /// Keeping this copy in the UI thread helps us deal with threading issues.
1781 public class ClientData
1783 public ClientData(string aSessionId, ICallback aCallback)
1785 SessionId = aSessionId;
1787 Fields = new List<DataField>();
1788 Layout = new TableLayout(1, 2); //Default to one column and two rows
1789 Callback = aCallback;
1792 public string SessionId { get; set; }
1793 public string Name { get; set; }
1794 public List<DataField> Fields { get; set; }
1795 public TableLayout Layout { get; set; }
1796 public ICallback Callback { get; set; }