# HG changeset patch # User Brad Greco # Date 1555981887 14400 # Node ID 23f2e0da10941ad37aae6ee831aa8406495b2d72 # Parent 2db36ab759de9bb46110e9fd0130ab75bbd6de8e - Fix the last execution status being lost after a check is edited. - Add comments. diff -r 2db36ab759de -r 23f2e0da1094 ServerMonitor/Forms/CheckForm.cs --- a/ServerMonitor/Forms/CheckForm.cs Mon Apr 22 21:10:42 2019 -0400 +++ b/ServerMonitor/Forms/CheckForm.cs Mon Apr 22 21:11:27 2019 -0400 @@ -10,9 +10,13 @@ namespace ServerMonitorApp { + /// Form for creating and editing a check. + /// + /// This form contains controls common to editing all types of checks. + /// Additional controls for specific check types may be shown in a panel below. + /// public partial class CheckForm : Form { - private readonly List checkControls = new List(); private bool helpShown; private CancellationTokenSource cancellationTokenSource; private Check workCheck; @@ -21,14 +25,22 @@ private Server server; private ServerMonitor monitor; + /// Raised when the Help button's location on the screen changes. public event EventHandler HelpLocationChanged; + /// The check being edited by the form. + /// This check object is not updated with the form values until the OK button is clicked. public Check Check { get; private set; } + /// The ID of the check being edited. public int CheckId { get; private set; } + /// The Help button's location on the screen. public Point HelpLocation => TypeHelpPictureBox.PointToScreen(Point.Empty); + /// Creates a check form to edit an existing check. + /// The server monitor. + /// The check to edit. public CheckForm(ServerMonitor monitor, Check check) { InitializeComponent(); @@ -39,6 +51,9 @@ this.monitor = monitor; } + /// Creates a check form for creating a new check. + /// The server monitor. + /// The server the new check will run on. public CheckForm(ServerMonitor monitor, Server server) { InitializeComponent(); @@ -48,51 +63,65 @@ private void CheckForm_Load(object sender, EventArgs e) { + // Set up control default values. CheckTypeComboBox.Items.AddRange(Check.CheckTypes); SeverityComboBox.Items.AddRange(new object[] { CheckStatus.Error, CheckStatus.Warning, CheckStatus.Information }); SeverityComboBox.SelectedIndex = 0; FrequencyUnitsComboBox.DataSource = Enum.GetValues(typeof(FrequencyUnits)); Helpers.FormatImageButton(RunButton); Helpers.FormatImageButton(CancelRunButton); + Icon = Resources.icon; + // Bind event listeners. Move += CheckForm_Move; CheckTypePanel.LocationChanged += CheckTypePanel_LocationChanged; GeneralGroupBox.Click += Control_Click; CheckSettingsPanel.Click += Control_Click; + // Set control values from the check. CheckTypeComboBox.SelectedItem = Check?.GetType(); SetTitle(); - Icon = Resources.icon; if (Check != null) LoadCheck(Check); } + /// Sets the form title from the check, or a default value for a new check. private void SetTitle() { string name = NameTextBox.Text.IsNullOrEmpty() ? "New Check" : NameTextBox.Text; Text = string.Format("{0}: {1}", server.Name, name); } + /// Handles the check type combo box changing. private void CheckTypeComboBox_SelectedIndexChanged(object sender, EventArgs e) { + // Show the check control for the selected check type. ShowCheckControl(); + // Create a temporary instance of the selected check type that will be used for validation + // and execution that can be discarded if the Cancel button is clicked. workCheck = (Check)Activator.CreateInstance((Type)CheckTypeComboBox.SelectedItem); } + /// Shows the display name of each check type in the combo box. private void CheckTypeComboBox_Format(object sender, ListControlConvertEventArgs e) { e.Value = Helpers.GetDisplayName((Type)e.ListItem); } + /// Shows a check control containing settings for the selected check type. private void ShowCheckControl() { + // Hide the existing check control. IEnumerable checkControls = CheckSettingsPanel.Controls.OfType(); foreach (CheckControl control in checkControls) control.Hide(); + // Show the check control for the selected check type if it has already been created. + // Allows switching check types without losing data. Type type = (Type)CheckTypeComboBox.SelectedItem; checkControl = checkControls.FirstOrDefault(c => c.CheckType == type); if (checkControl == null) { + // Create a new check control if one has not been created yet for this check type. checkControl = CheckControl.Create(type); if (checkControl != null) { @@ -107,6 +136,7 @@ } } + /// Populates common check controls from a check. private void LoadCheck(Check check) { Icon = Check.LastRunStatus.GetIcon(); @@ -119,12 +149,23 @@ FrequencyUpDown.Value = check.Schedule.Frequency; StartTimePicker.Value = new DateTime(1970, 1, 1) + check.Schedule.StartTime; EndTimePicker.Value = new DateTime(1970, 1, 1) + check.Schedule.EndTime; + // Populate the controls specific to this check type. checkControl?.LoadCheck(check); } + /// Updates a check with user inputs. + /// The check to be updated. + /// + /// Whether the check is being saved. + /// Checks are validated before being saved and before being executed. This parameter allows + /// them to distinguish between these cases. + /// + /// Whether the check was updated successfully. private bool UpdateCheck(Check check, bool saving = true) { + // The validation result message. An empty value indicates that validation succeeded. string result; + // Make sure we have a valid check type before doing anything else. if (CheckTypeComboBox.SelectedIndex == -1) { result = "Check type cannot be blank."; @@ -139,13 +180,16 @@ check.FailStatus = (CheckStatus)SeverityComboBox.SelectedItem; check.MaxConsecutiveFailures = (int)FailuresInput.Value; check.Schedule = new Schedule((FrequencyUnits)FrequencyUnitsComboBox.SelectedItem, (int)FrequencyUpDown.Value, StartTimePicker.Value.TimeOfDay, EndTimePicker.Value.TimeOfDay); + // Attempt to update the check from the check control inputs. try { checkControl?.UpdateCheck(check); + // If the update succeeded, run the check's own validation. result = check.Validate(saving); } catch (UpdateCheckException e) { + // If the update failed, set the validation message to the error message. result = e.Message; } } @@ -157,26 +201,43 @@ return true; } + /// Saves the check and closes the form. private void OkButton_Click(object sender, EventArgs e) { if (!UpdateCheck(workCheck)) return; - Check = workCheck; + if (Check == null) + { + // If this is a new check, just use the check object created by this form. + Check = workCheck; + } + else + { + // When editing an existing check, update it now that we know the validation will succeed. + // Don't replace it with the temporary check object because the temporary check + // is not a complete copy; some properties are missing such as LastRunStatus. + UpdateCheck(Check); + } + UpdateCheck(Check); server.UpdateCheck(Check); monitor.SaveServers(); Close(); } + /// Cancels the form. private void CancelCheckButton_Click(object sender, EventArgs e) { Close(); } + /// Executes the check with the current user inputs. private async void RunButton_Click(object sender, EventArgs e) { + // If the the inputs are invalid, show the error and return. if (!UpdateCheck(workCheck, false)) return; + // Update controls with the running status. RunButton.Enabled = false; RunButton.Text = "Running"; ResultLabel.Visible = ResultIconPictureBox.Visible = false; @@ -196,9 +257,13 @@ localCancellationTokenSource = null; } + /// Cancels a check execution. private void CancelRunButton_Click(object sender, EventArgs e) { + // Request the check to cancel. Some check types are not cancellable. cancellationTokenSource.Cancel(); + // Immediately update the UI so the user doesn't have to wait + // if a check is uncancellable. OnRunFinished(); } @@ -209,6 +274,7 @@ RunButton.Enabled = true; RunButton.Text = "Run"; CancelRunButton.Visible = false; + // If the check was not cancelled, show the results. if (result != null) { ResultLabel.Text = result.Message; @@ -217,18 +283,23 @@ } } + /// Gathers the descriptions of all check types to show in the Help window. private string BuildHelpText() { + // Build the string as RTF to allow bold text. StringBuilder rtf = new StringBuilder(@"{\rtf1\ansi "); foreach (Type checkType in Check.CheckTypes) { - // Arguments to AppendLine() must end with a \ for the RichTextBox to render the newline character + // Show the check type name in bold, and the check type description. + // Arguments to AppendLine() must end with a \ for the RichTextBox to render the newline character. rtf.Append(@"\b ").Append(Helpers.GetDisplayName(checkType)).AppendLine(@"\b0 \") - .Append(checkType.GetAttribute()?.Description ?? "No description provided.").AppendLine(@"\").AppendLine(@"\"); + .Append(checkType.GetAttribute()?.Description ?? "No description provided.") + .AppendLine(@"\").AppendLine(@"\"); } return rtf.ToString().TrimEnd(' ', '\r', '\n', '\\'); } + /// Shows or hides the Help popup window when the Help button is clicked. private void TypeHelpPictureBox_Click(object sender, EventArgs e) { if (helpShown) @@ -240,12 +311,15 @@ helpForm = new QuickHelpForm(BuildHelpText()); helpForm.FormClosed += QuickHelpForm_FormClosed; helpForm.Show(this); + // Keep focus on this form. Activate(); helpShown = true; + // Trigger the location changed event so the popup will appear in the correct location. OnHelpLocationChanged(); } } + /// Handles the closing of the Help popup. private void QuickHelpForm_FormClosed(object sender, FormClosedEventArgs e) { helpShown = false; @@ -253,27 +327,32 @@ helpForm.Dispose(); } + /// Notifies event subscribers that the location of the Help button on the screen changed. private void OnHelpLocationChanged() { HelpLocationChanged?.Invoke(this, new HelpLocationChangedEventArgs(HelpLocation)); } + /// The Help button location changes if its panel location changes (such as when the window is resized). private void CheckTypePanel_LocationChanged(object sender, EventArgs e) { OnHelpLocationChanged(); } + /// The Help button location changes if the window is moved. private void CheckForm_Move(object sender, EventArgs e) { OnHelpLocationChanged(); } + /// Closes the Help popup when another control is clicked. private void Control_Click(object sender, EventArgs e) { if (helpShown) helpForm.Close(); } + /// Hides the Help popup when ESC is pressed. protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == Keys.Escape && helpShown) @@ -284,22 +363,28 @@ return base.ProcessCmdKey(ref msg, keyData); } + /// Shows appropriate schedule controls depending on the time interval selected. private void FrequencyUnitsComboBox_SelectedIndexChanged(object sender, EventArgs e) { + // Show a single time input for daily schedules. + // For more frequent schedules, show a time range. ScheduleBetweenPanel.Visible = !(ScheduleAtPanel.Visible = FrequencyUnitsComboBox.SelectedIndex == 3); } + /// Formats the FrequencyUnits enum as a string. private void FrequencyUnitsComboBox_Format(object sender, ListControlConvertEventArgs e) { e.Value = e.Value.ToString().ToLower() + "s"; } + /// Updates the form title when the check's name is changed.. private void NameTextBox_TextChanged(object sender, EventArgs e) { SetTitle(); } } + /// Event arguments containing the location of the Help button. public class HelpLocationChangedEventArgs : EventArgs { public Point HelpLocation { get; private set; }