using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using ServerMonitorApp.Properties;
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 bool helpShown;
private CancellationTokenSource cancellationTokenSource;
private Check workCheck;
private CheckControl checkControl;
private QuickHelpForm helpForm;
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();
Check = check;
CheckId = Check.Id;
server = Check.Server;
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();
this.server = server;
this.monitor = monitor;
}
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));
FrequencyUnitsComboBox.SelectedIndex = 1;
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();
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)
{
checkControl.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
checkControl.Click += Control_Click;
CheckSettingsPanel.Controls.Add(checkControl);
}
}
if (checkControl != null)
{
checkControl.Show();
}
}
/// Populates common check controls from a check.
private void LoadCheck(Check check)
{
Icon = Check.LastRunStatus.GetIcon();
NameTextBox.Text = Check.Name;
EnabledCheckBox.Checked = check.Enabled;
TimeoutInput.Value = check.Timeout;
SeverityComboBox.SelectedItem = check.FailStatus;
FailuresInput.Value = check.MaxConsecutiveFailures;
FrequencyUnitsComboBox.SelectedItem = check.Schedule.Units;
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.";
}
else
{
check.Id = CheckId;
check.Server = server;
check.Name = NameTextBox.Text;
check.Enabled = EnabledCheckBox.Checked;
check.Timeout = (int)TimeoutInput.Value;
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;
}
}
if (!result.IsNullOrEmpty())
{
MessageBox.Show(result, "Error validating check", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
return true;
}
/// Saves the check and closes the form.
private void OkButton_Click(object sender, EventArgs e)
{
if (!UpdateCheck(workCheck))
return;
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;
CancelRunButton.Visible = true;
// Create a CancellationTokenSource for this execution, and set it to both variables.
// localCancellationTokenSource will mantain a reference to the token for this execution,
// while cancellationTokenSource might end up referencing a different token if the Cancel
// button is clicked (but the Task itself is unable to be cancelled), then Run is clicked
// again.
CancellationTokenSource localCancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource = localCancellationTokenSource;
// Do not update the check status or execution history when the check is run interactively.
// No need to pollute the history with manual executions that will likely fail as a
// check is being configured.
CheckResult result = await workCheck.ExecuteAsync(cancellationTokenSource.Token, false);
if (!localCancellationTokenSource.IsCancellationRequested)
OnRunFinished(result);
localCancellationTokenSource.Dispose();
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();
}
/// Updates the UI after a check is finished running.
/// Result of the check execution. If null, the check was cancelled before it completed.
private void OnRunFinished(CheckResult result = null)
{
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;
ResultIconPictureBox.Image = result.CheckStatus.GetImage();
ResultLabel.Visible = ResultIconPictureBox.Visible = true;
}
}
/// 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)
{
// 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(@"\");
}
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)
{
helpForm.Close();
}
else
{
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;
helpForm.FormClosed -= QuickHelpForm_FormClosed;
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)
{
helpForm.Close();
return true;
}
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; }
public HelpLocationChangedEventArgs(Point helpLocation)
{
HelpLocation = helpLocation;
}
}
}