Added taskbar flashing to notify users when a task is complete or requires user input (= when FlexibleMessageBox is thrown) while the application is inactive or minimized. Enhanced FlexibleMessageBox to better handle when the application is minimized by centering the dialog on the screen and repositioning it when the form is restored.
912 lines
43 KiB
C#
912 lines
43 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows.Forms;
|
|
|
|
namespace JR.Utils.GUI.Forms
|
|
{
|
|
public class FlexibleMessageBox
|
|
{
|
|
#region Public statics
|
|
|
|
public static double MAX_WIDTH_FACTOR = 0.7;
|
|
public static double MAX_HEIGHT_FACTOR = 0.9;
|
|
public static Font FONT = SystemFonts.MessageBoxFont;
|
|
|
|
#endregion
|
|
|
|
#region Public show functions
|
|
|
|
public static DialogResult Show(string text)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(string text, string caption)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, caption, buttons, icon, defaultButton);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, caption, buttons, icon, defaultButton);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal form class
|
|
|
|
private class FlexibleMessageBoxForm : Form
|
|
{
|
|
#region Constants and P/Invoke
|
|
|
|
private const int CS_DROPSHADOW = 0x00020000;
|
|
private const int WM_NCLBUTTONDOWN = 0xA1;
|
|
private const int HT_CAPTION = 0x2;
|
|
private const uint FLASHW_TRAY = 0x00000002;
|
|
private const uint FLASHW_TIMERNOFG = 0x0000000C;
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct FLASHWINFO
|
|
{
|
|
public uint cbSize;
|
|
public IntPtr hwnd;
|
|
public uint dwFlags;
|
|
public uint uCount;
|
|
public uint dwTimeout;
|
|
}
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool ReleaseCapture();
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool FlashWindowEx(ref FLASHWINFO pwfi);
|
|
|
|
protected override CreateParams CreateParams
|
|
{
|
|
get
|
|
{
|
|
CreateParams cp = base.CreateParams;
|
|
cp.ClassStyle |= CS_DROPSHADOW;
|
|
return cp;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Form-Designer generated code
|
|
|
|
private System.ComponentModel.IContainer components = null;
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing && (components != null))
|
|
{
|
|
components.Dispose();
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private void CloseButton_Click(object sender, EventArgs e)
|
|
{
|
|
this.DialogResult = DialogResult.Cancel;
|
|
this.Close();
|
|
}
|
|
|
|
private void TitlePanel_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Left)
|
|
{
|
|
ReleaseCapture();
|
|
SendMessage(this.Handle, WM_NCLBUTTONDOWN, (IntPtr)HT_CAPTION, IntPtr.Zero);
|
|
}
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
components = new System.ComponentModel.Container();
|
|
button1 = new System.Windows.Forms.Button();
|
|
richTextBoxMessage = new System.Windows.Forms.RichTextBox();
|
|
FlexibleMessageBoxFormBindingSource = new System.Windows.Forms.BindingSource(components);
|
|
panel1 = new System.Windows.Forms.Panel();
|
|
pictureBoxForIcon = new System.Windows.Forms.PictureBox();
|
|
button2 = new System.Windows.Forms.Button();
|
|
button3 = new System.Windows.Forms.Button();
|
|
titlePanel = new System.Windows.Forms.Panel();
|
|
titleLabel = new System.Windows.Forms.Label();
|
|
closeButton = new System.Windows.Forms.Button();
|
|
((System.ComponentModel.ISupportInitialize)FlexibleMessageBoxFormBindingSource).BeginInit();
|
|
panel1.SuspendLayout();
|
|
((System.ComponentModel.ISupportInitialize)pictureBoxForIcon).BeginInit();
|
|
titlePanel.SuspendLayout();
|
|
SuspendLayout();
|
|
//
|
|
// titlePanel
|
|
//
|
|
titlePanel.BackColor = System.Drawing.Color.FromArgb(20, 24, 29);
|
|
titlePanel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
|
titlePanel.Location = new System.Drawing.Point(6, 6);
|
|
titlePanel.Name = "titlePanel";
|
|
titlePanel.Size = new System.Drawing.Size(248, 28);
|
|
titlePanel.TabIndex = 10;
|
|
titlePanel.Controls.Add(closeButton);
|
|
titlePanel.Controls.Add(titleLabel);
|
|
titlePanel.MouseDown += TitlePanel_MouseDown;
|
|
//
|
|
// titleLabel
|
|
//
|
|
titleLabel.AutoSize = false;
|
|
titleLabel.Dock = System.Windows.Forms.DockStyle.Fill;
|
|
titleLabel.ForeColor = System.Drawing.Color.White;
|
|
titleLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
|
|
titleLabel.Location = new System.Drawing.Point(0, 0);
|
|
titleLabel.Name = "titleLabel";
|
|
titleLabel.Padding = new System.Windows.Forms.Padding(12, 0, 0, 0);
|
|
titleLabel.Size = new System.Drawing.Size(218, 28);
|
|
titleLabel.TabIndex = 0;
|
|
titleLabel.Text = "<Caption>";
|
|
titleLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
|
titleLabel.MouseDown += TitlePanel_MouseDown;
|
|
//
|
|
// closeButton
|
|
//
|
|
closeButton.Dock = System.Windows.Forms.DockStyle.Right;
|
|
closeButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
|
closeButton.FlatAppearance.BorderSize = 0;
|
|
closeButton.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(200, 60, 60);
|
|
closeButton.BackColor = System.Drawing.Color.FromArgb(20, 24, 29);
|
|
closeButton.ForeColor = System.Drawing.Color.White;
|
|
closeButton.Font = new System.Drawing.Font("Segoe UI", 9F);
|
|
closeButton.Location = new System.Drawing.Point(218, 0);
|
|
closeButton.Name = "closeButton";
|
|
closeButton.Size = new System.Drawing.Size(30, 28);
|
|
closeButton.TabIndex = 1;
|
|
closeButton.TabStop = false;
|
|
closeButton.Text = "✕";
|
|
closeButton.Click += CloseButton_Click;
|
|
//
|
|
// button1
|
|
//
|
|
button1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
|
button1.DialogResult = System.Windows.Forms.DialogResult.OK;
|
|
button1.Location = new System.Drawing.Point(26, 80);
|
|
button1.Name = "button1";
|
|
button1.Size = new System.Drawing.Size(75, 28);
|
|
button1.TabIndex = 2;
|
|
button1.Text = "OK";
|
|
button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
|
button1.FlatAppearance.BorderSize = 0;
|
|
button1.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(60, 65, 80);
|
|
button1.BackColor = System.Drawing.Color.FromArgb(42, 45, 58);
|
|
button1.ForeColor = System.Drawing.Color.White;
|
|
button1.Font = new System.Drawing.Font("Segoe UI", 9F);
|
|
button1.Cursor = System.Windows.Forms.Cursors.Hand;
|
|
button1.Visible = false;
|
|
//
|
|
// richTextBoxMessage
|
|
//
|
|
richTextBoxMessage.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom
|
|
| System.Windows.Forms.AnchorStyles.Left
|
|
| System.Windows.Forms.AnchorStyles.Right;
|
|
richTextBoxMessage.BackColor = System.Drawing.Color.FromArgb(20, 24, 29);
|
|
richTextBoxMessage.ForeColor = System.Drawing.Color.White;
|
|
richTextBoxMessage.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
|
richTextBoxMessage.DataBindings.Add(new System.Windows.Forms.Binding("Text", FlexibleMessageBoxFormBindingSource, "MessageText", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
|
|
richTextBoxMessage.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
|
richTextBoxMessage.Location = new System.Drawing.Point(46, 6);
|
|
richTextBoxMessage.Margin = new System.Windows.Forms.Padding(0);
|
|
richTextBoxMessage.Name = "richTextBoxMessage";
|
|
richTextBoxMessage.ReadOnly = true;
|
|
richTextBoxMessage.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.Vertical;
|
|
richTextBoxMessage.Size = new System.Drawing.Size(190, 20);
|
|
richTextBoxMessage.TabIndex = 0;
|
|
richTextBoxMessage.TabStop = false;
|
|
richTextBoxMessage.Text = "<Message>";
|
|
richTextBoxMessage.LinkClicked += new System.Windows.Forms.LinkClickedEventHandler(richTextBoxMessage_LinkClicked);
|
|
//
|
|
// panel1
|
|
//
|
|
panel1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom
|
|
| System.Windows.Forms.AnchorStyles.Left
|
|
| System.Windows.Forms.AnchorStyles.Right;
|
|
panel1.BackColor = System.Drawing.Color.FromArgb(20, 24, 29);
|
|
panel1.Controls.Add(pictureBoxForIcon);
|
|
panel1.Controls.Add(richTextBoxMessage);
|
|
panel1.Location = new System.Drawing.Point(6, 34);
|
|
panel1.Name = "panel1";
|
|
panel1.Size = new System.Drawing.Size(248, 59);
|
|
panel1.TabIndex = 1;
|
|
//
|
|
// pictureBoxForIcon
|
|
//
|
|
pictureBoxForIcon.BackColor = System.Drawing.Color.Transparent;
|
|
pictureBoxForIcon.Location = new System.Drawing.Point(15, 15);
|
|
pictureBoxForIcon.Name = "pictureBoxForIcon";
|
|
pictureBoxForIcon.Size = new System.Drawing.Size(32, 32);
|
|
pictureBoxForIcon.TabIndex = 8;
|
|
pictureBoxForIcon.TabStop = false;
|
|
//
|
|
// button2
|
|
//
|
|
button2.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
|
button2.DialogResult = System.Windows.Forms.DialogResult.OK;
|
|
button2.Location = new System.Drawing.Point(107, 80);
|
|
button2.Name = "button2";
|
|
button2.Size = new System.Drawing.Size(75, 28);
|
|
button2.TabIndex = 3;
|
|
button2.Text = "OK";
|
|
button2.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
|
button2.FlatAppearance.BorderSize = 0;
|
|
button2.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(60, 65, 80);
|
|
button2.BackColor = System.Drawing.Color.FromArgb(42, 45, 58);
|
|
button2.ForeColor = System.Drawing.Color.White;
|
|
button2.Font = new System.Drawing.Font("Segoe UI", 9F);
|
|
button2.Cursor = System.Windows.Forms.Cursors.Hand;
|
|
button2.Visible = false;
|
|
//
|
|
// button3
|
|
//
|
|
button3.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
|
button3.DialogResult = System.Windows.Forms.DialogResult.OK;
|
|
button3.Location = new System.Drawing.Point(188, 80);
|
|
button3.Name = "button3";
|
|
button3.Size = new System.Drawing.Size(75, 28);
|
|
button3.TabIndex = 0;
|
|
button3.Text = "OK";
|
|
button3.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
|
button3.FlatAppearance.BorderSize = 0;
|
|
button3.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(60, 65, 80);
|
|
button3.BackColor = System.Drawing.Color.FromArgb(42, 45, 58);
|
|
button3.ForeColor = System.Drawing.Color.White;
|
|
button3.Font = new System.Drawing.Font("Segoe UI", 9F);
|
|
button3.Cursor = System.Windows.Forms.Cursors.Hand;
|
|
button3.Visible = false;
|
|
//
|
|
// FlexibleMessageBoxForm
|
|
//
|
|
AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
|
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
|
BackColor = System.Drawing.Color.FromArgb(25, 25, 30);
|
|
ForeColor = System.Drawing.Color.White;
|
|
FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
|
ClientSize = new System.Drawing.Size(260, 115);
|
|
Padding = new System.Windows.Forms.Padding(5);
|
|
Controls.Add(titlePanel);
|
|
Controls.Add(button3);
|
|
Controls.Add(button2);
|
|
Controls.Add(button1);
|
|
Controls.Add(panel1);
|
|
MaximizeBox = false;
|
|
MinimizeBox = false;
|
|
MinimumSize = new System.Drawing.Size(276, 120);
|
|
Name = "FlexibleMessageBoxForm";
|
|
ShowIcon = false;
|
|
SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
|
|
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
|
Text = "<Caption>";
|
|
Shown += new System.EventHandler(FlexibleMessageBoxForm_Shown);
|
|
((System.ComponentModel.ISupportInitialize)FlexibleMessageBoxFormBindingSource).EndInit();
|
|
panel1.ResumeLayout(false);
|
|
((System.ComponentModel.ISupportInitialize)pictureBoxForIcon).EndInit();
|
|
titlePanel.ResumeLayout(false);
|
|
ResumeLayout(false);
|
|
|
|
// Apply rounded corners and custom painting
|
|
this.Paint += FlexibleMessageBoxForm_Paint;
|
|
button1.Paint += RoundedButton_Paint;
|
|
button2.Paint += RoundedButton_Paint;
|
|
button3.Paint += RoundedButton_Paint;
|
|
|
|
// Setup hover effects for buttons
|
|
SetupButtonHover(button1);
|
|
SetupButtonHover(button2);
|
|
SetupButtonHover(button3);
|
|
|
|
Activate();
|
|
}
|
|
|
|
private Button button1;
|
|
private BindingSource FlexibleMessageBoxFormBindingSource;
|
|
private RichTextBox richTextBoxMessage;
|
|
private Panel panel1;
|
|
private PictureBox pictureBoxForIcon;
|
|
private Button button2;
|
|
private Button button3;
|
|
private Panel titlePanel;
|
|
private Label titleLabel;
|
|
private Button closeButton;
|
|
|
|
#endregion
|
|
|
|
#region Custom Painting
|
|
|
|
private Dictionary<Button, bool> _buttonHoverState = new Dictionary<Button, bool>();
|
|
private const int SHADOW_SIZE = 2;
|
|
private const int CONTENT_RADIUS = 10;
|
|
|
|
private void FlexibleMessageBoxForm_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
|
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
|
|
|
int w = this.Width;
|
|
int h = this.Height;
|
|
|
|
// Draw shadow gradient layers around the content area
|
|
for (int i = SHADOW_SIZE; i >= 1; i--)
|
|
{
|
|
int alpha = (SHADOW_SIZE - i + 1) * 12; // 12, 24, 36, 48, 60
|
|
Rectangle shadowRect = new Rectangle(
|
|
SHADOW_SIZE - i,
|
|
SHADOW_SIZE - i,
|
|
w - (SHADOW_SIZE - i) * 2 - 1,
|
|
h - (SHADOW_SIZE - i) * 2 - 1);
|
|
|
|
using (Pen shadowPen = new Pen(Color.FromArgb(alpha, 0, 0, 0), 1))
|
|
using (GraphicsPath shadowPath = GetRoundedRectPath(shadowRect, CONTENT_RADIUS + i))
|
|
{
|
|
e.Graphics.DrawPath(shadowPen, shadowPath);
|
|
}
|
|
}
|
|
|
|
// Draw content background
|
|
Rectangle contentRect = new Rectangle(SHADOW_SIZE, SHADOW_SIZE, w - SHADOW_SIZE * 2, h - SHADOW_SIZE * 2);
|
|
using (GraphicsPath contentPath = GetRoundedRectPath(contentRect, CONTENT_RADIUS))
|
|
{
|
|
using (SolidBrush bgBrush = new SolidBrush(Color.FromArgb(20, 24, 29)))
|
|
{
|
|
e.Graphics.FillPath(bgBrush, contentPath);
|
|
}
|
|
|
|
// Draw thin border
|
|
using (Pen borderPen = new Pen(Color.FromArgb(70, 80, 100), 1f))
|
|
{
|
|
e.Graphics.DrawPath(borderPen, contentPath);
|
|
}
|
|
}
|
|
|
|
// Apply rounded region to form (with shadow area)
|
|
using (GraphicsPath regionPath = GetRoundedRectPath(new Rectangle(0, 0, w, h), CONTENT_RADIUS + SHADOW_SIZE))
|
|
{
|
|
this.Region = new Region(regionPath);
|
|
}
|
|
}
|
|
|
|
private void SetupButtonHover(Button btn)
|
|
{
|
|
_buttonHoverState[btn] = false;
|
|
|
|
btn.MouseEnter += (s, e) =>
|
|
{
|
|
_buttonHoverState[btn] = true;
|
|
btn.Invalidate();
|
|
};
|
|
|
|
btn.MouseLeave += (s, e) =>
|
|
{
|
|
_buttonHoverState[btn] = false;
|
|
btn.Invalidate();
|
|
};
|
|
}
|
|
|
|
private void RoundedButton_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
Button btn = sender as Button;
|
|
if (btn == null) return;
|
|
|
|
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
|
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
|
|
|
bool isHovered = _buttonHoverState.ContainsKey(btn) && _buttonHoverState[btn];
|
|
|
|
int radius = 4;
|
|
|
|
// Use a rect that's 1 pixel smaller to avoid edge clipping
|
|
Rectangle drawRect = new Rectangle(1, 1, btn.Width - 2, btn.Height - 2);
|
|
|
|
// Fill entire button area with parent background first to clear previous state
|
|
using (SolidBrush clearBrush = new SolidBrush(Color.FromArgb(20, 24, 29)))
|
|
{
|
|
e.Graphics.FillRectangle(clearBrush, 0, 0, btn.Width, btn.Height);
|
|
}
|
|
|
|
using (GraphicsPath path = GetRoundedRectPath(drawRect, radius))
|
|
{
|
|
// Determine colors based on hover state
|
|
Color bgColor = isHovered
|
|
? Color.FromArgb(93, 203, 173) // Accent color on hover
|
|
: btn.BackColor;
|
|
|
|
Color textColor = isHovered
|
|
? Color.FromArgb(20, 20, 20) // Dark text on accent
|
|
: btn.ForeColor;
|
|
|
|
// Draw background
|
|
using (SolidBrush brush = new SolidBrush(bgColor))
|
|
{
|
|
e.Graphics.FillPath(brush, path);
|
|
}
|
|
|
|
// Draw subtle border on normal state
|
|
if (!isHovered)
|
|
{
|
|
using (Pen borderPen = new Pen(Color.FromArgb(70, 75, 90), 1))
|
|
{
|
|
e.Graphics.DrawPath(borderPen, path);
|
|
}
|
|
}
|
|
|
|
// Draw text centered in original button bounds
|
|
TextRenderer.DrawText(e.Graphics, btn.Text, btn.Font,
|
|
new Rectangle(0, 0, btn.Width, btn.Height), textColor,
|
|
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
|
|
}
|
|
|
|
// Set region to full button size (not the draw rect)
|
|
using (GraphicsPath regionPath = GetRoundedRectPath(new Rectangle(0, 0, btn.Width, btn.Height), radius))
|
|
{
|
|
btn.Region = new Region(regionPath);
|
|
}
|
|
}
|
|
|
|
private GraphicsPath GetRoundedRectPath(Rectangle rect, int radius)
|
|
{
|
|
GraphicsPath path = new GraphicsPath();
|
|
|
|
if (radius <= 0)
|
|
{
|
|
path.AddRectangle(rect);
|
|
return path;
|
|
}
|
|
|
|
int diameter = radius * 2;
|
|
|
|
// Ensure diameter doesn't exceed rect dimensions
|
|
diameter = Math.Min(diameter, Math.Min(rect.Width, rect.Height));
|
|
radius = diameter / 2;
|
|
|
|
Rectangle arcRect = new Rectangle(rect.Location, new Size(diameter, diameter));
|
|
|
|
// Top left arc
|
|
path.AddArc(arcRect, 180, 90);
|
|
|
|
// Top right arc
|
|
arcRect.X = rect.Right - diameter;
|
|
path.AddArc(arcRect, 270, 90);
|
|
|
|
// Bottom right arc
|
|
arcRect.Y = rect.Bottom - diameter;
|
|
path.AddArc(arcRect, 0, 90);
|
|
|
|
// Bottom left arc
|
|
arcRect.X = rect.Left;
|
|
path.AddArc(arcRect, 90, 90);
|
|
|
|
path.CloseFigure();
|
|
return path;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private constants
|
|
|
|
private static readonly string STANDARD_MESSAGEBOX_SEPARATOR_LINES = "---------------------------\n";
|
|
private static readonly string STANDARD_MESSAGEBOX_SEPARATOR_SPACES = " ";
|
|
|
|
private enum ButtonID { OK = 0, CANCEL, YES, NO, ABORT, RETRY, IGNORE };
|
|
|
|
private enum TwoLetterISOLanguageID { en, de, es, it };
|
|
private static readonly string[] BUTTON_TEXTS_ENGLISH_EN = { "OK", "Cancel", "&Yes", "&No", "&Abort", "&Retry", "&Ignore" };
|
|
private static readonly string[] BUTTON_TEXTS_GERMAN_DE = { "OK", "Abbrechen", "&Ja", "&Nein", "&Abbrechen", "&Wiederholen", "&Ignorieren" };
|
|
private static readonly string[] BUTTON_TEXTS_SPANISH_ES = { "Aceptar", "Cancelar", "&Sí", "&No", "&Abortar", "&Reintentar", "&Ignorar" };
|
|
private static readonly string[] BUTTON_TEXTS_ITALIAN_IT = { "OK", "Annulla", "&Sì", "&No", "&Interrompi", "&Riprova", "&Ignora" };
|
|
|
|
#endregion
|
|
|
|
#region Private members
|
|
|
|
private MessageBoxDefaultButton defaultButton;
|
|
private int visibleButtonsCount;
|
|
private readonly TwoLetterISOLanguageID languageID = TwoLetterISOLanguageID.en;
|
|
|
|
#endregion
|
|
|
|
#region Private constructor
|
|
|
|
private FlexibleMessageBoxForm()
|
|
{
|
|
InitializeComponent();
|
|
_ = Enum.TryParse<TwoLetterISOLanguageID>(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, out languageID);
|
|
KeyPreview = true;
|
|
KeyUp += FlexibleMessageBoxForm_KeyUp;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private helper functions
|
|
|
|
private static string[] GetStringRows(string message)
|
|
{
|
|
if (string.IsNullOrEmpty(message))
|
|
{
|
|
return null;
|
|
}
|
|
return message.Split(new char[] { '\n' }, StringSplitOptions.None);
|
|
}
|
|
|
|
private string GetButtonText(ButtonID buttonID)
|
|
{
|
|
int buttonTextArrayIndex = Convert.ToInt32(buttonID);
|
|
switch (languageID)
|
|
{
|
|
case TwoLetterISOLanguageID.de: return BUTTON_TEXTS_GERMAN_DE[buttonTextArrayIndex];
|
|
case TwoLetterISOLanguageID.es: return BUTTON_TEXTS_SPANISH_ES[buttonTextArrayIndex];
|
|
case TwoLetterISOLanguageID.it: return BUTTON_TEXTS_ITALIAN_IT[buttonTextArrayIndex];
|
|
default: return BUTTON_TEXTS_ENGLISH_EN[buttonTextArrayIndex];
|
|
}
|
|
}
|
|
|
|
private static double GetCorrectedWorkingAreaFactor(double workingAreaFactor)
|
|
{
|
|
const double MIN_FACTOR = 0.2;
|
|
const double MAX_FACTOR = 1.0;
|
|
return workingAreaFactor < MIN_FACTOR ? MIN_FACTOR : workingAreaFactor > MAX_FACTOR ? MAX_FACTOR : workingAreaFactor;
|
|
}
|
|
|
|
private static void SetDialogStartPosition(FlexibleMessageBoxForm flexibleMessageBoxForm, IWin32Window owner)
|
|
{
|
|
flexibleMessageBoxForm.StartPosition = FormStartPosition.Manual;
|
|
|
|
// Try to get owner form, fallback to active form if owner is null
|
|
Form ownerForm = null;
|
|
if (owner != null)
|
|
{
|
|
ownerForm = owner as Form;
|
|
if (ownerForm == null)
|
|
{
|
|
Control ownerControl = Control.FromHandle(owner.Handle);
|
|
ownerForm = ownerControl?.FindForm();
|
|
}
|
|
}
|
|
|
|
// Fallback to active form if no owner specified
|
|
if (ownerForm == null)
|
|
{
|
|
ownerForm = Form.ActiveForm;
|
|
}
|
|
|
|
if (ownerForm != null && ownerForm.Visible && ownerForm.WindowState != FormWindowState.Minimized)
|
|
{
|
|
// Center relative to owner window
|
|
int x = ownerForm.Left + (ownerForm.Width - flexibleMessageBoxForm.Width) / 2;
|
|
int y = ownerForm.Top + (ownerForm.Height - flexibleMessageBoxForm.Height) / 2;
|
|
|
|
// Ensure the dialog stays within screen bounds
|
|
Screen screen = Screen.FromControl(ownerForm);
|
|
x = Math.Max(screen.WorkingArea.Left, Math.Min(x, screen.WorkingArea.Right - flexibleMessageBoxForm.Width));
|
|
y = Math.Max(screen.WorkingArea.Top, Math.Min(y, screen.WorkingArea.Bottom - flexibleMessageBoxForm.Height));
|
|
|
|
flexibleMessageBoxForm.Left = x;
|
|
flexibleMessageBoxForm.Top = y;
|
|
}
|
|
else
|
|
{
|
|
// No owner found or minimized: center on current screen
|
|
CenterOnScreen(flexibleMessageBoxForm);
|
|
}
|
|
}
|
|
|
|
private static void CenterOnScreen(FlexibleMessageBoxForm form)
|
|
{
|
|
Screen screen = Screen.FromPoint(Cursor.Position);
|
|
form.Left = screen.WorkingArea.Left + (screen.WorkingArea.Width - form.Width) / 2;
|
|
form.Top = screen.WorkingArea.Top + (screen.WorkingArea.Height - form.Height) / 2;
|
|
}
|
|
|
|
private static void SetDialogSizes(FlexibleMessageBoxForm flexibleMessageBoxForm, string text, string caption)
|
|
{
|
|
flexibleMessageBoxForm.MaximumSize = new Size(Convert.ToInt32(SystemInformation.WorkingArea.Width * FlexibleMessageBoxForm.GetCorrectedWorkingAreaFactor(MAX_WIDTH_FACTOR)),
|
|
Convert.ToInt32(SystemInformation.WorkingArea.Height * FlexibleMessageBoxForm.GetCorrectedWorkingAreaFactor(MAX_HEIGHT_FACTOR)));
|
|
|
|
string[] stringRows = GetStringRows(text);
|
|
if (stringRows == null) return;
|
|
|
|
int textHeight = TextRenderer.MeasureText(text, FONT).Height;
|
|
const int SCROLLBAR_WIDTH_OFFSET = 15;
|
|
int longestTextRowWidth = stringRows.Max(textForRow => TextRenderer.MeasureText(textForRow, FONT).Width);
|
|
int captionWidth = TextRenderer.MeasureText(caption, SystemFonts.CaptionFont).Width;
|
|
int textWidth = Math.Max(longestTextRowWidth + SCROLLBAR_WIDTH_OFFSET, captionWidth);
|
|
|
|
int marginWidth = flexibleMessageBoxForm.Width - flexibleMessageBoxForm.richTextBoxMessage.Width;
|
|
int marginHeight = flexibleMessageBoxForm.Height - flexibleMessageBoxForm.richTextBoxMessage.Height;
|
|
|
|
flexibleMessageBoxForm.Size = new Size(textWidth + marginWidth, textHeight + marginHeight);
|
|
}
|
|
|
|
private static void SetDialogIcon(FlexibleMessageBoxForm flexibleMessageBoxForm, MessageBoxIcon icon)
|
|
{
|
|
switch (icon)
|
|
{
|
|
case MessageBoxIcon.Information:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Image = SystemIcons.Information.ToBitmap();
|
|
break;
|
|
case MessageBoxIcon.Warning:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Image = SystemIcons.Warning.ToBitmap();
|
|
break;
|
|
case MessageBoxIcon.Error:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Image = SystemIcons.Error.ToBitmap();
|
|
break;
|
|
case MessageBoxIcon.Question:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Image = SystemIcons.Question.ToBitmap();
|
|
break;
|
|
default:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Visible = false;
|
|
flexibleMessageBoxForm.richTextBoxMessage.Left -= flexibleMessageBoxForm.pictureBoxForIcon.Width;
|
|
flexibleMessageBoxForm.richTextBoxMessage.Width += flexibleMessageBoxForm.pictureBoxForIcon.Width;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static void SetDialogButtons(FlexibleMessageBoxForm flexibleMessageBoxForm, MessageBoxButtons buttons, MessageBoxDefaultButton defaultButton)
|
|
{
|
|
switch (buttons)
|
|
{
|
|
case MessageBoxButtons.AbortRetryIgnore:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 3;
|
|
flexibleMessageBoxForm.button1.Visible = true;
|
|
flexibleMessageBoxForm.button1.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.ABORT);
|
|
flexibleMessageBoxForm.button1.DialogResult = DialogResult.Abort;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.RETRY);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.Retry;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.IGNORE);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.Ignore;
|
|
flexibleMessageBoxForm.ControlBox = false;
|
|
break;
|
|
|
|
case MessageBoxButtons.OKCancel:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 2;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.OK);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.OK;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.CANCEL);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.Cancel;
|
|
flexibleMessageBoxForm.CancelButton = flexibleMessageBoxForm.button3;
|
|
break;
|
|
|
|
case MessageBoxButtons.RetryCancel:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 2;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.RETRY);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.Retry;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.CANCEL);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.Cancel;
|
|
flexibleMessageBoxForm.CancelButton = flexibleMessageBoxForm.button3;
|
|
break;
|
|
|
|
case MessageBoxButtons.YesNo:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 2;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.YES);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.Yes;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.NO);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.No;
|
|
flexibleMessageBoxForm.ControlBox = false;
|
|
break;
|
|
|
|
case MessageBoxButtons.YesNoCancel:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 3;
|
|
flexibleMessageBoxForm.button1.Visible = true;
|
|
flexibleMessageBoxForm.button1.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.YES);
|
|
flexibleMessageBoxForm.button1.DialogResult = DialogResult.Yes;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.NO);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.No;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.CANCEL);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.Cancel;
|
|
flexibleMessageBoxForm.CancelButton = flexibleMessageBoxForm.button3;
|
|
break;
|
|
|
|
case MessageBoxButtons.OK:
|
|
default:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 1;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.OK);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.OK;
|
|
flexibleMessageBoxForm.CancelButton = flexibleMessageBoxForm.button3;
|
|
break;
|
|
}
|
|
flexibleMessageBoxForm.defaultButton = defaultButton;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private event handlers
|
|
|
|
private void FlexibleMessageBoxForm_Shown(object sender, EventArgs e)
|
|
{
|
|
int buttonIndexToFocus;
|
|
switch (defaultButton)
|
|
{
|
|
case MessageBoxDefaultButton.Button1:
|
|
default:
|
|
buttonIndexToFocus = 1;
|
|
break;
|
|
case MessageBoxDefaultButton.Button2:
|
|
buttonIndexToFocus = 2;
|
|
break;
|
|
case MessageBoxDefaultButton.Button3:
|
|
buttonIndexToFocus = 3;
|
|
break;
|
|
}
|
|
|
|
if (buttonIndexToFocus > visibleButtonsCount)
|
|
buttonIndexToFocus = visibleButtonsCount;
|
|
|
|
Button buttonToFocus = buttonIndexToFocus == 3 ? button3 : buttonIndexToFocus == 2 ? button2 : button1;
|
|
_ = buttonToFocus.Focus();
|
|
}
|
|
|
|
private void richTextBoxMessage_LinkClicked(object sender, LinkClickedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
_ = Process.Start(e.LinkText);
|
|
}
|
|
finally
|
|
{
|
|
Cursor.Current = Cursors.Default;
|
|
}
|
|
}
|
|
|
|
private void FlexibleMessageBoxForm_KeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
if (e.Control && (e.KeyCode == Keys.C || e.KeyCode == Keys.Insert))
|
|
{
|
|
string buttonsTextLine = (button1.Visible ? button1.Text + STANDARD_MESSAGEBOX_SEPARATOR_SPACES : string.Empty)
|
|
+ (button2.Visible ? button2.Text + STANDARD_MESSAGEBOX_SEPARATOR_SPACES : string.Empty)
|
|
+ (button3.Visible ? button3.Text + STANDARD_MESSAGEBOX_SEPARATOR_SPACES : string.Empty);
|
|
|
|
string textForClipboard = STANDARD_MESSAGEBOX_SEPARATOR_LINES
|
|
+ Text + Environment.NewLine
|
|
+ STANDARD_MESSAGEBOX_SEPARATOR_LINES
|
|
+ richTextBoxMessage.Text + Environment.NewLine
|
|
+ STANDARD_MESSAGEBOX_SEPARATOR_LINES
|
|
+ buttonsTextLine.Replace("&", string.Empty) + Environment.NewLine
|
|
+ STANDARD_MESSAGEBOX_SEPARATOR_LINES;
|
|
|
|
Clipboard.SetText(textForClipboard);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public string CaptionText { get; set; }
|
|
public string MessageText { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region Public show function
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
|
|
{
|
|
FlexibleMessageBoxForm flexibleMessageBoxForm = new FlexibleMessageBoxForm
|
|
{
|
|
ShowInTaskbar = false,
|
|
CaptionText = caption,
|
|
MessageText = text
|
|
};
|
|
flexibleMessageBoxForm.FlexibleMessageBoxFormBindingSource.DataSource = flexibleMessageBoxForm;
|
|
flexibleMessageBoxForm.titleLabel.Text = caption;
|
|
|
|
SetDialogButtons(flexibleMessageBoxForm, buttons, defaultButton);
|
|
SetDialogIcon(flexibleMessageBoxForm, icon);
|
|
|
|
flexibleMessageBoxForm.Font = FONT;
|
|
flexibleMessageBoxForm.richTextBoxMessage.Font = FONT;
|
|
|
|
SetDialogSizes(flexibleMessageBoxForm, text, caption);
|
|
|
|
// Force panel resize to fix closebutton position
|
|
int contentWidth = flexibleMessageBoxForm.ClientSize.Width - 16; // 8px padding
|
|
flexibleMessageBoxForm.titlePanel.Width = contentWidth;
|
|
|
|
// Get owner form
|
|
Form ownerForm = owner as Form ?? Form.ActiveForm;
|
|
bool ownerWasMinimized = ownerForm != null && ownerForm.WindowState == FormWindowState.Minimized;
|
|
|
|
SetDialogStartPosition(flexibleMessageBoxForm, owner);
|
|
|
|
// If owner was minimized, reposition dialog when owner is restored
|
|
if (ownerWasMinimized && ownerForm != null)
|
|
{
|
|
EventHandler resizeHandler = null;
|
|
resizeHandler = (s, e) =>
|
|
{
|
|
if (ownerForm.WindowState != FormWindowState.Minimized && flexibleMessageBoxForm.Visible && !flexibleMessageBoxForm.IsDisposed)
|
|
{
|
|
SetDialogStartPosition(flexibleMessageBoxForm, owner);
|
|
ownerForm.Resize -= resizeHandler;
|
|
}
|
|
};
|
|
ownerForm.Resize += resizeHandler;
|
|
}
|
|
|
|
// Flash taskbar if application is inactive or minimized
|
|
if (Form.ActiveForm == null || ownerWasMinimized)
|
|
{
|
|
Form targetForm = ownerForm ?? Application.OpenForms.Cast<Form>().FirstOrDefault(f => f != null && !f.IsDisposed && f != flexibleMessageBoxForm && f.Visible);
|
|
if (targetForm != null && !targetForm.IsDisposed)
|
|
{
|
|
FLASHWINFO info = new FLASHWINFO
|
|
{
|
|
cbSize = (uint)Marshal.SizeOf(typeof(FLASHWINFO)),
|
|
hwnd = targetForm.Handle,
|
|
dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG,
|
|
uCount = 5,
|
|
dwTimeout = 0
|
|
};
|
|
_ = FlashWindowEx(ref info);
|
|
}
|
|
}
|
|
|
|
return flexibleMessageBoxForm.ShowDialog(owner);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |