Adding support for display type selection.
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;
17 using SharpDisplayInterface;
18 using SharpDisplayClient;
21 namespace SharpDisplayManager
23 public partial class MainForm : Form
25 DateTime LastTickTime;
27 System.Drawing.Bitmap iBmp;
28 bool iCreateBitmap; //Workaround render to bitmap issues when minimized
29 ServiceHost iServiceHost;
31 /// Our collection of clients
33 public Dictionary<string, ClientData> iClients;
38 LastTickTime = DateTime.Now;
39 iDisplay = new Display();
40 iClients = new Dictionary<string, ClientData>();
42 InitializeComponent();
46 marqueeLabelTop.Font = Properties.Settings.Default.DisplayFont;
47 marqueeLabelBottom.Font = Properties.Settings.Default.DisplayFont;
48 checkBoxShowBorders.Checked = Properties.Settings.Default.DisplayShowBorders;
49 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
50 checkBoxReverseScreen.Checked = Properties.Settings.Default.DisplayReverseScreen;
51 comboBoxDisplayType.SelectedIndex = Properties.Settings.Default.DisplayType;
53 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
54 //We have a bug when drawing minimized and reusing our bitmap
55 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
56 iCreateBitmap = false;
59 private void MainForm_Load(object sender, EventArgs e)
68 if (Properties.Settings.Default.DisplayConnectOnStartup)
70 OpenDisplayConnection();
75 private void buttonFont_Click(object sender, EventArgs e)
77 //fontDialog.ShowColor = true;
78 //fontDialog.ShowApply = true;
79 fontDialog.ShowEffects = true;
80 fontDialog.Font = marqueeLabelTop.Font;
82 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
84 //fontDialog.ShowHelp = true;
86 //fontDlg.MaxSize = 40;
87 //fontDlg.MinSize = 22;
89 //fontDialog.Parent = this;
90 //fontDialog.StartPosition = FormStartPosition.CenterParent;
92 //DlgBox.ShowDialog(fontDialog);
94 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
95 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
98 //MsgBox.Show("MessageBox MsgBox", "MsgBox caption");
100 //MessageBox.Show("Ok");
101 marqueeLabelTop.Font = fontDialog.Font;
102 marqueeLabelBottom.Font = fontDialog.Font;
103 Properties.Settings.Default.DisplayFont = fontDialog.Font;
104 Properties.Settings.Default.Save();
113 void CheckFontHeight()
115 if (marqueeLabelBottom.Font.Height > marqueeLabelBottom.Height)
117 labelWarning.Text = "WARNING: Selected font is too height by " + (marqueeLabelBottom.Font.Height - marqueeLabelBottom.Height) + " pixels!";
118 labelWarning.Visible = true;
122 labelWarning.Visible = false;
127 private void buttonCapture_Click(object sender, EventArgs e)
129 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
130 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
131 //Bitmap bmpToSave = new Bitmap(bmp);
132 bmp.Save("D:\\capture.png");
134 marqueeLabelTop.Text = "Sweet";
137 string outputFileName = "d:\\capture.png";
138 using (MemoryStream memory = new MemoryStream())
140 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
142 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
143 byte[] bytes = memory.ToArray();
144 fs.Write(bytes, 0, bytes.Length);
151 private void CheckForRequestResults()
153 if (iDisplay.IsRequestPending())
155 switch (iDisplay.AttemptRequestCompletion())
157 case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
158 if (iDisplay.PowerSupplyStatus())
160 toolStripStatusLabelPower.Text = "ON";
164 toolStripStatusLabelPower.Text = "OFF";
166 //Issue next request then
167 iDisplay.RequestDeviceId();
170 case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
171 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
172 //Issue next request then
173 iDisplay.RequestFirmwareRevision();
176 case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
177 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
178 //No more request to issue
185 public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
188 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
190 return aBmp.Width - aX - 1;
193 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
195 return iBmp.Height - aY - 1;
198 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
203 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
209 //This is our timer tick responsible to perform our render
210 private void timer_Tick(object sender, EventArgs e)
212 //Update our animations
213 DateTime NewTickTime = DateTime.Now;
215 marqueeLabelTop.UpdateAnimation(LastTickTime, NewTickTime);
216 marqueeLabelBottom.UpdateAnimation(LastTickTime, NewTickTime);
219 if (iDisplay.IsOpen())
221 CheckForRequestResults();
226 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
228 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
229 //iBmp.Save("D:\\capture.png");
231 //Select proper coordinate translation functions
232 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
233 CoordinateTranslationDelegate screenX;
234 CoordinateTranslationDelegate screenY;
236 if (Properties.Settings.Default.DisplayReverseScreen)
238 screenX = ScreenReversedX;
239 screenY = ScreenReversedY;
247 //Send it to our display
248 for (int i = 0; i < iBmp.Width; i++)
250 for (int j = 0; j < iBmp.Height; j++)
254 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
255 //For some reason when the app is minimized in the task bar only the alpha of our color is set.
256 //Thus that strange test for rendering to work both when the app is in the task bar and when it isn't.
257 iDisplay.SetPixel(screenX(iBmp, i), screenY(iBmp, j), Convert.ToInt32(!(color != 0xFF000000)));
262 iDisplay.SwapBuffers();
266 //Compute instant FPS
267 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " FPS";
269 LastTickTime = NewTickTime;
273 private void OpenDisplayConnection()
275 CloseDisplayConnection();
277 if (iDisplay.Open((Display.TMiniDisplayType)Properties.Settings.Default.DisplayType))
280 iDisplay.RequestPowerSupplyStatus();
285 toolStripStatusLabelConnect.Text = "Connection error";
289 private void CloseDisplayConnection()
295 private void buttonOpen_Click(object sender, EventArgs e)
297 OpenDisplayConnection();
300 private void buttonClose_Click(object sender, EventArgs e)
302 CloseDisplayConnection();
305 private void buttonClear_Click(object sender, EventArgs e)
308 iDisplay.SwapBuffers();
311 private void buttonFill_Click(object sender, EventArgs e)
314 iDisplay.SwapBuffers();
317 private void trackBarBrightness_Scroll(object sender, EventArgs e)
319 Properties.Settings.Default.DisplayBrightness = trackBarBrightness.Value;
320 Properties.Settings.Default.Save();
321 iDisplay.SetBrightness(trackBarBrightness.Value);
325 private void UpdateStatus()
327 if (iDisplay.IsOpen())
329 buttonFill.Enabled = true;
330 buttonClear.Enabled = true;
331 buttonOpen.Enabled = false;
332 buttonClose.Enabled = true;
333 trackBarBrightness.Enabled = true;
334 trackBarBrightness.Minimum = iDisplay.MinBrightness();
335 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
336 trackBarBrightness.Value = Properties.Settings.Default.DisplayBrightness;
337 trackBarBrightness.LargeChange = Math.Max(1,(iDisplay.MaxBrightness() - iDisplay.MinBrightness())/5);
338 trackBarBrightness.SmallChange = 1;
339 iDisplay.SetBrightness(Properties.Settings.Default.DisplayBrightness);
341 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
342 //+ " - " + iDisplay.SerialNumber();
346 buttonFill.Enabled = false;
347 buttonClear.Enabled = false;
348 buttonOpen.Enabled = true;
349 buttonClose.Enabled = false;
350 trackBarBrightness.Enabled = false;
351 toolStripStatusLabelConnect.Text = "Disconnected";
357 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
359 //Save our show borders setting
360 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
361 Properties.Settings.Default.DisplayShowBorders = checkBoxShowBorders.Checked;
362 Properties.Settings.Default.Save();
365 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
367 //Save our connect on startup setting
368 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
369 Properties.Settings.Default.Save();
372 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
374 //Save our reverse screen setting
375 Properties.Settings.Default.DisplayReverseScreen = checkBoxReverseScreen.Checked;
376 Properties.Settings.Default.Save();
379 private void MainForm_Resize(object sender, EventArgs e)
381 if (WindowState == FormWindowState.Minimized)
384 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
385 iCreateBitmap = true;
389 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
395 public void StartServer()
397 iServiceHost = new ServiceHost
399 typeof(DisplayServer),
400 new Uri[] { new Uri("net.tcp://localhost:8001/") }
403 iServiceHost.AddServiceEndpoint(typeof(IDisplayService), new NetTcpBinding(SecurityMode.None,true), "DisplayService");
407 public void StopServer()
409 if (iClients.Count > 0 && !iClosing)
413 BroadcastCloseEvent();
417 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
419 iClosing = false; //We make sure we force close if asked twice
424 //We removed that as it often lags for some reason
425 //iServiceHost.Close();
429 public void BroadcastCloseEvent()
431 Trace.TraceInformation("BroadcastCloseEvent - start");
433 var inactiveClients = new List<string>();
434 foreach (var client in iClients)
436 //if (client.Key != eventData.ClientName)
440 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
441 client.Value.Callback.OnCloseOrder(/*eventData*/);
445 inactiveClients.Add(client.Key);
450 if (inactiveClients.Count > 0)
452 foreach (var client in inactiveClients)
454 iClients.Remove(client);
455 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
460 private void buttonStartClient_Click(object sender, EventArgs e)
462 Thread clientThread = new Thread(SharpDisplayClient.Program.Main);
463 clientThread.Start();
467 private void buttonSuspend_Click(object sender, EventArgs e)
469 timer.Enabled = !timer.Enabled;
472 buttonSuspend.Text = "Suspend";
476 buttonSuspend.Text = "Pause";
480 private void buttonCloseClients_Click(object sender, EventArgs e)
482 BroadcastCloseEvent();
485 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
490 //Delegates are used for our thread safe method
491 public delegate void AddClientDelegate(string aSessionId, IDisplayServiceCallback aCallback);
492 public delegate void RemoveClientDelegate(string aSessionId);
493 public delegate void SetTextDelegate(string SessionId, TextField aTextField);
494 public delegate void SetTextsDelegate(string SessionId, System.Collections.Generic.IList<TextField> aTextFields);
495 public delegate void SetClientNameDelegate(string aSessionId, string aName);
501 /// <param name="aSessionId"></param>
502 /// <param name="aCallback"></param>
503 public void AddClientThreadSafe(string aSessionId, IDisplayServiceCallback aCallback)
505 if (this.InvokeRequired)
507 //Not in the proper thread, invoke ourselves
508 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
509 this.Invoke(d, new object[] { aSessionId, aCallback });
513 //We are in the proper thread
514 //Add this session to our collection of clients
515 ClientData newClient = new ClientData(aSessionId, aCallback);
516 Program.iMainForm.iClients.Add(aSessionId, newClient);
517 //Add this session to our UI
518 UpdateClientTreeViewNode(newClient);
525 /// <param name="aSessionId"></param>
526 public void RemoveClientThreadSafe(string aSessionId)
528 if (this.InvokeRequired)
530 //Not in the proper thread, invoke ourselves
531 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
532 this.Invoke(d, new object[] { aSessionId });
536 //We are in the proper thread
537 //Remove this session from both client collection and UI tree view
538 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
540 Program.iMainForm.iClients.Remove(aSessionId);
541 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
544 if (iClosing && iClients.Count == 0)
546 //We were closing our form
547 //All clients are now closed
548 //Just resume our close operation
558 /// <param name="aLineIndex"></param>
559 /// <param name="aText"></param>
560 public void SetTextThreadSafe(string aSessionId, TextField aTextField)
562 if (this.InvokeRequired)
564 //Not in the proper thread, invoke ourselves
565 SetTextDelegate d = new SetTextDelegate(SetTextThreadSafe);
566 this.Invoke(d, new object[] { aSessionId, aTextField });
570 ClientData client = iClients[aSessionId];
573 //Make sure all our texts are in place
574 while (client.Texts.Count < (aTextField.Index + 1))
576 //Add a text field with proper index
577 client.Texts.Add(new TextField(client.Texts.Count));
579 client.Texts[aTextField.Index] = aTextField;
581 //We are in the proper thread
582 //Only support two lines for now
583 if (aTextField.Index == 0)
585 marqueeLabelTop.Text = aTextField.Text;
586 marqueeLabelTop.TextAlign = aTextField.Alignment;
588 else if (aTextField.Index == 1)
590 marqueeLabelBottom.Text = aTextField.Text;
591 marqueeLabelBottom.TextAlign = aTextField.Alignment;
595 UpdateClientTreeViewNode(client);
603 /// <param name="aTexts"></param>
604 public void SetTextsThreadSafe(string aSessionId, System.Collections.Generic.IList<TextField> aTextFields)
606 if (this.InvokeRequired)
608 //Not in the proper thread, invoke ourselves
609 SetTextsDelegate d = new SetTextsDelegate(SetTextsThreadSafe);
610 this.Invoke(d, new object[] { aSessionId, aTextFields });
614 //We are in the proper thread
615 ClientData client = iClients[aSessionId];
618 //Populate our client with the given text fields
620 foreach (TextField textField in aTextFields)
622 if (client.Texts.Count < (j + 1))
624 client.Texts.Add(textField);
628 client.Texts[j] = textField;
632 //Only support two lines for now
633 for (int i = 0; i < aTextFields.Count; i++)
635 if (aTextFields[i].Index == 0)
637 marqueeLabelTop.Text = aTextFields[i].Text;
638 marqueeLabelTop.TextAlign = aTextFields[i].Alignment;
640 else if (aTextFields[i].Index == 1)
642 marqueeLabelBottom.Text = aTextFields[i].Text;
643 marqueeLabelBottom.TextAlign = aTextFields[i].Alignment;
648 UpdateClientTreeViewNode(client);
657 /// <param name="aSessionId"></param>
658 /// <param name="aName"></param>
659 public void SetClientNameThreadSafe(string aSessionId, string aName)
661 if (this.InvokeRequired)
663 //Not in the proper thread, invoke ourselves
664 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
665 this.Invoke(d, new object[] { aSessionId, aName });
669 //We are in the proper thread
671 ClientData client = iClients[aSessionId];
676 //Update our tree-view
677 UpdateClientTreeViewNode(client);
685 /// <param name="aClient"></param>
686 private void UpdateClientTreeViewNode(ClientData aClient)
693 TreeNode node = null;
694 //Check that our client node already exists
695 //Get our client root node using its key which is our session ID
696 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
699 //We already have a node for that client
701 //Clear children as we are going to recreate them below
706 //Client node does not exists create a new one
707 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
708 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
714 if (aClient.Name != "")
716 //We have a name, us it as text for our root node
717 node.Text = aClient.Name;
718 //Add a child with SessionId
719 node.Nodes.Add(new TreeNode(aClient.SessionId));
723 //No name, use session ID instead
724 node.Text = aClient.SessionId;
727 if (aClient.Texts.Count > 0)
729 //Create root node for our texts
730 TreeNode textsRoot = new TreeNode("Text");
731 node.Nodes.Add(textsRoot);
732 //For each text add a new entry
733 foreach (TextField field in aClient.Texts)
735 textsRoot.Nodes.Add(new TreeNode(field.Text));
743 private void buttonAddRow_Click(object sender, EventArgs e)
745 if (tableLayoutPanel.RowCount < 6)
747 tableLayoutPanel.RowCount++;
752 private void buttonRemoveRow_Click(object sender, EventArgs e)
754 if (tableLayoutPanel.RowCount > 1)
756 tableLayoutPanel.RowCount--;
761 private void buttonAddColumn_Click(object sender, EventArgs e)
763 if (tableLayoutPanel.ColumnCount < 8)
765 tableLayoutPanel.ColumnCount++;
770 private void buttonRemoveColumn_Click(object sender, EventArgs e)
772 if (tableLayoutPanel.ColumnCount > 1)
774 tableLayoutPanel.ColumnCount--;
779 private void buttonAlignLeft_Click(object sender, EventArgs e)
781 marqueeLabelTop.TextAlign = ContentAlignment.MiddleLeft;
782 marqueeLabelBottom.TextAlign = ContentAlignment.MiddleLeft;
785 private void buttonAlignCenter_Click(object sender, EventArgs e)
787 marqueeLabelTop.TextAlign = ContentAlignment.MiddleCenter;
788 marqueeLabelBottom.TextAlign = ContentAlignment.MiddleCenter;
791 private void buttonAlignRight_Click(object sender, EventArgs e)
793 marqueeLabelTop.TextAlign = ContentAlignment.MiddleRight;
794 marqueeLabelBottom.TextAlign = ContentAlignment.MiddleRight;
797 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
799 Properties.Settings.Default.DisplayType = comboBoxDisplayType.SelectedIndex;
800 Properties.Settings.Default.Save();
801 OpenDisplayConnection();
807 /// A UI thread copy of a client relevant data.
808 /// Keeping this copy in the UI thread helps us deal with threading issues.
810 public class ClientData
812 public ClientData(string aSessionId, IDisplayServiceCallback aCallback)
814 SessionId = aSessionId;
816 Texts = new List<TextField>();
817 Callback = aCallback;
820 public string SessionId { get; set; }
821 public string Name { get; set; }
822 public List<TextField> Texts { get; set; }
823 public IDisplayServiceCallback Callback { get; set; }