#pragma once #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files #include using namespace System; using namespace System::Windows::Forms; using namespace System::Drawing; // ported from https://stackoverflow.com/a/7785745 // original code by user LarsTech https://stackoverflow.com/users/719186/larstech static ref class SkinnedMessageBox { private: ref class SkinnedMessageBoxForm : Form { private: void SkinnedMessageBoxForm_Shown(Object^ sender, EventArgs^ e) { Activate(); } void SkinnedMessageBoxForm_FormClosed(Object^ sender, FormClosedEventArgs^ e) { if (CancelButton && (DialogResult == System::Windows::Forms::DialogResult::None || DialogResult == System::Windows::Forms::DialogResult::Cancel)) DialogResult = CancelButton->DialogResult; } void InitComponentNew() { this->SuspendLayout(); this->AutoSize = true; this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::FixedDialog; this->Width = 350; this->Height = 140; this->StartPosition = FormStartPosition::CenterScreen; this->MaximizeBox = false; this->MinimizeBox = false; this->ShowInTaskbar = false; this->ShowIcon = false; __try { this->Icon = Application::OpenForms[0]->Icon; } __except (1) { } this->Shown += gcnew EventHandler(this, &SkinnedMessageBoxForm::SkinnedMessageBoxForm_Shown); this->FormClosed += gcnew FormClosedEventHandler(this, &SkinnedMessageBoxForm::SkinnedMessageBoxForm_FormClosed); //this->Font = SystemFonts::MessageBoxFont; //this->Font = gcnew Font(SystemFonts::MessageBoxFont->FontFamily, SystemFonts::MessageBoxFont->SizeInPoints); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::None; this->ResumeLayout(false); this->PerformLayout(); } float ScaleWidth = 1.0; float ScaleHeight = 1.0; int btnWidth = 85; int btnHeight = 23; int btnPadding = 12; static cli::array^ buttonrefs = gcnew cli::array{ gcnew Button, gcnew Button, gcnew Button }; int numbuttons = 1; Button^ AddBtn(String^ text, System::Windows::Forms::DialogResult result, int btnNumber) { Button^ btn = buttonrefs[btnNumber]; btn->Text = text; btn->DialogResult = result; btn->Anchor = AnchorStyles::Bottom | AnchorStyles::Right; btn->Width = btnWidth; btn->Height = btnHeight; btn->Location = Point(ClientSize.Width - btnWidth * (numbuttons - btnNumber) - btnPadding * (numbuttons - btnNumber), ClientSize.Height - btnHeight - btnPadding); Controls->Add(btn); //buttonrefs[btnNumber] = btn; return btn; } void SetBackCol(Control^ elem, Color color) { Type^ elemType = elem->GetType(); if ((elem->HasChildren || elemType == Button::typeid || elemType == ComboBox::typeid) && elemType != NumericUpDown::typeid) { elem->BackColor = color; for (int i = 0; i < elem->Controls->Count; i++) { SetBackCol(elem->Controls[i], color); } } } void SetForeCol(Control^ elem, Color color) { Type^ elemType = elem->GetType(); if (elemType != NumericUpDown::typeid && elemType != TextBox::typeid) { elem->ForeColor = color; } if (elem->HasChildren && elemType != NumericUpDown::typeid) { for (int i = 0; i < elem->Controls->Count; i++) { SetForeCol(elem->Controls[i], color); } } } void SetUIStyle(Control^ elem, FlatStyle style) { Type^ elemType = elem->GetType(); if (elemType == Button::typeid) { ((Button^)elem)->FlatStyle = style; } else if (elemType == ComboBox::typeid) { ((ComboBox^)elem)->FlatStyle = style; } else if (elemType == CheckBox::typeid) { ((CheckBox^)elem)->FlatStyle = style; } if (elem->HasChildren && elemType != NumericUpDown::typeid) { for (int i = 0; i < elem->Controls->Count; i++) { SetUIStyle(elem->Controls[i], style); } } } public: SkinnedMessageBoxForm(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultbutton, MessageBoxOptions options) { InitComponentNew(); Graphics^ grfx = Graphics::FromHwnd(Handle); ScaleWidth = Font->SizeInPoints / 72 * grfx->DpiX / 10.67f; ScaleHeight = ScaleWidth; btnWidth *= ScaleWidth; btnHeight *= ScaleHeight; btnPadding *= ScaleWidth; Width *= ScaleWidth; Height *= ScaleHeight; if (owner->Handle != IntPtr::Zero) { Owner = Control::FromHandle(owner->Handle)->FindForm(); } else { Owner = ActiveForm; } Text = caption; System::Windows::Forms::Padding lblMargin = System::Windows::Forms::Padding(14 * ScaleWidth, 0 * ScaleHeight, 14 * ScaleWidth, 45 * ScaleHeight); Point lblLocation = Point(14 * ScaleWidth, 14 * ScaleHeight); Drawing::Size lblMinSize = Drawing::Size(ClientSize.Width - lblLocation.X - lblMargin.Right, 0); Drawing::Size lblMaxSize = Drawing::Size(600 * ScaleWidth, 0 * ScaleHeight); Label^ MessageLbl = gcnew Label(); MessageLbl->Text = message; MessageLbl->AutoSize = true; MessageLbl->Margin = lblMargin; MessageLbl->Location = lblLocation; MessageLbl->MinimumSize = lblMinSize; MessageLbl->MaximumSize = lblMaxSize; Controls->Add(MessageLbl); if (buttons == MessageBoxButtons::OK) { numbuttons = 1; Button^ OkBtn = AddBtn("&OK", System::Windows::Forms::DialogResult::OK, 0); CancelButton = OkBtn; //(not sure why, but this matches official .NET behaviour) } else if (buttons == MessageBoxButtons::OKCancel) { numbuttons = 2; AddBtn("&OK", System::Windows::Forms::DialogResult::OK, 0); Button^ CancelBtn = AddBtn("&Cancel", System::Windows::Forms::DialogResult::Cancel, 1); CancelButton = CancelBtn; } else if (buttons == MessageBoxButtons::YesNo) { numbuttons = 2; AddBtn("&Yes", System::Windows::Forms::DialogResult::Yes, 0); Button^ NoBtn = AddBtn("&No", System::Windows::Forms::DialogResult::No, 1); CancelButton = NoBtn; //(official .NET doesn't set cancel button, but this seems pretty clear to me...) } else if (buttons == MessageBoxButtons::RetryCancel) { numbuttons = 2; AddBtn("&Retry", System::Windows::Forms::DialogResult::Retry, 0); Button^ CancelBtn = AddBtn("&Cancel", System::Windows::Forms::DialogResult::Cancel, 1); CancelButton = CancelBtn; } else if (buttons == MessageBoxButtons::YesNoCancel) { numbuttons = 3; AddBtn("&Yes", System::Windows::Forms::DialogResult::Yes, 0); AddBtn("&No", System::Windows::Forms::DialogResult::No, 1); Button^ CancelBtn = AddBtn("&Cancel", System::Windows::Forms::DialogResult::Cancel, 2); CancelButton = CancelBtn; } else if (buttons == MessageBoxButtons::AbortRetryIgnore) { numbuttons = 3; AddBtn("&Abort", System::Windows::Forms::DialogResult::Abort, 0); AddBtn("&Retry", System::Windows::Forms::DialogResult::Retry, 1); AddBtn("&Ignore", System::Windows::Forms::DialogResult::Ignore, 2); //this.CancelButton = IgnoreBtn; //(official .NET doesn't set cancel button) } else { throw gcnew System::ComponentModel::InvalidEnumArgumentException("buttons", (int)buttons, MessageBoxButtons::typeid); } if (!CancelButton) ControlBox = false; if (icon != MessageBoxIcon::None) { int iconsize = 32 * ScaleWidth; int iconpadding = 12 * ScaleWidth; int textpadding = -8 * ScaleWidth; PictureBox^ iconPB = gcnew PictureBox(); iconPB->Width = iconsize; iconPB->Height = iconsize; iconPB->Top = 10 * ScaleHeight; iconPB->Left = 10 * ScaleWidth; iconPB->AccessibleName = "Icon"; iconPB->SizeMode = PictureBoxSizeMode::Zoom; System::Resources::ResourceManager^ ResMgr = gcnew System::Resources::ResourceManager("Launcher.SkinnedMessageBox", SkinnedMessageBox::typeid->Assembly); // there are many duplicated values: // https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.messageboxicon?view=netframework-4.7.2 if (icon == MessageBoxIcon::Error) // also Hand and Stop { Drawing::Icon^ erricon = gcnew Drawing::Icon(SystemIcons::Error, iconsize, iconsize); iconPB->Image = erricon->ToBitmap(); iconPB->AccessibleDescription = "Error"; //this.Icon = SystemIcons.Error; System::Media::SystemSounds::Hand->Play(); } else if (icon == MessageBoxIcon::Question) { Drawing::Icon^ qicon = gcnew Drawing::Icon(SystemIcons::Question, iconsize, iconsize); iconPB->Image = qicon->ToBitmap(); iconPB->AccessibleDescription = "Question"; //this.Icon = SystemIcons.Question; System::Media::SystemSounds::Question->Play(); } else if (icon == MessageBoxIcon::Warning) // also Exclamation { iconPB->Image = (Image^)(ResMgr->GetObject("PokeSnarf4xd")); iconPB->AccessibleDescription = "Warning"; //this.Icon = SystemIcons.Warning; //textpadding -= 2 * ScaleWidth; // triangle shape looks worse without this System::Media::SystemSounds::Exclamation->Play(); } else if (icon == MessageBoxIcon::Information) // also Asterisk { Drawing::Icon^ infoicon = gcnew Drawing::Icon(SystemIcons::Information, iconsize, iconsize); iconPB->Image = infoicon->ToBitmap(); iconPB->AccessibleDescription = "Information"; //this.Icon = SystemIcons.Question; System::Media::SystemSounds::Asterisk->Play(); } else { throw gcnew System::ComponentModel::InvalidEnumArgumentException("icon", (int)icon, MessageBoxIcon::typeid); } MessageLbl->MinimumSize = Drawing::Size(MessageLbl->MinimumSize.Width - (iconsize + iconpadding + textpadding), MessageLbl->MinimumSize.Height); MessageLbl->Left += (iconsize + iconpadding + textpadding); Controls->Add(iconPB); } if (defaultbutton == MessageBoxDefaultButton::Button1) { AcceptButton = buttonrefs[0]; ActiveControl = buttonrefs[0]; } else if (defaultbutton == MessageBoxDefaultButton::Button2) { if (!Controls->Contains(buttonrefs[1])) throw gcnew ArgumentOutOfRangeException("defaultbutton", "defaultbutton cannot be set to a button number that does not exist"); AcceptButton = buttonrefs[1]; ActiveControl = buttonrefs[1]; } else if (defaultbutton == MessageBoxDefaultButton::Button3) { if (!Controls->Contains(buttonrefs[2])) throw gcnew ArgumentOutOfRangeException("defaultbutton", "defaultbutton cannot be set to a button number that does not exist"); AcceptButton = buttonrefs[2]; ActiveControl = buttonrefs[2]; } else { throw gcnew System::ComponentModel::InvalidEnumArgumentException("defaultbutton", (int)defaultbutton, MessageBoxDefaultButton::typeid); } // TODO: TEXT ALIGNMENT AND RTL MODE /* // MessageBoxOptions are more like flags than concrete settings. // For ease of use, we assume default value is whatever winforms gives us in current locale -- but we override if any value is set if ((options & MessageBoxOptions.RightAlign) == MessageBoxOptions.RightAlign) MessageLbl.TextAlign = ContentAlignment.TopRight; else if (options != 0) MessageLbl.TextAlign = ContentAlignment.TopLeft; // RtlReading should be what actually affects layout, and RightAlign only affects the text label if ((options & MessageBoxOptions.RtlReading) == MessageBoxOptions.RtlReading) { foreach (Control control in this.Controls) { control.Left = this.ClientSize.Width - control.Right; control.RightToLeft = RightToLeft.Yes; } this.RightToLeft = RightToLeft.Yes; this.RightToLeftLayout = true; } else if (options != 0) { foreach (Control control in this.Controls) { control.RightToLeft = RightToLeft.No; } this.RightToLeft = RightToLeft.No; this.RightToLeftLayout = false; } */ SetBackCol(this, Color::FromArgb(64, 64, 64)); SetForeCol(this, Color::White); SetUIStyle(this, FlatStyle::Flat); } SkinnedMessageBoxForm(String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultbutton, MessageBoxOptions options) { SkinnedMessageBoxForm(NativeWindow::FromHandle((IntPtr::Zero)), message, caption, buttons, icon, defaultbutton, options); } }; public: static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultbutton, MessageBoxOptions options) { // I probably shouldn't do this, but from what I can tell this should be safe enough -- out of thread owner is allowed // and it only seems to appear when debugging (???) Control::CheckForIllegalCrossThreadCalls = false; SkinnedMessageBoxForm^ mb = gcnew SkinnedMessageBoxForm(owner, message, caption, buttons, icon, defaultbutton, options); DialogResult res = mb->ShowDialog(); Control::CheckForIllegalCrossThreadCalls = true; return res; } static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultbutton) { return Show(owner, message, caption, buttons, icon, defaultbutton, (MessageBoxOptions)0); } static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon) { return Show(owner, message, caption, buttons, icon, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0); } static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons) { return Show(owner, message, caption, buttons, MessageBoxIcon::None, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0); } static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption) { return Show(owner, message, caption, MessageBoxButtons::OK, MessageBoxIcon::None, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0); } static DialogResult Show(IWin32Window^ owner, String^ message) { return Show(owner, message, "", MessageBoxButtons::OK, MessageBoxIcon::None, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0); } };