UI.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Drawing.Drawing2D;
  6. using System.Windows.Forms;
  7. using System.Windows.Forms.VisualStyles;
  8. namespace PaintDotNet.SystemLayer
  9. {
  10. /// <summary>
  11. /// 包含与用户界面相关的静态方法
  12. /// </summary>
  13. public static class UI
  14. {
  15. private static bool initScales = false;
  16. private static float xScale;
  17. private static float yScale;
  18. public static void FlashForm(Form form)
  19. {
  20. IntPtr hWnd = form.Handle;
  21. SafeNativeMethods.FlashWindow(hWnd, false);
  22. SafeNativeMethods.FlashWindow(hWnd, false);
  23. GC.KeepAlive(form);
  24. }
  25. /// <summary>
  26. /// In some circumstances, the window manager will draw the window larger than it reports
  27. /// its size to be. You can use this function to retrieve the size of this extra border
  28. /// padding.
  29. /// </summary>
  30. /// <param name="window"></param>
  31. /// <returns>
  32. /// An integer greater than or equal to zero that describes the size of the border padding
  33. /// which is not reported via the window's Size or Bounds property.
  34. /// </returns>
  35. /// <remarks>
  36. /// Note to implementors: This method may simply return 0. It is provided for use in Windows
  37. /// Vista when DWM+Aero is enabled in which case sizable FloatingToolForm windows do not
  38. /// visibly dock to the correct locations.
  39. /// </remarks>
  40. public static int GetExtendedFrameBounds(Form window)
  41. {
  42. int returnVal;
  43. if (OS.IsVistaOrLater)
  44. {
  45. unsafe
  46. {
  47. int* rcVal = stackalloc int[4];
  48. int hr = SafeNativeMethods.DwmGetWindowAttribute(
  49. window.Handle,
  50. NativeConstants.DWMWA_EXTENDED_FRAME_BOUNDS,
  51. (void*)rcVal,
  52. 4 * (uint)sizeof(int));
  53. if (hr >= 0)
  54. {
  55. returnVal = -rcVal[0];
  56. }
  57. else
  58. {
  59. returnVal = 0;
  60. }
  61. }
  62. }
  63. else
  64. {
  65. returnVal = 0;
  66. }
  67. GC.KeepAlive(window);
  68. return Math.Max(0, returnVal);
  69. }
  70. private static void InitScaleFactors(Control c)
  71. {
  72. if (c == null)
  73. {
  74. xScale = 1.0f;
  75. yScale = 1.0f;
  76. }
  77. else
  78. {
  79. using (Graphics g = c.CreateGraphics())
  80. {
  81. xScale = g.DpiX / 96.0f;
  82. yScale = g.DpiY / 96.0f;
  83. }
  84. }
  85. initScales = true;
  86. }
  87. public static void InitScaling(Control c)
  88. {
  89. if (!initScales)
  90. {
  91. InitScaleFactors(c);
  92. }
  93. }
  94. public static float ScaleWidth(float width)
  95. {
  96. return (float)Math.Round(width * GetXScaleFactor());
  97. }
  98. public static int ScaleWidth(int width)
  99. {
  100. return (int)Math.Round((float)width * GetXScaleFactor());
  101. }
  102. public static int ScaleHeight(int height)
  103. {
  104. return (int)Math.Round((float)height * GetYScaleFactor());
  105. }
  106. public static float ScaleHeight(float height)
  107. {
  108. return (float)Math.Round(height * GetYScaleFactor());
  109. }
  110. public static Size ScaleSize(Size size)
  111. {
  112. return new Size(ScaleWidth(size.Width), ScaleHeight(size.Height));
  113. }
  114. public static Point ScalePoint(Point pt)
  115. {
  116. return new Point(ScaleWidth(pt.X), ScaleHeight(pt.Y));
  117. }
  118. public static float GetXScaleFactor()
  119. {
  120. if (!initScales)
  121. {
  122. return 1;
  123. // throw new InvalidOperationException("Must call InitScaling() first");
  124. }
  125. return xScale;
  126. }
  127. public static float GetYScaleFactor()
  128. {
  129. if (!initScales)
  130. {
  131. return 1;
  132. //throw new InvalidOperationException("Must call InitScaling() first");
  133. }
  134. return yScale;
  135. }
  136. public static void DrawCommandButton(
  137. Graphics g,
  138. PushButtonState state,
  139. Rectangle rect,
  140. Color backColor,
  141. Control childControl)
  142. {
  143. VisualStyleElement element = null;
  144. int alpha = 255;
  145. if (OS.IsVistaOrLater)
  146. {
  147. const string className = "BUTTON";
  148. const int partID = NativeConstants.BP_COMMANDLINK;
  149. int stateID;
  150. switch (state)
  151. {
  152. case PushButtonState.Default:
  153. stateID = NativeConstants.CMDLS_DEFAULTED;
  154. break;
  155. case PushButtonState.Disabled:
  156. stateID = NativeConstants.CMDLS_DISABLED;
  157. break;
  158. case PushButtonState.Hot:
  159. stateID = NativeConstants.CMDLS_HOT;
  160. break;
  161. case PushButtonState.Normal:
  162. stateID = NativeConstants.CMDLS_NORMAL;
  163. break;
  164. case PushButtonState.Pressed:
  165. stateID = NativeConstants.CMDLS_PRESSED;
  166. break;
  167. default:
  168. throw new InvalidEnumArgumentException();
  169. }
  170. try
  171. {
  172. element = VisualStyleElement.CreateElement(className, partID, stateID);
  173. if (!VisualStyleRenderer.IsElementDefined(element))
  174. {
  175. element = null;
  176. }
  177. }
  178. catch (InvalidOperationException)
  179. {
  180. element = null;
  181. }
  182. }
  183. if (element == null)
  184. {
  185. switch (state)
  186. {
  187. case PushButtonState.Default:
  188. element = VisualStyleElement.Button.PushButton.Default;
  189. alpha = 95;
  190. break;
  191. case PushButtonState.Disabled:
  192. element = VisualStyleElement.Button.PushButton.Disabled;
  193. break;
  194. case PushButtonState.Hot:
  195. element = VisualStyleElement.Button.PushButton.Hot;
  196. break;
  197. case PushButtonState.Normal:
  198. alpha = 0;
  199. element = VisualStyleElement.Button.PushButton.Normal;
  200. break;
  201. case PushButtonState.Pressed:
  202. element = VisualStyleElement.Button.PushButton.Pressed;
  203. break;
  204. default:
  205. throw new InvalidEnumArgumentException();
  206. }
  207. }
  208. if (element != null)
  209. {
  210. try
  211. {
  212. VisualStyleRenderer renderer = new VisualStyleRenderer(element);
  213. renderer.DrawParentBackground(g, rect, childControl);
  214. renderer.DrawBackground(g, rect);
  215. }
  216. catch (Exception)
  217. {
  218. element = null;
  219. }
  220. }
  221. if (element == null)
  222. {
  223. ButtonRenderer.DrawButton(g, rect, state);
  224. }
  225. if (alpha != 255)
  226. {
  227. using (Brush backBrush = new SolidBrush(Color.FromArgb(255 - alpha, backColor)))
  228. {
  229. CompositingMode oldCM = g.CompositingMode;
  230. try
  231. {
  232. g.CompositingMode = CompositingMode.SourceOver;
  233. g.FillRectangle(backBrush, rect);
  234. }
  235. finally
  236. {
  237. g.CompositingMode = oldCM;
  238. }
  239. }
  240. }
  241. }
  242. /// <summary>
  243. /// Sets the control's redraw state.
  244. /// </summary>
  245. /// <param name="control">The control whose state should be modified.</param>
  246. /// <param name="enabled">The new state for redrawing ability.</param>
  247. /// <remarks>
  248. /// Note to implementors: This method is used by SuspendControlPainting() and ResumeControlPainting().
  249. /// This may be implemented as a no-op.
  250. /// </remarks>
  251. private static void SetControlRedrawImpl(Control control, bool enabled)
  252. {
  253. SafeNativeMethods.SendMessageW(control.Handle, NativeConstants.WM_SETREDRAW, enabled ? new IntPtr(1) : IntPtr.Zero, IntPtr.Zero);
  254. GC.KeepAlive(control);
  255. }
  256. private static Dictionary<Control, int> controlRedrawStack = new Dictionary<Control, int>();
  257. /// <summary>
  258. /// Suspends the control's ability to draw itself.
  259. /// </summary>
  260. /// <param name="control">The control to suspend drawing for.</param>
  261. /// <remarks>
  262. /// When drawing is suspended, any painting performed in the control's WM_PAINT, OnPaint(),
  263. /// WM_ERASEBKND, or OnPaintBackground() handlers is completely ignored. Invalidation rectangles
  264. /// are not accumulated during this period, so when drawing is resumed (with
  265. /// ResumeControlPainting()), it is usually a good idea to call Invalidate(true) on the control.
  266. /// This method must be matched at a later time by a corresponding call to ResumeControlPainting().
  267. /// If you call SuspendControlPainting() multiple times for the same control, then you must
  268. /// call ResumeControlPainting() once for each call.
  269. /// Note to implementors: Do not modify this method. Instead, modify SetControlRedrawImpl(),
  270. /// which may be implemented as a no-op.
  271. /// </remarks>
  272. public static void SuspendControlPainting(Control control)
  273. {
  274. int pushCount;
  275. if (controlRedrawStack.TryGetValue(control, out pushCount))
  276. {
  277. ++pushCount;
  278. }
  279. else
  280. {
  281. pushCount = 1;
  282. }
  283. if (pushCount == 1)
  284. {
  285. SetControlRedrawImpl(control, false);
  286. }
  287. controlRedrawStack[control] = pushCount;
  288. }
  289. /// <summary>
  290. /// Resumes the control's ability to draw itself.
  291. /// </summary>
  292. /// <param name="control">The control to suspend drawing for.</param>
  293. /// <remarks>
  294. /// This method must be matched by a preceding call to SuspendControlPainting(). If that method
  295. /// was called multiple times, then this method must be called a corresponding number of times
  296. /// in order to enable drawing.
  297. /// This method must be matched at a later time by a corresponding call to ResumeControlPainting().
  298. /// If you call SuspendControlPainting() multiple times for the same control, then you must
  299. /// call ResumeControlPainting() once for each call.
  300. /// Note to implementors: Do not modify this method. Instead, modify SetControlRedrawImpl(),
  301. /// which may be implemented as a no-op.
  302. /// </remarks>
  303. public static void ResumeControlPainting(Control control)
  304. {
  305. int pushCount;
  306. if (controlRedrawStack.TryGetValue(control, out pushCount))
  307. {
  308. --pushCount;
  309. }
  310. else
  311. {
  312. throw new InvalidOperationException("There was no previous matching SuspendControlPainting() for this control");
  313. }
  314. if (pushCount == 0)
  315. {
  316. SetControlRedrawImpl(control, true);
  317. controlRedrawStack.Remove(control);
  318. }
  319. else
  320. {
  321. controlRedrawStack[control] = pushCount;
  322. }
  323. }
  324. /// <summary>
  325. /// Queries whether painting is enabled for the given control.
  326. /// </summary>
  327. /// <param name="control">The control to query suspension for.</param>
  328. /// <returns>
  329. /// false if the control's painting has been suspended via a call to SuspendControlPainting(),
  330. /// otherwise true.
  331. /// </returns>
  332. /// <remarks>
  333. /// You may use the return value of this method to optimize away painting. If this
  334. /// method returns false, then you may skip your entire OnPaint() method. This saves
  335. /// processor time by avoiding all of the non-painting drawing and resource initialization
  336. /// and destruction that is typically contained in OnPaint().
  337. /// This method assumes painting suspension is being exclusively managed with Suspend-
  338. /// and ResumeControlPainting().
  339. /// </remarks>
  340. public static bool IsControlPaintingEnabled(Control control)
  341. {
  342. int pushCount;
  343. if (!controlRedrawStack.TryGetValue(control, out pushCount))
  344. {
  345. pushCount = 0;
  346. }
  347. return (pushCount == 0);
  348. }
  349. private static IntPtr hRgn = SafeNativeMethods.CreateRectRgn(0, 0, 1, 1);
  350. /// <summary>
  351. /// This method retrieves the update region of a control.
  352. /// </summary>
  353. /// <param name="control">The control to retrieve the update region for.</param>
  354. /// <returns>
  355. /// An array of rectangles specifying the area that has been invalidated, or
  356. /// null if this could not be determined.
  357. /// </returns>
  358. /// <remarks>
  359. /// This method is not thread safe.
  360. /// Note to implementors: This method may be implemented as a no-op. In this case, just return null.
  361. /// </remarks>
  362. public static Rectangle[] GetUpdateRegion(Control control)
  363. {
  364. SafeNativeMethods.GetUpdateRgn(control.Handle, hRgn, false);
  365. Rectangle[] scans;
  366. int area;
  367. PdnGraphics.GetRegionScans(hRgn, out scans, out area);
  368. GC.KeepAlive(control);
  369. return scans;
  370. }
  371. /// <summary>
  372. /// Sets a form's opacity.
  373. /// </summary>
  374. /// <param name="form"></param>
  375. /// <param name="opacity"></param>
  376. /// <remarks>
  377. /// Note to implementors: This may be implemented as just "form.Opacity = opacity".
  378. /// This method works around some visual clumsiness in .NET 2.0 related to
  379. /// transitioning between opacity == 1.0 and opacity != 1.0.</remarks>
  380. public static void SetFormOpacity(Form form, double opacity)
  381. {
  382. if (opacity < 0.0 || opacity > 1.0)
  383. {
  384. throw new ArgumentOutOfRangeException("opacity", "must be in the range [0, 1]");
  385. }
  386. uint exStyle = SafeNativeMethods.GetWindowLongW(form.Handle, NativeConstants.GWL_EXSTYLE);
  387. byte bOldAlpha = 255;
  388. if ((exStyle & NativeConstants.GWL_EXSTYLE) != 0)
  389. {
  390. uint dwOldKey;
  391. uint dwOldFlags;
  392. bool result = SafeNativeMethods.GetLayeredWindowAttributes(form.Handle, out dwOldKey, out bOldAlpha, out dwOldFlags);
  393. }
  394. byte bNewAlpha = (byte)(opacity * 255.0);
  395. uint newExStyle = exStyle;
  396. if (bNewAlpha != 255)
  397. {
  398. newExStyle |= NativeConstants.WS_EX_LAYERED;
  399. }
  400. if (newExStyle != exStyle || (newExStyle & NativeConstants.WS_EX_LAYERED) != 0)
  401. {
  402. if (newExStyle != exStyle)
  403. {
  404. SafeNativeMethods.SetWindowLongW(form.Handle, NativeConstants.GWL_EXSTYLE, newExStyle);
  405. }
  406. if ((newExStyle & NativeConstants.WS_EX_LAYERED) != 0)
  407. {
  408. SafeNativeMethods.SetLayeredWindowAttributes(form.Handle, 0, bNewAlpha, NativeConstants.LWA_ALPHA);
  409. }
  410. }
  411. GC.KeepAlive(form);
  412. }
  413. /// <summary>
  414. /// This WndProc implements click-through functionality. Some controls (MenuStrip, ToolStrip) will not
  415. /// recognize a click unless the form they are hosted in is active. So the first click will activate the
  416. /// form and then a second is required to actually make the click happen.
  417. /// </summary>
  418. /// <param name="m">The Message that was passed to your WndProc.</param>
  419. /// <returns>true if the message was processed, false if it was not</returns>
  420. /// <remarks>
  421. /// You should first call base.WndProc(), and then call this method. This method is only intended to
  422. /// change a return value, not to change actual processing before that.
  423. /// </remarks>
  424. internal static bool ClickThroughWndProc(ref Message m)
  425. {
  426. bool returnVal = false;
  427. if (m.Msg == NativeConstants.WM_MOUSEACTIVATE)
  428. {
  429. if (m.Result == (IntPtr)NativeConstants.MA_ACTIVATEANDEAT)
  430. {
  431. m.Result = (IntPtr)NativeConstants.MA_ACTIVATE;
  432. returnVal = true;
  433. }
  434. }
  435. return returnVal;
  436. }
  437. public static bool IsOurAppActive
  438. {
  439. get
  440. {
  441. foreach (Form form in Application.OpenForms)
  442. {
  443. if (form == Form.ActiveForm)
  444. {
  445. return true;
  446. }
  447. }
  448. return false;
  449. }
  450. }
  451. private static VisualStyleClass DetermineVisualStyleClass()
  452. {
  453. return Do.TryCatch(DetermineVisualStyleClassImpl, ex => VisualStyleClass.Other);
  454. }
  455. private static VisualStyleClass DetermineVisualStyleClassImpl()
  456. {
  457. VisualStyleClass vsClass;
  458. if (!VisualStyleInformation.IsSupportedByOS)
  459. {
  460. vsClass = VisualStyleClass.Classic;
  461. }
  462. else if (!VisualStyleInformation.IsEnabledByUser)
  463. {
  464. vsClass = VisualStyleClass.Classic;
  465. }
  466. else if (0 == string.Compare(VisualStyleInformation.Author, "MSX", StringComparison.InvariantCulture) &&
  467. 0 == string.Compare(VisualStyleInformation.DisplayName, "Aero style", StringComparison.InvariantCulture))
  468. {
  469. vsClass = VisualStyleClass.Aero;
  470. }
  471. else if (0 == string.Compare(VisualStyleInformation.Company, "Microsoft Corporation", StringComparison.InvariantCulture) &&
  472. 0 == string.Compare(VisualStyleInformation.Author, "Microsoft Design Team", StringComparison.InvariantCulture))
  473. {
  474. if (0 == string.Compare(VisualStyleInformation.DisplayName, "Windows XP style", StringComparison.InvariantCulture) || // Luna
  475. 0 == string.Compare(VisualStyleInformation.DisplayName, "Zune Style", StringComparison.InvariantCulture) || // Zune
  476. 0 == string.Compare(VisualStyleInformation.DisplayName, "Media Center style", StringComparison.InvariantCulture)) // Royale
  477. {
  478. vsClass = VisualStyleClass.Luna;
  479. }
  480. else
  481. {
  482. vsClass = VisualStyleClass.Other;
  483. }
  484. }
  485. else
  486. {
  487. vsClass = VisualStyleClass.Other;
  488. }
  489. return vsClass;
  490. }
  491. public static VisualStyleClass VisualStyleClass
  492. {
  493. get
  494. {
  495. return DetermineVisualStyleClass();
  496. }
  497. }
  498. public static void EnableShield(Button button, bool enableShield)
  499. {
  500. IntPtr hWnd = button.Handle;
  501. SafeNativeMethods.SendMessageW(
  502. hWnd,
  503. NativeConstants.BCM_SETSHIELD,
  504. IntPtr.Zero,
  505. enableShield ? new IntPtr(1) : IntPtr.Zero);
  506. GC.KeepAlive(button);
  507. }
  508. public static bool HideAllScrollBar(Control control)
  509. {
  510. return SafeNativeMethods.ShowScrollBar(control.Handle, 3, false);
  511. }
  512. public static bool HideHorizontalScrollBar(Control control)
  513. {
  514. return SafeNativeMethods.ShowScrollBar(control.Handle, NativeConstants.SB_HORZ, false);
  515. }
  516. public static bool HideVerticalScrollBar(Control control)
  517. {
  518. return SafeNativeMethods.ShowScrollBar(control.Handle, NativeConstants.SB_VERT, false);
  519. }
  520. public static void RestoreWindow(IWin32Window window)
  521. {
  522. IntPtr hWnd = window.Handle;
  523. SafeNativeMethods.ShowWindow(hWnd, NativeConstants.SW_RESTORE);
  524. GC.KeepAlive(window);
  525. }
  526. public static void ShowComboBox(ComboBox comboBox, bool show)
  527. {
  528. IntPtr hWnd = comboBox.Handle;
  529. SafeNativeMethods.SendMessageW(
  530. hWnd,
  531. NativeConstants.CB_SHOWDROPDOWN,
  532. show ? new IntPtr(1) : IntPtr.Zero,
  533. IntPtr.Zero);
  534. GC.KeepAlive(comboBox);
  535. }
  536. /// <summary>
  537. /// Disables the system menu "Close" menu command, as well as the "X" close button on the window title bar.
  538. /// </summary>
  539. /// <remarks>
  540. /// Note to implementors: This method may *not* be implemented as a no-op. The purpose is to make it so that
  541. /// calling the Close() method is the only way to close a dialog, which is something that can only be done
  542. /// programmatically.
  543. /// </remarks>
  544. public static void DisableCloseBox(IWin32Window window)
  545. {
  546. IntPtr hWnd = window.Handle;
  547. IntPtr hMenu = SafeNativeMethods.GetSystemMenu(hWnd, false);
  548. if (hMenu == IntPtr.Zero)
  549. {
  550. NativeMethods.ThrowOnWin32Error("GetSystemMenu() returned NULL");
  551. }
  552. int result = SafeNativeMethods.EnableMenuItem(
  553. hMenu,
  554. NativeConstants.SC_CLOSE,
  555. NativeConstants.MF_BYCOMMAND | NativeConstants.MF_GRAYED);
  556. bool bResult = SafeNativeMethods.DrawMenuBar(hWnd);
  557. if (!bResult)
  558. {
  559. NativeMethods.ThrowOnWin32Error("DrawMenuBar returned FALSE");
  560. }
  561. GC.KeepAlive(window);
  562. }
  563. internal static void InvokeThroughModalTrampoline(IWin32Window owner, Procedure<IWin32Window> invokeMe)
  564. {
  565. using (Form modalityFix = new Form())
  566. {
  567. modalityFix.ShowInTaskbar = false;
  568. modalityFix.TransparencyKey = modalityFix.BackColor;
  569. UI.SetFormOpacity(modalityFix, 0);
  570. modalityFix.ControlBox = false;
  571. modalityFix.FormBorderStyle = FormBorderStyle.None;
  572. Control ownerAsControl = owner as Control;
  573. if (ownerAsControl != null)
  574. {
  575. Form ownerForm = ownerAsControl.FindForm();
  576. if (ownerForm != null)
  577. {
  578. Rectangle clientRect = ownerForm.RectangleToScreen(ownerForm.ClientRectangle);
  579. modalityFix.Icon = ownerForm.Icon;
  580. modalityFix.Location = clientRect.Location;
  581. modalityFix.Size = clientRect.Size;
  582. modalityFix.StartPosition = FormStartPosition.Manual;
  583. }
  584. }
  585. modalityFix.Shown +=
  586. delegate (object sender, EventArgs e)
  587. {
  588. invokeMe(modalityFix);
  589. modalityFix.Close();
  590. };
  591. modalityFix.ShowDialog(owner);
  592. GC.KeepAlive(modalityFix);
  593. }
  594. }
  595. }
  596. }