Toasts – Blame information for rev 35

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 // =====COPYRIGHT=====
2 // Code originally retrieved from http://www.vbforums.com/showthread.php?t=547778 - no license information supplied
3 // =====COPYRIGHT=====
4  
34 office 5 using Markdig.Extensions.Tables;
1 office 6 using System;
15 office 7 using System.Collections.Concurrent;
16 office 8 using System.Collections.Generic;
9 using System.Diagnostics;
1 office 10 using System.Drawing;
8 office 11 using System.IO;
15 office 12 using System.Linq;
8 office 13 using System.Media;
34 office 14 using System.Runtime.InteropServices;
16 office 15 using System.Threading;
16 using System.Threading.Tasks;
1 office 17 using System.Windows.Forms;
34 office 18 using TheArtOfDev.HtmlRenderer.WinForms;
11 office 19 using Toasts.Properties;
34 office 20 using static System.Windows.Forms.VisualStyles.VisualStyleElement;
1 office 21  
22 namespace Toasts
23 {
2 office 24 public partial class ToastForm : Form
1 office 25 {
24 office 26 #region Public Fields and Properties
25 office 27  
24 office 28 public bool EnableChime { get; set; } = true;
29  
30 public int LingerTime { get; set; } = 5000;
31  
32 public FormAnimator.AnimationMethod AnimationMethod { get; set; } = FormAnimator.AnimationMethod.Slide;
33  
34 public FormAnimator.AnimationDirection AnimationDirection { get; set; } = FormAnimator.AnimationDirection.Up;
35  
36 public int AnimationDuration { get; set; } = 500;
37  
35 office 38 public string ContentType { get; internal set; } = "text/plain";
39  
26 office 40 public byte[] Chime
41 {
42 get => _chime;
43 set
44 {
45 if (value is null)
46 {
47 return;
48 }
24 office 49  
26 office 50 _chime = value;
51 }
52 }
53  
24 office 54 /// <summary>
55 ///
56 /// </summary>
57 /// <remarks>clone the image for safety</remarks>
58 public Image Image
59 {
34 office 60 get
61 {
62 if (imageBox.Image == null)
63 {
64 return null;
65 }
66 return new Bitmap(imageBox.Image);
67 }
33 office 68 set => imageBox.Image = value;
24 office 69 }
70  
71 #endregion
72  
1 office 73 #region Static Fields and Constants
74  
16 office 75 private static readonly object OpenNotificationsLock = new object();
1 office 76  
16 office 77 private static readonly HashSet<ToastForm> OpenNotifications = new HashSet<ToastForm>();
78  
1 office 79 #endregion
80  
12 office 81 #region Private Overrides
82  
83 protected override bool ShowWithoutActivation => true;
84 protected override CreateParams CreateParams
85 {
86 get
87 {
34 office 88  
12 office 89 var baseParams = base.CreateParams;
34 office 90  
12 office 91 const int WS_EX_NOACTIVATE = 0x08000000;
92 const int WS_EX_TOOLWINDOW = 0x00000080;
93 const int WS_EX_TOPMOST = 0x00000008;
94 baseParams.ExStyle |= WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
34 office 95  
12 office 96 return baseParams;
97 }
98 }
99  
35 office 100  
101  
12 office 102 #endregion
103  
1 office 104 #region Constructors, Destructors and Finalizers
105  
11 office 106 private ToastForm()
7 office 107 {
108 InitializeComponent();
34 office 109 // round rectangles
110 //Region = Region.FromHrgn(NativeMethods.CreateRoundRectRgn(0, 0, Width - 5, Height - 5, 20, 20));
111  
16 office 112 _screenWidth = Screen.PrimaryScreen.WorkingArea.Width;
113 _screenHeight = Screen.PrimaryScreen.WorkingArea.Height;
15 office 114  
24 office 115 _formAnimator = new FormAnimator(this, AnimationMethod, AnimationDirection, AnimationDuration);
16 office 116  
117 using (var memoryStream = new MemoryStream())
15 office 118 {
16 office 119 Resources.normal.CopyTo(memoryStream);
120  
121 memoryStream.Position = 0L;
122  
24 office 123 Chime = memoryStream.ToArray();
15 office 124 }
125  
18 office 126 _showFormTaskCompletionSource = new TaskCompletionSource<object>();
127  
25 office 128 _toastTimer = new System.Timers.Timer { Enabled = false, AutoReset = false, Interval = LingerTime };
15 office 129 _toastTimer.Elapsed += ToastTimer_Elapsed;
7 office 130 }
11 office 131  
1 office 132 /// <summary>
15 office 133 /// Display a toast with a title, body and a logo indefinitely on screen.
1 office 134 /// </summary>
15 office 135 /// <param name="title">the toast title</param>
136 /// <param name="body">the toast body</param>
24 office 137 public ToastForm(string title, string body) : this()
15 office 138 {
139 labelTitle.Text = title;
27 office 140 htmlPanel1.Text = body;
15 office 141 }
142  
143 /// <summary>
11 office 144 /// Clean up any resources being used.
7 office 145 /// </summary>
146 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
147 protected override void Dispose(bool disposing)
148 {
16 office 149 if (disposing && components != null)
33 office 150 {
151 components.Dispose();
152 }
153  
154 if (_toastTimer != null)
16 office 155 {
33 office 156 _toastTimer.Dispose();
157 _toastTimer = null;
158 }
16 office 159  
33 office 160 if (Image != null)
161 {
24 office 162 Image.Dispose();
33 office 163 Image = null;
16 office 164 }
33 office 165  
7 office 166 base.Dispose(disposing);
167 }
168  
1 office 169 #endregion
170  
171 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
172  
16 office 173 private readonly FormAnimator _formAnimator;
1 office 174  
16 office 175 private System.Timers.Timer _toastTimer;
1 office 176  
16 office 177 private readonly int _screenWidth;
178  
179 private readonly int _screenHeight;
21 office 180  
18 office 181 private readonly TaskCompletionSource<object> _showFormTaskCompletionSource;
16 office 182  
26 office 183 private byte[] _chime;
184  
1 office 185 #endregion
186  
187 #region Event Handlers
188  
25 office 189 private void ToastForm_Load(object sender, EventArgs e)
1 office 190 {
24 office 191 if (EnableChime)
16 office 192 {
24 office 193 Task.Factory.StartNew(() =>
9 office 194 {
24 office 195 using (var memoryStream = new MemoryStream(Chime))
16 office 196 {
24 office 197 using (var player = new SoundPlayer(memoryStream))
198 {
199 player.Play();
200 }
16 office 201 }
24 office 202 });
203 }
8 office 204  
16 office 205 try
8 office 206 {
16 office 207 lock (OpenNotificationsLock)
8 office 208 {
35 office 209 // if not image is provided, just collapse the panel for the extra space
210 if (imageBox.Image == null)
211 {
212 splitContainer1.Panel1Collapsed = true;
213 }
214  
34 office 215 Invoke(new MethodInvoker(() =>
216 {
217 // compute notification height from body
218 var maxWidth = tableLayoutPanel4.Width;
219 using (var m_Bitmap = new Bitmap(64, 64))
220 {
221 using (var graphics = Graphics.FromImage(m_Bitmap))
222 {
35 office 223 switch(ContentType)
224 {
225 case "text/markdown":
226 var htmlDocument = new HtmlAgilityPack.HtmlDocument();
227 htmlDocument.LoadHtml(htmlPanel1.Text);
228 foreach (var node in htmlDocument.DocumentNode.SelectNodes("//img"))
229 {
230 node.SetAttributeValue("style", $"max-width: {maxWidth}px");
231 }
232  
233 htmlPanel1.Text = htmlDocument.DocumentNode.WriteTo();
234 break;
235 default:
236 break;
237 }
238  
239 PointF point = new PointF(0, 0);
240 SizeF maxSize = new SizeF(maxWidth, Screen.PrimaryScreen.WorkingArea.Height);
241 var renderSize = HtmlRender.Render(graphics, htmlPanel1.Text, point, maxSize);
34 office 242 // total height = height of text fitting in rectangle + the height of the title on top + one extra line for the last line wrap
35 office 243 var computedOptimalHeight = (int)Math.Round(renderSize.Height) + labelTitle.Height + (int)Math.Round(htmlPanel1.Font.GetHeight());
244 // keep the default height of a notification constant
245 if(computedOptimalHeight > Height)
246 {
247 Height = computedOptimalHeight;
248 }
34 office 249 }
250 }
251 }));
252  
16 office 253 var notifications = OpenNotifications.ToArray();
8 office 254  
16 office 255 foreach (var openForm in notifications)
15 office 256 {
16 office 257 if (openForm == null)
258 {
259 continue;
260 }
7 office 261  
16 office 262 if (openForm.IsDisposed)
263 {
264 OpenNotifications.Remove(openForm);
15 office 265  
16 office 266 continue;
267 }
1 office 268  
16 office 269 if (openForm.IsHandleCreated != true)
270 {
271 continue;
272 }
4 office 273  
16 office 274 // Move each open form upwards to make room for this one
275 var handle = Handle;
276 openForm.BeginInvoke(new MethodInvoker(() =>
277 {
278 try
279 {
280 if (openForm.Handle == handle)
281 {
282 return;
283 }
1 office 284  
16 office 285 openForm.Top -= Height;
286 }
287 catch
288 {
289 Debug.WriteLine("Error while moving forms up.");
290 }
291 }));
292 }
15 office 293  
16 office 294 Invoke(new MethodInvoker(() =>
295 {
34 office 296 // set the location of the toast
16 office 297 Location = new Point(_screenWidth - Width, _screenHeight - Height);
298 }));
299  
300 OpenNotifications.Add(this);
301  
25 office 302 _toastTimer.Enabled = true;
24 office 303 _toastTimer.Start();
16 office 304 }
305 }
306 catch
15 office 307 {
16 office 308 Debug.WriteLine("Error on form load event.");
15 office 309 }
1 office 310 }
311  
25 office 312 private void ToastForm_FormClosed(object sender, FormClosedEventArgs e)
1 office 313 {
16 office 314 try
1 office 315 {
16 office 316 lock (OpenNotificationsLock)
7 office 317 {
16 office 318 OpenNotifications.Remove(this);
7 office 319 }
16 office 320 }
321 catch
322 {
323 Debug.WriteLine("Error in form closed event.");
324 }
325 }
15 office 326  
25 office 327 private void ToastForm_Shown(object sender, EventArgs e)
16 office 328 {
329 // Reverse animation direction for hiding.
330 switch (_formAnimator.Direction)
331 {
332 case FormAnimator.AnimationDirection.Up:
333 _formAnimator.Direction = FormAnimator.AnimationDirection.Down;
334 break;
335 case FormAnimator.AnimationDirection.Down:
336 _formAnimator.Direction = FormAnimator.AnimationDirection.Up;
337 break;
338 case FormAnimator.AnimationDirection.Left:
339 _formAnimator.Direction = FormAnimator.AnimationDirection.Right;
340 break;
341 case FormAnimator.AnimationDirection.Right:
342 _formAnimator.Direction = FormAnimator.AnimationDirection.Left;
343 break;
1 office 344 }
34 office 345  
18 office 346 _showFormTaskCompletionSource.TrySetResult(new { });
1 office 347 }
348  
18 office 349 private async void ToastTimer_Elapsed(object sender, EventArgs e)
1 office 350 {
18 office 351 await _showFormTaskCompletionSource.Task;
352  
16 office 353 if (IsDisposed)
15 office 354 {
355 return;
356 }
1 office 357  
16 office 358 try
359 {
360 _toastTimer.Stop();
361 BeginInvoke(new MethodInvoker(() =>
362 {
363 lock (OpenNotificationsLock)
364 {
365 Close();
366 }
367 }));
368 }
369 catch
370 {
371 Debug.WriteLine("Error in timer elapsed event.");
372 }
1 office 373 }
374  
15 office 375 private void Toast_Click(object sender, EventArgs e)
1 office 376 {
16 office 377 try
378 {
21 office 379 BeginInvoke(new MethodInvoker(() =>
16 office 380 {
21 office 381 lock (OpenNotificationsLock)
382 {
383 Close();
384 }
385 }));
16 office 386 }
387 catch
388 {
389 Debug.WriteLine("Error in form click event.");
390 }
1 office 391 }
392  
393 #endregion
35 office 394  
395 private void htmlPanel1_TextChanged(object sender, EventArgs e)
396 {
397  
398 }
1 office 399 }
400 }