using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; namespace PaintDotNet.SystemLayer { /// /// 包含与用户界面相关的静态方法 /// public static class UI { private static bool initScales = false; private static float xScale; private static float yScale; public static void FlashForm(Form form) { IntPtr hWnd = form.Handle; SafeNativeMethods.FlashWindow(hWnd, false); SafeNativeMethods.FlashWindow(hWnd, false); GC.KeepAlive(form); } /// /// In some circumstances, the window manager will draw the window larger than it reports /// its size to be. You can use this function to retrieve the size of this extra border /// padding. /// /// /// /// An integer greater than or equal to zero that describes the size of the border padding /// which is not reported via the window's Size or Bounds property. /// /// /// Note to implementors: This method may simply return 0. It is provided for use in Windows /// Vista when DWM+Aero is enabled in which case sizable FloatingToolForm windows do not /// visibly dock to the correct locations. /// public static int GetExtendedFrameBounds(Form window) { int returnVal; if (OS.IsVistaOrLater) { unsafe { int* rcVal = stackalloc int[4]; int hr = SafeNativeMethods.DwmGetWindowAttribute( window.Handle, NativeConstants.DWMWA_EXTENDED_FRAME_BOUNDS, (void*)rcVal, 4 * (uint)sizeof(int)); if (hr >= 0) { returnVal = -rcVal[0]; } else { returnVal = 0; } } } else { returnVal = 0; } GC.KeepAlive(window); return Math.Max(0, returnVal); } private static void InitScaleFactors(Control c) { if (c == null) { xScale = 1.0f; yScale = 1.0f; } else { using (Graphics g = c.CreateGraphics()) { xScale = g.DpiX / 96.0f; yScale = g.DpiY / 96.0f; } } initScales = true; } public static void InitScaling(Control c) { if (!initScales) { InitScaleFactors(c); } } public static float ScaleWidth(float width) { return (float)Math.Round(width * GetXScaleFactor()); } public static int ScaleWidth(int width) { return (int)Math.Round((float)width * GetXScaleFactor()); } public static int ScaleHeight(int height) { return (int)Math.Round((float)height * GetYScaleFactor()); } public static float ScaleHeight(float height) { return (float)Math.Round(height * GetYScaleFactor()); } public static Size ScaleSize(Size size) { return new Size(ScaleWidth(size.Width), ScaleHeight(size.Height)); } public static Point ScalePoint(Point pt) { return new Point(ScaleWidth(pt.X), ScaleHeight(pt.Y)); } public static float GetXScaleFactor() { if (!initScales) { return 1; // throw new InvalidOperationException("Must call InitScaling() first"); } return xScale; } public static float GetYScaleFactor() { if (!initScales) { return 1; //throw new InvalidOperationException("Must call InitScaling() first"); } return yScale; } public static void DrawCommandButton( Graphics g, PushButtonState state, Rectangle rect, Color backColor, Control childControl) { VisualStyleElement element = null; int alpha = 255; if (OS.IsVistaOrLater) { const string className = "BUTTON"; const int partID = NativeConstants.BP_COMMANDLINK; int stateID; switch (state) { case PushButtonState.Default: stateID = NativeConstants.CMDLS_DEFAULTED; break; case PushButtonState.Disabled: stateID = NativeConstants.CMDLS_DISABLED; break; case PushButtonState.Hot: stateID = NativeConstants.CMDLS_HOT; break; case PushButtonState.Normal: stateID = NativeConstants.CMDLS_NORMAL; break; case PushButtonState.Pressed: stateID = NativeConstants.CMDLS_PRESSED; break; default: throw new InvalidEnumArgumentException(); } try { element = VisualStyleElement.CreateElement(className, partID, stateID); if (!VisualStyleRenderer.IsElementDefined(element)) { element = null; } } catch (InvalidOperationException) { element = null; } } if (element == null) { switch (state) { case PushButtonState.Default: element = VisualStyleElement.Button.PushButton.Default; alpha = 95; break; case PushButtonState.Disabled: element = VisualStyleElement.Button.PushButton.Disabled; break; case PushButtonState.Hot: element = VisualStyleElement.Button.PushButton.Hot; break; case PushButtonState.Normal: alpha = 0; element = VisualStyleElement.Button.PushButton.Normal; break; case PushButtonState.Pressed: element = VisualStyleElement.Button.PushButton.Pressed; break; default: throw new InvalidEnumArgumentException(); } } if (element != null) { try { VisualStyleRenderer renderer = new VisualStyleRenderer(element); renderer.DrawParentBackground(g, rect, childControl); renderer.DrawBackground(g, rect); } catch (Exception) { element = null; } } if (element == null) { ButtonRenderer.DrawButton(g, rect, state); } if (alpha != 255) { using (Brush backBrush = new SolidBrush(Color.FromArgb(255 - alpha, backColor))) { CompositingMode oldCM = g.CompositingMode; try { g.CompositingMode = CompositingMode.SourceOver; g.FillRectangle(backBrush, rect); } finally { g.CompositingMode = oldCM; } } } } /// /// Sets the control's redraw state. /// /// The control whose state should be modified. /// The new state for redrawing ability. /// /// Note to implementors: This method is used by SuspendControlPainting() and ResumeControlPainting(). /// This may be implemented as a no-op. /// private static void SetControlRedrawImpl(Control control, bool enabled) { SafeNativeMethods.SendMessageW(control.Handle, NativeConstants.WM_SETREDRAW, enabled ? new IntPtr(1) : IntPtr.Zero, IntPtr.Zero); GC.KeepAlive(control); } private static Dictionary controlRedrawStack = new Dictionary(); /// /// Suspends the control's ability to draw itself. /// /// The control to suspend drawing for. /// /// When drawing is suspended, any painting performed in the control's WM_PAINT, OnPaint(), /// WM_ERASEBKND, or OnPaintBackground() handlers is completely ignored. Invalidation rectangles /// are not accumulated during this period, so when drawing is resumed (with /// ResumeControlPainting()), it is usually a good idea to call Invalidate(true) on the control. /// This method must be matched at a later time by a corresponding call to ResumeControlPainting(). /// If you call SuspendControlPainting() multiple times for the same control, then you must /// call ResumeControlPainting() once for each call. /// Note to implementors: Do not modify this method. Instead, modify SetControlRedrawImpl(), /// which may be implemented as a no-op. /// public static void SuspendControlPainting(Control control) { int pushCount; if (controlRedrawStack.TryGetValue(control, out pushCount)) { ++pushCount; } else { pushCount = 1; } if (pushCount == 1) { SetControlRedrawImpl(control, false); } controlRedrawStack[control] = pushCount; } /// /// Resumes the control's ability to draw itself. /// /// The control to suspend drawing for. /// /// This method must be matched by a preceding call to SuspendControlPainting(). If that method /// was called multiple times, then this method must be called a corresponding number of times /// in order to enable drawing. /// This method must be matched at a later time by a corresponding call to ResumeControlPainting(). /// If you call SuspendControlPainting() multiple times for the same control, then you must /// call ResumeControlPainting() once for each call. /// Note to implementors: Do not modify this method. Instead, modify SetControlRedrawImpl(), /// which may be implemented as a no-op. /// public static void ResumeControlPainting(Control control) { int pushCount; if (controlRedrawStack.TryGetValue(control, out pushCount)) { --pushCount; } else { throw new InvalidOperationException("There was no previous matching SuspendControlPainting() for this control"); } if (pushCount == 0) { SetControlRedrawImpl(control, true); controlRedrawStack.Remove(control); } else { controlRedrawStack[control] = pushCount; } } /// /// Queries whether painting is enabled for the given control. /// /// The control to query suspension for. /// /// false if the control's painting has been suspended via a call to SuspendControlPainting(), /// otherwise true. /// /// /// You may use the return value of this method to optimize away painting. If this /// method returns false, then you may skip your entire OnPaint() method. This saves /// processor time by avoiding all of the non-painting drawing and resource initialization /// and destruction that is typically contained in OnPaint(). /// This method assumes painting suspension is being exclusively managed with Suspend- /// and ResumeControlPainting(). /// public static bool IsControlPaintingEnabled(Control control) { int pushCount; if (!controlRedrawStack.TryGetValue(control, out pushCount)) { pushCount = 0; } return (pushCount == 0); } private static IntPtr hRgn = SafeNativeMethods.CreateRectRgn(0, 0, 1, 1); /// /// This method retrieves the update region of a control. /// /// The control to retrieve the update region for. /// /// An array of rectangles specifying the area that has been invalidated, or /// null if this could not be determined. /// /// /// This method is not thread safe. /// Note to implementors: This method may be implemented as a no-op. In this case, just return null. /// public static Rectangle[] GetUpdateRegion(Control control) { SafeNativeMethods.GetUpdateRgn(control.Handle, hRgn, false); Rectangle[] scans; int area; PdnGraphics.GetRegionScans(hRgn, out scans, out area); GC.KeepAlive(control); return scans; } /// /// Sets a form's opacity. /// /// /// /// /// Note to implementors: This may be implemented as just "form.Opacity = opacity". /// This method works around some visual clumsiness in .NET 2.0 related to /// transitioning between opacity == 1.0 and opacity != 1.0. public static void SetFormOpacity(Form form, double opacity) { if (opacity < 0.0 || opacity > 1.0) { throw new ArgumentOutOfRangeException("opacity", "must be in the range [0, 1]"); } uint exStyle = SafeNativeMethods.GetWindowLongW(form.Handle, NativeConstants.GWL_EXSTYLE); byte bOldAlpha = 255; if ((exStyle & NativeConstants.GWL_EXSTYLE) != 0) { uint dwOldKey; uint dwOldFlags; bool result = SafeNativeMethods.GetLayeredWindowAttributes(form.Handle, out dwOldKey, out bOldAlpha, out dwOldFlags); } byte bNewAlpha = (byte)(opacity * 255.0); uint newExStyle = exStyle; if (bNewAlpha != 255) { newExStyle |= NativeConstants.WS_EX_LAYERED; } if (newExStyle != exStyle || (newExStyle & NativeConstants.WS_EX_LAYERED) != 0) { if (newExStyle != exStyle) { SafeNativeMethods.SetWindowLongW(form.Handle, NativeConstants.GWL_EXSTYLE, newExStyle); } if ((newExStyle & NativeConstants.WS_EX_LAYERED) != 0) { SafeNativeMethods.SetLayeredWindowAttributes(form.Handle, 0, bNewAlpha, NativeConstants.LWA_ALPHA); } } GC.KeepAlive(form); } /// /// This WndProc implements click-through functionality. Some controls (MenuStrip, ToolStrip) will not /// recognize a click unless the form they are hosted in is active. So the first click will activate the /// form and then a second is required to actually make the click happen. /// /// The Message that was passed to your WndProc. /// true if the message was processed, false if it was not /// /// You should first call base.WndProc(), and then call this method. This method is only intended to /// change a return value, not to change actual processing before that. /// internal static bool ClickThroughWndProc(ref Message m) { bool returnVal = false; if (m.Msg == NativeConstants.WM_MOUSEACTIVATE) { if (m.Result == (IntPtr)NativeConstants.MA_ACTIVATEANDEAT) { m.Result = (IntPtr)NativeConstants.MA_ACTIVATE; returnVal = true; } } return returnVal; } public static bool IsOurAppActive { get { foreach (Form form in Application.OpenForms) { if (form == Form.ActiveForm) { return true; } } return false; } } private static VisualStyleClass DetermineVisualStyleClass() { return Do.TryCatch(DetermineVisualStyleClassImpl, ex => VisualStyleClass.Other); } private static VisualStyleClass DetermineVisualStyleClassImpl() { VisualStyleClass vsClass; if (!VisualStyleInformation.IsSupportedByOS) { vsClass = VisualStyleClass.Classic; } else if (!VisualStyleInformation.IsEnabledByUser) { vsClass = VisualStyleClass.Classic; } else if (0 == string.Compare(VisualStyleInformation.Author, "MSX", StringComparison.InvariantCulture) && 0 == string.Compare(VisualStyleInformation.DisplayName, "Aero style", StringComparison.InvariantCulture)) { vsClass = VisualStyleClass.Aero; } else if (0 == string.Compare(VisualStyleInformation.Company, "Microsoft Corporation", StringComparison.InvariantCulture) && 0 == string.Compare(VisualStyleInformation.Author, "Microsoft Design Team", StringComparison.InvariantCulture)) { if (0 == string.Compare(VisualStyleInformation.DisplayName, "Windows XP style", StringComparison.InvariantCulture) || // Luna 0 == string.Compare(VisualStyleInformation.DisplayName, "Zune Style", StringComparison.InvariantCulture) || // Zune 0 == string.Compare(VisualStyleInformation.DisplayName, "Media Center style", StringComparison.InvariantCulture)) // Royale { vsClass = VisualStyleClass.Luna; } else { vsClass = VisualStyleClass.Other; } } else { vsClass = VisualStyleClass.Other; } return vsClass; } public static VisualStyleClass VisualStyleClass { get { return DetermineVisualStyleClass(); } } public static void EnableShield(Button button, bool enableShield) { IntPtr hWnd = button.Handle; SafeNativeMethods.SendMessageW( hWnd, NativeConstants.BCM_SETSHIELD, IntPtr.Zero, enableShield ? new IntPtr(1) : IntPtr.Zero); GC.KeepAlive(button); } public static bool HideAllScrollBar(Control control) { return SafeNativeMethods.ShowScrollBar(control.Handle, 3, false); } public static bool HideHorizontalScrollBar(Control control) { return SafeNativeMethods.ShowScrollBar(control.Handle, NativeConstants.SB_HORZ, false); } public static bool HideVerticalScrollBar(Control control) { return SafeNativeMethods.ShowScrollBar(control.Handle, NativeConstants.SB_VERT, false); } public static void RestoreWindow(IWin32Window window) { IntPtr hWnd = window.Handle; SafeNativeMethods.ShowWindow(hWnd, NativeConstants.SW_RESTORE); GC.KeepAlive(window); } public static void ShowComboBox(ComboBox comboBox, bool show) { IntPtr hWnd = comboBox.Handle; SafeNativeMethods.SendMessageW( hWnd, NativeConstants.CB_SHOWDROPDOWN, show ? new IntPtr(1) : IntPtr.Zero, IntPtr.Zero); GC.KeepAlive(comboBox); } /// /// Disables the system menu "Close" menu command, as well as the "X" close button on the window title bar. /// /// /// Note to implementors: This method may *not* be implemented as a no-op. The purpose is to make it so that /// calling the Close() method is the only way to close a dialog, which is something that can only be done /// programmatically. /// public static void DisableCloseBox(IWin32Window window) { IntPtr hWnd = window.Handle; IntPtr hMenu = SafeNativeMethods.GetSystemMenu(hWnd, false); if (hMenu == IntPtr.Zero) { NativeMethods.ThrowOnWin32Error("GetSystemMenu() returned NULL"); } int result = SafeNativeMethods.EnableMenuItem( hMenu, NativeConstants.SC_CLOSE, NativeConstants.MF_BYCOMMAND | NativeConstants.MF_GRAYED); bool bResult = SafeNativeMethods.DrawMenuBar(hWnd); if (!bResult) { NativeMethods.ThrowOnWin32Error("DrawMenuBar returned FALSE"); } GC.KeepAlive(window); } internal static void InvokeThroughModalTrampoline(IWin32Window owner, Procedure invokeMe) { using (Form modalityFix = new Form()) { modalityFix.ShowInTaskbar = false; modalityFix.TransparencyKey = modalityFix.BackColor; UI.SetFormOpacity(modalityFix, 0); modalityFix.ControlBox = false; modalityFix.FormBorderStyle = FormBorderStyle.None; Control ownerAsControl = owner as Control; if (ownerAsControl != null) { Form ownerForm = ownerAsControl.FindForm(); if (ownerForm != null) { Rectangle clientRect = ownerForm.RectangleToScreen(ownerForm.ClientRectangle); modalityFix.Icon = ownerForm.Icon; modalityFix.Location = clientRect.Location; modalityFix.Size = clientRect.Size; modalityFix.StartPosition = FormStartPosition.Manual; } } modalityFix.Shown += delegate (object sender, EventArgs e) { invokeMe(modalityFix); modalityFix.Close(); }; modalityFix.ShowDialog(owner); GC.KeepAlive(modalityFix); } } } }