comparison ServerMonitor/Forms/CheckForm.cs @ 15:23f2e0da1094

- Fix the last execution status being lost after a check is edited. - Add comments.
author Brad Greco <brad@bgreco.net>
date Mon, 22 Apr 2019 21:11:27 -0400
parents d92176c5398a
children 7626b099aefd
comparison
equal deleted inserted replaced
14:2db36ab759de 15:23f2e0da1094
8 using System.Windows.Forms; 8 using System.Windows.Forms;
9 using ServerMonitorApp.Properties; 9 using ServerMonitorApp.Properties;
10 10
11 namespace ServerMonitorApp 11 namespace ServerMonitorApp
12 { 12 {
13 /// <summary>Form for creating and editing a check.</summary>
14 /// <remarks>
15 /// This form contains controls common to editing all types of checks.
16 /// Additional controls for specific check types may be shown in a panel below.
17 /// </remarks>
13 public partial class CheckForm : Form 18 public partial class CheckForm : Form
14 { 19 {
15 private readonly List<CheckControl> checkControls = new List<CheckControl>();
16 private bool helpShown; 20 private bool helpShown;
17 private CancellationTokenSource cancellationTokenSource; 21 private CancellationTokenSource cancellationTokenSource;
18 private Check workCheck; 22 private Check workCheck;
19 private CheckControl checkControl; 23 private CheckControl checkControl;
20 private QuickHelpForm helpForm; 24 private QuickHelpForm helpForm;
21 private Server server; 25 private Server server;
22 private ServerMonitor monitor; 26 private ServerMonitor monitor;
23 27
28 /// <summary>Raised when the Help button's location on the screen changes.</summary>
24 public event EventHandler<HelpLocationChangedEventArgs> HelpLocationChanged; 29 public event EventHandler<HelpLocationChangedEventArgs> HelpLocationChanged;
25 30
31 /// <summary>The check being edited by the form.</summary>
32 /// <remarks>This check object is not updated with the form values until the OK button is clicked.</remarks>
26 public Check Check { get; private set; } 33 public Check Check { get; private set; }
27 34
35 /// <summary>The ID of the check being edited.</summary>
28 public int CheckId { get; private set; } 36 public int CheckId { get; private set; }
29 37
38 /// <summary>The Help button's location on the screen.</summary>
30 public Point HelpLocation => TypeHelpPictureBox.PointToScreen(Point.Empty); 39 public Point HelpLocation => TypeHelpPictureBox.PointToScreen(Point.Empty);
31 40
41 /// <summary>Creates a check form to edit an existing check.</summary>
42 /// <param name="monitor">The server monitor.</param>
43 /// <param name="check">The check to edit.</param>
32 public CheckForm(ServerMonitor monitor, Check check) 44 public CheckForm(ServerMonitor monitor, Check check)
33 { 45 {
34 InitializeComponent(); 46 InitializeComponent();
35 47
36 Check = check; 48 Check = check;
37 CheckId = Check.Id; 49 CheckId = Check.Id;
38 server = Check.Server; 50 server = Check.Server;
39 this.monitor = monitor; 51 this.monitor = monitor;
40 } 52 }
41 53
54 /// <summary>Creates a check form for creating a new check.</summary>
55 /// <param name="monitor">The server monitor.</param>
56 /// <param name="server">The server the new check will run on.</param>
42 public CheckForm(ServerMonitor monitor, Server server) 57 public CheckForm(ServerMonitor monitor, Server server)
43 { 58 {
44 InitializeComponent(); 59 InitializeComponent();
45 this.server = server; 60 this.server = server;
46 this.monitor = monitor; 61 this.monitor = monitor;
47 } 62 }
48 63
49 private void CheckForm_Load(object sender, EventArgs e) 64 private void CheckForm_Load(object sender, EventArgs e)
50 { 65 {
66 // Set up control default values.
51 CheckTypeComboBox.Items.AddRange(Check.CheckTypes); 67 CheckTypeComboBox.Items.AddRange(Check.CheckTypes);
52 SeverityComboBox.Items.AddRange(new object[] { CheckStatus.Error, CheckStatus.Warning, CheckStatus.Information }); 68 SeverityComboBox.Items.AddRange(new object[] { CheckStatus.Error, CheckStatus.Warning, CheckStatus.Information });
53 SeverityComboBox.SelectedIndex = 0; 69 SeverityComboBox.SelectedIndex = 0;
54 FrequencyUnitsComboBox.DataSource = Enum.GetValues(typeof(FrequencyUnits)); 70 FrequencyUnitsComboBox.DataSource = Enum.GetValues(typeof(FrequencyUnits));
55 Helpers.FormatImageButton(RunButton); 71 Helpers.FormatImageButton(RunButton);
56 Helpers.FormatImageButton(CancelRunButton); 72 Helpers.FormatImageButton(CancelRunButton);
57 73 Icon = Resources.icon;
74
75 // Bind event listeners.
58 Move += CheckForm_Move; 76 Move += CheckForm_Move;
59 CheckTypePanel.LocationChanged += CheckTypePanel_LocationChanged; 77 CheckTypePanel.LocationChanged += CheckTypePanel_LocationChanged;
60 GeneralGroupBox.Click += Control_Click; 78 GeneralGroupBox.Click += Control_Click;
61 CheckSettingsPanel.Click += Control_Click; 79 CheckSettingsPanel.Click += Control_Click;
62 80
81 // Set control values from the check.
63 CheckTypeComboBox.SelectedItem = Check?.GetType(); 82 CheckTypeComboBox.SelectedItem = Check?.GetType();
64 SetTitle(); 83 SetTitle();
65 Icon = Resources.icon;
66 if (Check != null) 84 if (Check != null)
67 LoadCheck(Check); 85 LoadCheck(Check);
68 } 86 }
69 87
88 /// <summary>Sets the form title from the check, or a default value for a new check.</summary>
70 private void SetTitle() 89 private void SetTitle()
71 { 90 {
72 string name = NameTextBox.Text.IsNullOrEmpty() ? "New Check" : NameTextBox.Text; 91 string name = NameTextBox.Text.IsNullOrEmpty() ? "New Check" : NameTextBox.Text;
73 Text = string.Format("{0}: {1}", server.Name, name); 92 Text = string.Format("{0}: {1}", server.Name, name);
74 } 93 }
75 94
95 /// <summary>Handles the check type combo box changing.</summary>
76 private void CheckTypeComboBox_SelectedIndexChanged(object sender, EventArgs e) 96 private void CheckTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
77 { 97 {
98 // Show the check control for the selected check type.
78 ShowCheckControl(); 99 ShowCheckControl();
100 // Create a temporary instance of the selected check type that will be used for validation
101 // and execution that can be discarded if the Cancel button is clicked.
79 workCheck = (Check)Activator.CreateInstance((Type)CheckTypeComboBox.SelectedItem); 102 workCheck = (Check)Activator.CreateInstance((Type)CheckTypeComboBox.SelectedItem);
80 } 103 }
81 104
105 /// <summary>Shows the display name of each check type in the combo box.</summary>
82 private void CheckTypeComboBox_Format(object sender, ListControlConvertEventArgs e) 106 private void CheckTypeComboBox_Format(object sender, ListControlConvertEventArgs e)
83 { 107 {
84 e.Value = Helpers.GetDisplayName((Type)e.ListItem); 108 e.Value = Helpers.GetDisplayName((Type)e.ListItem);
85 } 109 }
86 110
111 /// <summary>Shows a check control containing settings for the selected check type.</summary>
87 private void ShowCheckControl() 112 private void ShowCheckControl()
88 { 113 {
114 // Hide the existing check control.
89 IEnumerable<CheckControl> checkControls = CheckSettingsPanel.Controls.OfType<CheckControl>(); 115 IEnumerable<CheckControl> checkControls = CheckSettingsPanel.Controls.OfType<CheckControl>();
90 foreach (CheckControl control in checkControls) 116 foreach (CheckControl control in checkControls)
91 control.Hide(); 117 control.Hide();
118 // Show the check control for the selected check type if it has already been created.
119 // Allows switching check types without losing data.
92 Type type = (Type)CheckTypeComboBox.SelectedItem; 120 Type type = (Type)CheckTypeComboBox.SelectedItem;
93 checkControl = checkControls.FirstOrDefault(c => c.CheckType == type); 121 checkControl = checkControls.FirstOrDefault(c => c.CheckType == type);
94 if (checkControl == null) 122 if (checkControl == null)
95 { 123 {
124 // Create a new check control if one has not been created yet for this check type.
96 checkControl = CheckControl.Create(type); 125 checkControl = CheckControl.Create(type);
97 if (checkControl != null) 126 if (checkControl != null)
98 { 127 {
99 checkControl.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; 128 checkControl.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
100 checkControl.Click += Control_Click; 129 checkControl.Click += Control_Click;
105 { 134 {
106 checkControl.Show(); 135 checkControl.Show();
107 } 136 }
108 } 137 }
109 138
139 /// <summary>Populates common check controls from a check.</summary>
110 private void LoadCheck(Check check) 140 private void LoadCheck(Check check)
111 { 141 {
112 Icon = Check.LastRunStatus.GetIcon(); 142 Icon = Check.LastRunStatus.GetIcon();
113 NameTextBox.Text = Check.Name; 143 NameTextBox.Text = Check.Name;
114 EnabledCheckBox.Checked = check.Enabled; 144 EnabledCheckBox.Checked = check.Enabled;
117 FailuresInput.Value = check.MaxConsecutiveFailures; 147 FailuresInput.Value = check.MaxConsecutiveFailures;
118 FrequencyUnitsComboBox.SelectedItem = check.Schedule.Units; 148 FrequencyUnitsComboBox.SelectedItem = check.Schedule.Units;
119 FrequencyUpDown.Value = check.Schedule.Frequency; 149 FrequencyUpDown.Value = check.Schedule.Frequency;
120 StartTimePicker.Value = new DateTime(1970, 1, 1) + check.Schedule.StartTime; 150 StartTimePicker.Value = new DateTime(1970, 1, 1) + check.Schedule.StartTime;
121 EndTimePicker.Value = new DateTime(1970, 1, 1) + check.Schedule.EndTime; 151 EndTimePicker.Value = new DateTime(1970, 1, 1) + check.Schedule.EndTime;
152 // Populate the controls specific to this check type.
122 checkControl?.LoadCheck(check); 153 checkControl?.LoadCheck(check);
123 } 154 }
124 155
156 /// <summary>Updates a check with user inputs.</summary>
157 /// <param name="check">The check to be updated.</param>
158 /// <param name="saving">
159 /// Whether the check is being saved.
160 /// Checks are validated before being saved and before being executed. This parameter allows
161 /// them to distinguish between these cases.
162 /// </param>
163 /// <returns>Whether the check was updated successfully.</returns>
125 private bool UpdateCheck(Check check, bool saving = true) 164 private bool UpdateCheck(Check check, bool saving = true)
126 { 165 {
166 // The validation result message. An empty value indicates that validation succeeded.
127 string result; 167 string result;
168 // Make sure we have a valid check type before doing anything else.
128 if (CheckTypeComboBox.SelectedIndex == -1) 169 if (CheckTypeComboBox.SelectedIndex == -1)
129 { 170 {
130 result = "Check type cannot be blank."; 171 result = "Check type cannot be blank.";
131 } 172 }
132 else 173 else
137 check.Enabled = EnabledCheckBox.Checked; 178 check.Enabled = EnabledCheckBox.Checked;
138 check.Timeout = (int)TimeoutInput.Value; 179 check.Timeout = (int)TimeoutInput.Value;
139 check.FailStatus = (CheckStatus)SeverityComboBox.SelectedItem; 180 check.FailStatus = (CheckStatus)SeverityComboBox.SelectedItem;
140 check.MaxConsecutiveFailures = (int)FailuresInput.Value; 181 check.MaxConsecutiveFailures = (int)FailuresInput.Value;
141 check.Schedule = new Schedule((FrequencyUnits)FrequencyUnitsComboBox.SelectedItem, (int)FrequencyUpDown.Value, StartTimePicker.Value.TimeOfDay, EndTimePicker.Value.TimeOfDay); 182 check.Schedule = new Schedule((FrequencyUnits)FrequencyUnitsComboBox.SelectedItem, (int)FrequencyUpDown.Value, StartTimePicker.Value.TimeOfDay, EndTimePicker.Value.TimeOfDay);
183 // Attempt to update the check from the check control inputs.
142 try 184 try
143 { 185 {
144 checkControl?.UpdateCheck(check); 186 checkControl?.UpdateCheck(check);
187 // If the update succeeded, run the check's own validation.
145 result = check.Validate(saving); 188 result = check.Validate(saving);
146 } 189 }
147 catch (UpdateCheckException e) 190 catch (UpdateCheckException e)
148 { 191 {
192 // If the update failed, set the validation message to the error message.
149 result = e.Message; 193 result = e.Message;
150 } 194 }
151 } 195 }
152 if (!result.IsNullOrEmpty()) 196 if (!result.IsNullOrEmpty())
153 { 197 {
155 return false; 199 return false;
156 } 200 }
157 return true; 201 return true;
158 } 202 }
159 203
204 /// <summary>Saves the check and closes the form.</summary>
160 private void OkButton_Click(object sender, EventArgs e) 205 private void OkButton_Click(object sender, EventArgs e)
161 { 206 {
162 if (!UpdateCheck(workCheck)) 207 if (!UpdateCheck(workCheck))
163 return; 208 return;
164 Check = workCheck; 209 if (Check == null)
210 {
211 // If this is a new check, just use the check object created by this form.
212 Check = workCheck;
213 }
214 else
215 {
216 // When editing an existing check, update it now that we know the validation will succeed.
217 // Don't replace it with the temporary check object because the temporary check
218 // is not a complete copy; some properties are missing such as LastRunStatus.
219 UpdateCheck(Check);
220 }
221 UpdateCheck(Check);
165 server.UpdateCheck(Check); 222 server.UpdateCheck(Check);
166 monitor.SaveServers(); 223 monitor.SaveServers();
167 Close(); 224 Close();
168 } 225 }
169 226
227 /// <summary>Cancels the form.</summary>
170 private void CancelCheckButton_Click(object sender, EventArgs e) 228 private void CancelCheckButton_Click(object sender, EventArgs e)
171 { 229 {
172 Close(); 230 Close();
173 } 231 }
174 232
233 /// <summary>Executes the check with the current user inputs.</summary>
175 private async void RunButton_Click(object sender, EventArgs e) 234 private async void RunButton_Click(object sender, EventArgs e)
176 { 235 {
236 // If the the inputs are invalid, show the error and return.
177 if (!UpdateCheck(workCheck, false)) 237 if (!UpdateCheck(workCheck, false))
178 return; 238 return;
179 239
240 // Update controls with the running status.
180 RunButton.Enabled = false; 241 RunButton.Enabled = false;
181 RunButton.Text = "Running"; 242 RunButton.Text = "Running";
182 ResultLabel.Visible = ResultIconPictureBox.Visible = false; 243 ResultLabel.Visible = ResultIconPictureBox.Visible = false;
183 CancelRunButton.Visible = true; 244 CancelRunButton.Visible = true;
184 245
194 OnRunFinished(result); 255 OnRunFinished(result);
195 localCancellationTokenSource.Dispose(); 256 localCancellationTokenSource.Dispose();
196 localCancellationTokenSource = null; 257 localCancellationTokenSource = null;
197 } 258 }
198 259
260 /// <summary>Cancels a check execution.</summary>
199 private void CancelRunButton_Click(object sender, EventArgs e) 261 private void CancelRunButton_Click(object sender, EventArgs e)
200 { 262 {
263 // Request the check to cancel. Some check types are not cancellable.
201 cancellationTokenSource.Cancel(); 264 cancellationTokenSource.Cancel();
265 // Immediately update the UI so the user doesn't have to wait
266 // if a check is uncancellable.
202 OnRunFinished(); 267 OnRunFinished();
203 } 268 }
204 269
205 /// <summary>Updates the UI after a check is finished running.</summary> 270 /// <summary>Updates the UI after a check is finished running.</summary>
206 /// <param name="result">Result of the check execution. If null, the check was cancelled before it completed.</param> 271 /// <param name="result">Result of the check execution. If null, the check was cancelled before it completed.</param>
207 private void OnRunFinished(CheckResult result = null) 272 private void OnRunFinished(CheckResult result = null)
208 { 273 {
209 RunButton.Enabled = true; 274 RunButton.Enabled = true;
210 RunButton.Text = "Run"; 275 RunButton.Text = "Run";
211 CancelRunButton.Visible = false; 276 CancelRunButton.Visible = false;
277 // If the check was not cancelled, show the results.
212 if (result != null) 278 if (result != null)
213 { 279 {
214 ResultLabel.Text = result.Message; 280 ResultLabel.Text = result.Message;
215 ResultIconPictureBox.Image = result.CheckStatus.GetImage(); 281 ResultIconPictureBox.Image = result.CheckStatus.GetImage();
216 ResultLabel.Visible = ResultIconPictureBox.Visible = true; 282 ResultLabel.Visible = ResultIconPictureBox.Visible = true;
217 } 283 }
218 } 284 }
219 285
286 /// <summary>Gathers the descriptions of all check types to show in the Help window.</summary>
220 private string BuildHelpText() 287 private string BuildHelpText()
221 { 288 {
289 // Build the string as RTF to allow bold text.
222 StringBuilder rtf = new StringBuilder(@"{\rtf1\ansi "); 290 StringBuilder rtf = new StringBuilder(@"{\rtf1\ansi ");
223 foreach (Type checkType in Check.CheckTypes) 291 foreach (Type checkType in Check.CheckTypes)
224 { 292 {
225 // Arguments to AppendLine() must end with a \ for the RichTextBox to render the newline character 293 // Show the check type name in bold, and the check type description.
294 // Arguments to AppendLine() must end with a \ for the RichTextBox to render the newline character.
226 rtf.Append(@"\b ").Append(Helpers.GetDisplayName(checkType)).AppendLine(@"\b0 \") 295 rtf.Append(@"\b ").Append(Helpers.GetDisplayName(checkType)).AppendLine(@"\b0 \")
227 .Append(checkType.GetAttribute<DescriptionAttribute>()?.Description ?? "No description provided.").AppendLine(@"\").AppendLine(@"\"); 296 .Append(checkType.GetAttribute<DescriptionAttribute>()?.Description ?? "No description provided.")
297 .AppendLine(@"\").AppendLine(@"\");
228 } 298 }
229 return rtf.ToString().TrimEnd(' ', '\r', '\n', '\\'); 299 return rtf.ToString().TrimEnd(' ', '\r', '\n', '\\');
230 } 300 }
231 301
302 /// <summary>Shows or hides the Help popup window when the Help button is clicked.</summary>
232 private void TypeHelpPictureBox_Click(object sender, EventArgs e) 303 private void TypeHelpPictureBox_Click(object sender, EventArgs e)
233 { 304 {
234 if (helpShown) 305 if (helpShown)
235 { 306 {
236 helpForm.Close(); 307 helpForm.Close();
238 else 309 else
239 { 310 {
240 helpForm = new QuickHelpForm(BuildHelpText()); 311 helpForm = new QuickHelpForm(BuildHelpText());
241 helpForm.FormClosed += QuickHelpForm_FormClosed; 312 helpForm.FormClosed += QuickHelpForm_FormClosed;
242 helpForm.Show(this); 313 helpForm.Show(this);
314 // Keep focus on this form.
243 Activate(); 315 Activate();
244 helpShown = true; 316 helpShown = true;
317 // Trigger the location changed event so the popup will appear in the correct location.
245 OnHelpLocationChanged(); 318 OnHelpLocationChanged();
246 } 319 }
247 } 320 }
248 321
322 /// <summary>Handles the closing of the Help popup.</summary>
249 private void QuickHelpForm_FormClosed(object sender, FormClosedEventArgs e) 323 private void QuickHelpForm_FormClosed(object sender, FormClosedEventArgs e)
250 { 324 {
251 helpShown = false; 325 helpShown = false;
252 helpForm.FormClosed -= QuickHelpForm_FormClosed; 326 helpForm.FormClosed -= QuickHelpForm_FormClosed;
253 helpForm.Dispose(); 327 helpForm.Dispose();
254 } 328 }
255 329
330 /// <summary>Notifies event subscribers that the location of the Help button on the screen changed.</summary>
256 private void OnHelpLocationChanged() 331 private void OnHelpLocationChanged()
257 { 332 {
258 HelpLocationChanged?.Invoke(this, new HelpLocationChangedEventArgs(HelpLocation)); 333 HelpLocationChanged?.Invoke(this, new HelpLocationChangedEventArgs(HelpLocation));
259 } 334 }
260 335
336 /// <summary>The Help button location changes if its panel location changes (such as when the window is resized).</summary>
261 private void CheckTypePanel_LocationChanged(object sender, EventArgs e) 337 private void CheckTypePanel_LocationChanged(object sender, EventArgs e)
262 { 338 {
263 OnHelpLocationChanged(); 339 OnHelpLocationChanged();
264 } 340 }
265 341
342 /// <summary>The Help button location changes if the window is moved.</summary>
266 private void CheckForm_Move(object sender, EventArgs e) 343 private void CheckForm_Move(object sender, EventArgs e)
267 { 344 {
268 OnHelpLocationChanged(); 345 OnHelpLocationChanged();
269 } 346 }
270 347
348 /// <summary>Closes the Help popup when another control is clicked.</summary>
271 private void Control_Click(object sender, EventArgs e) 349 private void Control_Click(object sender, EventArgs e)
272 { 350 {
273 if (helpShown) 351 if (helpShown)
274 helpForm.Close(); 352 helpForm.Close();
275 } 353 }
276 354
355 /// <summary>Hides the Help popup when ESC is pressed.</summary>
277 protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 356 protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
278 { 357 {
279 if (keyData == Keys.Escape && helpShown) 358 if (keyData == Keys.Escape && helpShown)
280 { 359 {
281 helpForm.Close(); 360 helpForm.Close();
282 return true; 361 return true;
283 } 362 }
284 return base.ProcessCmdKey(ref msg, keyData); 363 return base.ProcessCmdKey(ref msg, keyData);
285 } 364 }
286 365
366 /// <summary>Shows appropriate schedule controls depending on the time interval selected.</summary>
287 private void FrequencyUnitsComboBox_SelectedIndexChanged(object sender, EventArgs e) 367 private void FrequencyUnitsComboBox_SelectedIndexChanged(object sender, EventArgs e)
288 { 368 {
369 // Show a single time input for daily schedules.
370 // For more frequent schedules, show a time range.
289 ScheduleBetweenPanel.Visible = !(ScheduleAtPanel.Visible = FrequencyUnitsComboBox.SelectedIndex == 3); 371 ScheduleBetweenPanel.Visible = !(ScheduleAtPanel.Visible = FrequencyUnitsComboBox.SelectedIndex == 3);
290 } 372 }
291 373
374 /// <summary>Formats the FrequencyUnits enum as a string.</summary>
292 private void FrequencyUnitsComboBox_Format(object sender, ListControlConvertEventArgs e) 375 private void FrequencyUnitsComboBox_Format(object sender, ListControlConvertEventArgs e)
293 { 376 {
294 e.Value = e.Value.ToString().ToLower() + "s"; 377 e.Value = e.Value.ToString().ToLower() + "s";
295 } 378 }
296 379
380 /// <summary>Updates the form title when the check's name is changed..</summary>
297 private void NameTextBox_TextChanged(object sender, EventArgs e) 381 private void NameTextBox_TextChanged(object sender, EventArgs e)
298 { 382 {
299 SetTitle(); 383 SetTitle();
300 } 384 }
301 } 385 }
302 386
387 /// <summary>Event arguments containing the location of the Help button.</summary>
303 public class HelpLocationChangedEventArgs : EventArgs 388 public class HelpLocationChangedEventArgs : EventArgs
304 { 389 {
305 public Point HelpLocation { get; private set; } 390 public Point HelpLocation { get; private set; }
306 391
307 public HelpLocationChangedEventArgs(Point helpLocation) 392 public HelpLocationChangedEventArgs(Point helpLocation)