SkinnedMessageBox.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. #pragma once
  2. #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
  3. // Windows Header Files
  4. #include <windows.h>
  5. using namespace System;
  6. using namespace System::Windows::Forms;
  7. using namespace System::Drawing;
  8. // ported from https://stackoverflow.com/a/7785745
  9. // original code by user LarsTech https://stackoverflow.com/users/719186/larstech
  10. static ref class SkinnedMessageBox
  11. {
  12. private: ref class SkinnedMessageBoxForm : Form
  13. {
  14. private:
  15. void SkinnedMessageBoxForm_Shown(Object^ sender, EventArgs^ e)
  16. {
  17. Activate();
  18. }
  19. void SkinnedMessageBoxForm_FormClosed(Object^ sender, FormClosedEventArgs^ e)
  20. {
  21. if (CancelButton && (DialogResult == System::Windows::Forms::DialogResult::None || DialogResult == System::Windows::Forms::DialogResult::Cancel))
  22. DialogResult = CancelButton->DialogResult;
  23. }
  24. void InitComponentNew()
  25. {
  26. this->SuspendLayout();
  27. this->AutoSize = true;
  28. this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::FixedDialog;
  29. this->Width = 350;
  30. this->Height = 140;
  31. this->StartPosition = FormStartPosition::CenterScreen;
  32. this->MaximizeBox = false;
  33. this->MinimizeBox = false;
  34. this->ShowInTaskbar = false;
  35. this->ShowIcon = false;
  36. __try
  37. {
  38. this->Icon = Application::OpenForms[0]->Icon;
  39. }
  40. __except (1) { }
  41. this->Shown += gcnew EventHandler(this, &SkinnedMessageBoxForm::SkinnedMessageBoxForm_Shown);
  42. this->FormClosed += gcnew FormClosedEventHandler(this, &SkinnedMessageBoxForm::SkinnedMessageBoxForm_FormClosed);
  43. //this->Font = SystemFonts::MessageBoxFont;
  44. //this->Font = gcnew Font(SystemFonts::MessageBoxFont->FontFamily, SystemFonts::MessageBoxFont->SizeInPoints);
  45. this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::None;
  46. this->ResumeLayout(false);
  47. this->PerformLayout();
  48. }
  49. float ScaleWidth = 1.0;
  50. float ScaleHeight = 1.0;
  51. int btnWidth = 85;
  52. int btnHeight = 23;
  53. int btnPadding = 12;
  54. static cli::array<Button^>^ buttonrefs = gcnew cli::array<Button^>{ gcnew Button, gcnew Button, gcnew Button };
  55. int numbuttons = 1;
  56. Button^ AddBtn(String^ text, System::Windows::Forms::DialogResult result, int btnNumber)
  57. {
  58. Button^ btn = buttonrefs[btnNumber];
  59. btn->Text = text;
  60. btn->DialogResult = result;
  61. btn->Anchor = AnchorStyles::Bottom | AnchorStyles::Right;
  62. btn->Width = btnWidth;
  63. btn->Height = btnHeight;
  64. btn->Location = Point(ClientSize.Width - btnWidth * (numbuttons - btnNumber) - btnPadding * (numbuttons - btnNumber), ClientSize.Height - btnHeight - btnPadding);
  65. Controls->Add(btn);
  66. //buttonrefs[btnNumber] = btn;
  67. return btn;
  68. }
  69. void SetBackCol(Control^ elem, Color color)
  70. {
  71. Type^ elemType = elem->GetType();
  72. if ((elem->HasChildren || elemType == Button::typeid || elemType == ComboBox::typeid) && elemType != NumericUpDown::typeid)
  73. {
  74. elem->BackColor = color;
  75. for (int i = 0; i < elem->Controls->Count; i++)
  76. {
  77. SetBackCol(elem->Controls[i], color);
  78. }
  79. }
  80. }
  81. void SetForeCol(Control^ elem, Color color)
  82. {
  83. Type^ elemType = elem->GetType();
  84. if (elemType != NumericUpDown::typeid && elemType != TextBox::typeid)
  85. {
  86. elem->ForeColor = color;
  87. }
  88. if (elem->HasChildren && elemType != NumericUpDown::typeid)
  89. {
  90. for (int i = 0; i < elem->Controls->Count; i++)
  91. {
  92. SetForeCol(elem->Controls[i], color);
  93. }
  94. }
  95. }
  96. void SetUIStyle(Control^ elem, FlatStyle style)
  97. {
  98. Type^ elemType = elem->GetType();
  99. if (elemType == Button::typeid)
  100. {
  101. ((Button^)elem)->FlatStyle = style;
  102. }
  103. else if (elemType == ComboBox::typeid)
  104. {
  105. ((ComboBox^)elem)->FlatStyle = style;
  106. }
  107. else if (elemType == CheckBox::typeid)
  108. {
  109. ((CheckBox^)elem)->FlatStyle = style;
  110. }
  111. if (elem->HasChildren && elemType != NumericUpDown::typeid)
  112. {
  113. for (int i = 0; i < elem->Controls->Count; i++)
  114. {
  115. SetUIStyle(elem->Controls[i], style);
  116. }
  117. }
  118. }
  119. public:
  120. SkinnedMessageBoxForm(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultbutton, MessageBoxOptions options)
  121. {
  122. InitComponentNew();
  123. Graphics^ grfx = Graphics::FromHwnd(Handle);
  124. ScaleWidth = Font->SizeInPoints / 72 * grfx->DpiX / 10.67f;
  125. ScaleHeight = ScaleWidth;
  126. btnWidth *= ScaleWidth;
  127. btnHeight *= ScaleHeight;
  128. btnPadding *= ScaleWidth;
  129. Width *= ScaleWidth;
  130. Height *= ScaleHeight;
  131. if (owner->Handle != IntPtr::Zero)
  132. {
  133. Owner = Control::FromHandle(owner->Handle)->FindForm();
  134. }
  135. else
  136. {
  137. Owner = ActiveForm;
  138. }
  139. Text = caption;
  140. System::Windows::Forms::Padding lblMargin = System::Windows::Forms::Padding(14 * ScaleWidth, 0 * ScaleHeight, 14 * ScaleWidth, 45 * ScaleHeight);
  141. Point lblLocation = Point(14 * ScaleWidth, 14 * ScaleHeight);
  142. Drawing::Size lblMinSize = Drawing::Size(ClientSize.Width - lblLocation.X - lblMargin.Right, 0);
  143. Drawing::Size lblMaxSize = Drawing::Size(600 * ScaleWidth, 0 * ScaleHeight);
  144. Label^ MessageLbl = gcnew Label();
  145. MessageLbl->Text = message;
  146. MessageLbl->AutoSize = true;
  147. MessageLbl->Margin = lblMargin;
  148. MessageLbl->Location = lblLocation;
  149. MessageLbl->MinimumSize = lblMinSize;
  150. MessageLbl->MaximumSize = lblMaxSize;
  151. Controls->Add(MessageLbl);
  152. if (buttons == MessageBoxButtons::OK)
  153. {
  154. numbuttons = 1;
  155. Button^ OkBtn = AddBtn("&OK", System::Windows::Forms::DialogResult::OK, 0);
  156. CancelButton = OkBtn;
  157. //(not sure why, but this matches official .NET behaviour)
  158. }
  159. else if (buttons == MessageBoxButtons::OKCancel)
  160. {
  161. numbuttons = 2;
  162. AddBtn("&OK", System::Windows::Forms::DialogResult::OK, 0);
  163. Button^ CancelBtn = AddBtn("&Cancel", System::Windows::Forms::DialogResult::Cancel, 1);
  164. CancelButton = CancelBtn;
  165. }
  166. else if (buttons == MessageBoxButtons::YesNo)
  167. {
  168. numbuttons = 2;
  169. AddBtn("&Yes", System::Windows::Forms::DialogResult::Yes, 0);
  170. Button^ NoBtn = AddBtn("&No", System::Windows::Forms::DialogResult::No, 1);
  171. CancelButton = NoBtn;
  172. //(official .NET doesn't set cancel button, but this seems pretty clear to me...)
  173. }
  174. else if (buttons == MessageBoxButtons::RetryCancel)
  175. {
  176. numbuttons = 2;
  177. AddBtn("&Retry", System::Windows::Forms::DialogResult::Retry, 0);
  178. Button^ CancelBtn = AddBtn("&Cancel", System::Windows::Forms::DialogResult::Cancel, 1);
  179. CancelButton = CancelBtn;
  180. }
  181. else if (buttons == MessageBoxButtons::YesNoCancel)
  182. {
  183. numbuttons = 3;
  184. AddBtn("&Yes", System::Windows::Forms::DialogResult::Yes, 0);
  185. AddBtn("&No", System::Windows::Forms::DialogResult::No, 1);
  186. Button^ CancelBtn = AddBtn("&Cancel", System::Windows::Forms::DialogResult::Cancel, 2);
  187. CancelButton = CancelBtn;
  188. }
  189. else if (buttons == MessageBoxButtons::AbortRetryIgnore)
  190. {
  191. numbuttons = 3;
  192. AddBtn("&Abort", System::Windows::Forms::DialogResult::Abort, 0);
  193. AddBtn("&Retry", System::Windows::Forms::DialogResult::Retry, 1);
  194. AddBtn("&Ignore", System::Windows::Forms::DialogResult::Ignore, 2);
  195. //this.CancelButton = IgnoreBtn;
  196. //(official .NET doesn't set cancel button)
  197. }
  198. else
  199. {
  200. throw gcnew System::ComponentModel::InvalidEnumArgumentException("buttons", (int)buttons, MessageBoxButtons::typeid);
  201. }
  202. if (!CancelButton)
  203. ControlBox = false;
  204. if (icon != MessageBoxIcon::None)
  205. {
  206. int iconsize = 32 * ScaleWidth;
  207. int iconpadding = 12 * ScaleWidth;
  208. int textpadding = -8 * ScaleWidth;
  209. PictureBox^ iconPB = gcnew PictureBox();
  210. iconPB->Width = iconsize;
  211. iconPB->Height = iconsize;
  212. iconPB->Top = 10 * ScaleHeight;
  213. iconPB->Left = 10 * ScaleWidth;
  214. iconPB->AccessibleName = "Icon";
  215. iconPB->SizeMode = PictureBoxSizeMode::Zoom;
  216. System::Resources::ResourceManager^ ResMgr = gcnew System::Resources::ResourceManager("Launcher.SkinnedMessageBox", SkinnedMessageBox::typeid->Assembly);
  217. // there are many duplicated values:
  218. // https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.messageboxicon?view=netframework-4.7.2
  219. if (icon == MessageBoxIcon::Error) // also Hand and Stop
  220. {
  221. iconPB->Image = (Image^)(ResMgr->GetObject("FeelsThinkMan"));
  222. iconPB->AccessibleDescription = "Error";
  223. //this.Icon = SystemIcons.Error;
  224. System::Media::SystemSounds::Hand->Play();
  225. }
  226. else if (icon == MessageBoxIcon::Question)
  227. {
  228. iconPB->Image = (Image^)(ResMgr->GetObject("PokeWhat4xvc2"));
  229. iconPB->AccessibleDescription = "Question";
  230. //this.Icon = SystemIcons.Question;
  231. System::Media::SystemSounds::Question->Play();
  232. }
  233. else if (icon == MessageBoxIcon::Warning) // also Exclamation
  234. {
  235. iconPB->Image = (Image^)(ResMgr->GetObject("PokeSnarf4xd"));
  236. iconPB->AccessibleDescription = "Warning";
  237. //this.Icon = SystemIcons.Warning;
  238. //textpadding -= 2 * ScaleWidth; // triangle shape looks worse without this
  239. System::Media::SystemSounds::Exclamation->Play();
  240. }
  241. else if (icon == MessageBoxIcon::Information) // also Asterisk
  242. {
  243. /*Drawing::Icon^ infoicon = gcnew Drawing::Icon(SystemIcons::Information, iconsize, iconsize);
  244. iconPB->Image = infoicon->ToBitmap();
  245. iconPB->AccessibleDescription = "Information";
  246. //this.Icon = SystemIcons.Question;
  247. System::Media::SystemSounds::Asterisk->Play();*/
  248. iconPB->Image = (Image^)(ResMgr->GetObject("Help"));
  249. iconPB->AccessibleDescription = "Question";
  250. //this.Icon = SystemIcons.Question;
  251. System::Media::SystemSounds::Question->Play();
  252. }
  253. else
  254. {
  255. throw gcnew System::ComponentModel::InvalidEnumArgumentException("icon", (int)icon, MessageBoxIcon::typeid);
  256. }
  257. MessageLbl->MinimumSize = Drawing::Size(MessageLbl->MinimumSize.Width - (iconsize + iconpadding + textpadding), MessageLbl->MinimumSize.Height);
  258. MessageLbl->Left += (iconsize + iconpadding + textpadding);
  259. Controls->Add(iconPB);
  260. }
  261. if (defaultbutton == MessageBoxDefaultButton::Button1)
  262. {
  263. AcceptButton = buttonrefs[0];
  264. ActiveControl = buttonrefs[0];
  265. }
  266. else if (defaultbutton == MessageBoxDefaultButton::Button2)
  267. {
  268. if (!Controls->Contains(buttonrefs[1])) throw gcnew ArgumentOutOfRangeException("defaultbutton", "defaultbutton cannot be set to a button number that does not exist");
  269. AcceptButton = buttonrefs[1];
  270. ActiveControl = buttonrefs[1];
  271. }
  272. else if (defaultbutton == MessageBoxDefaultButton::Button3)
  273. {
  274. if (!Controls->Contains(buttonrefs[2])) throw gcnew ArgumentOutOfRangeException("defaultbutton", "defaultbutton cannot be set to a button number that does not exist");
  275. AcceptButton = buttonrefs[2];
  276. ActiveControl = buttonrefs[2];
  277. }
  278. else
  279. {
  280. throw gcnew System::ComponentModel::InvalidEnumArgumentException("defaultbutton", (int)defaultbutton, MessageBoxDefaultButton::typeid);
  281. }
  282. // TODO: TEXT ALIGNMENT AND RTL MODE
  283. /*
  284. // MessageBoxOptions are more like flags than concrete settings.
  285. // For ease of use, we assume default value is whatever winforms gives us in current locale -- but we override if any value is set
  286. if ((options & MessageBoxOptions.RightAlign) == MessageBoxOptions.RightAlign) MessageLbl.TextAlign = ContentAlignment.TopRight;
  287. else if (options != 0) MessageLbl.TextAlign = ContentAlignment.TopLeft;
  288. // RtlReading should be what actually affects layout, and RightAlign only affects the text label
  289. if ((options & MessageBoxOptions.RtlReading) == MessageBoxOptions.RtlReading)
  290. {
  291. foreach (Control control in this.Controls)
  292. {
  293. control.Left = this.ClientSize.Width - control.Right;
  294. control.RightToLeft = RightToLeft.Yes;
  295. }
  296. this.RightToLeft = RightToLeft.Yes;
  297. this.RightToLeftLayout = true;
  298. }
  299. else if (options != 0)
  300. {
  301. foreach (Control control in this.Controls)
  302. {
  303. control.RightToLeft = RightToLeft.No;
  304. }
  305. this.RightToLeft = RightToLeft.No;
  306. this.RightToLeftLayout = false;
  307. }
  308. */
  309. SetBackCol(this, Color::FromArgb(64, 64, 64));
  310. SetForeCol(this, Color::White);
  311. SetUIStyle(this, FlatStyle::Flat);
  312. }
  313. SkinnedMessageBoxForm(String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultbutton, MessageBoxOptions options)
  314. {
  315. SkinnedMessageBoxForm(NativeWindow::FromHandle((IntPtr::Zero)), message, caption, buttons, icon, defaultbutton, options);
  316. }
  317. };
  318. public:
  319. static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultbutton, MessageBoxOptions options)
  320. {
  321. // I probably shouldn't do this, but from what I can tell this should be safe enough -- out of thread owner is allowed
  322. // and it only seems to appear when debugging (???)
  323. Control::CheckForIllegalCrossThreadCalls = false;
  324. SkinnedMessageBoxForm^ mb = gcnew SkinnedMessageBoxForm(owner, message, caption, buttons, icon, defaultbutton, options);
  325. DialogResult res = mb->ShowDialog();
  326. Control::CheckForIllegalCrossThreadCalls = true;
  327. return res;
  328. }
  329. static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultbutton)
  330. {
  331. return Show(owner, message, caption, buttons, icon, defaultbutton, (MessageBoxOptions)0);
  332. }
  333. static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons, MessageBoxIcon icon)
  334. {
  335. return Show(owner, message, caption, buttons, icon, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0);
  336. }
  337. static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption, MessageBoxButtons buttons)
  338. {
  339. return Show(owner, message, caption, buttons, MessageBoxIcon::None, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0);
  340. }
  341. static DialogResult Show(IWin32Window^ owner, String^ message, String^ caption)
  342. {
  343. return Show(owner, message, caption, MessageBoxButtons::OK, MessageBoxIcon::None, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0);
  344. }
  345. static DialogResult Show(IWin32Window^ owner, String^ message)
  346. {
  347. return Show(owner, message, "", MessageBoxButtons::OK, MessageBoxIcon::None, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0);
  348. }
  349. };