using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; using Vanara.PInvoke; using static Vanara.PInvoke.Gdi32; using static Vanara.PInvoke.UxTheme; namespace Vanara.Extensions; /// /// Extension methods for for glass effects and extended method functionality. Also provides GetFont2 /// and GetMargins2 methods that corrects base library's non-functioning methods. /// public static partial class VisualStylesRendererExtension { //private static readonly Dictionary bmpCache = new(); private delegate void DrawWrapperMethod(HDC hdc); /// /// Draws the background image of the current visual style element within the specified bounding rectangle and optionally clipped to /// the specified clipping rectangle. /// /// The instance. /// The used to draw the background image. /// A in which the background image is drawn. /// A that defines a clipping rectangle for the drawing operation. /// If set to true flip the image for right to left layout. public static void DrawBackground(this VisualStyleRenderer rnd, IDeviceContext dc, Rectangle bounds, Rectangle? clipRectangle = null, bool rightToLeft = false) => /* { var h = rnd.GetHashCode(); Bitmap bmp; if (!bmpCache.TryGetValue(h, out bmp)) bmpCache.Add(h, bmp = GraphicsExtension.GetTransparentBitmap(GetBackgroundBitmap(rnd, Color.White), GetBackgroundBitmap(rnd, Color.Black))); if (rightToLeft) bmp.RotateFlip(RotateFlipType.RotateNoneFlipX); if (clipRectangle != null) dc.SetClip(clipRectangle.Value); using (var attr = new ImageAttributes()) { dc.CompositingMode = CompositingMode.SourceOver; dc.CompositingQuality = CompositingQuality.HighQuality; dc.InterpolationMode = InterpolationMode.HighQualityBicubic; dc.PixelOffsetMode = PixelOffsetMode.HighQuality; attr.SetWrapMode(WrapMode.TileFlipXY); dc.DrawImage(bmp, bounds, 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, attr); }*/ rnd.DrawBackground(dc, bounds, clipRectangle ?? bounds); /// /// Draws the background image of the current visual style element onto a glass background within the specified bounding rectangle /// and optionally clipped to the specified clipping rectangle. /// /// The instance. /// The used to draw the background image. /// A in which the background image is drawn. /// A that defines a clipping rectangle for the drawing operation. /// If set to true flip the image for right to left layout. public static void DrawGlassBackground(this VisualStyleRenderer rnd, IDeviceContext dc, Rectangle bounds, Rectangle? clipRectangle = null, bool rightToLeft = false) { var ht = new SafeHTHEME(rnd.Handle, false); dc.DrawViaDIB(bounds, (memoryHdc, b) => { var rBounds = new RECT(bounds); //var opts = new DrawThemeBackgroundOptions(clipRectangle); // Draw background var oldLayout = DCLayout.GDI_ERROR; if (rightToLeft) if ((oldLayout = SetLayout(memoryHdc, DCLayout.LAYOUT_RTL)) == DCLayout.GDI_ERROR) throw new NotSupportedException("Unable to change graphics layout to RTL."); DrawThemeBackground(ht, memoryHdc, rnd.Part, rnd.State, rBounds, clipRectangle); if (oldLayout != DCLayout.GDI_ERROR) SetLayout(memoryHdc, oldLayout); }); } /// Draws the image from the specified within the specified bounds on a glass background. /// The instance. /// The used to draw the image. /// A in which the image is drawn. /// An that contains the to draw. /// The index of the within to draw. public static void DrawGlassImage(this VisualStyleRenderer rnd, IDeviceContext g, Rectangle bounds, ImageList imageList, int imageIndex) { var ht = new SafeHTHEME(rnd.Handle, false); g.DrawViaDIB(bounds, (memoryHdc, b) => DrawThemeIcon(ht, memoryHdc, rnd.Part, rnd.State, bounds, imageList.Handle, imageIndex)); } /// Draws the specified image within the specified bounds on a glass background. /// The instance. /// The used to draw the image. /// A in which the image is drawn. /// An that contains the to draw. /// /// if set to true draws the image in a disabled state using the method. /// public static void DrawGlassImage(this VisualStyleRenderer? rnd, IDeviceContext g, Rectangle bounds, Image image, bool disabled = false) => g.DrawViaDIB(bounds, (memoryHdc, b) => { using var mg = Graphics.FromHdc(memoryHdc.DangerousGetHandle()); if (disabled) ControlPaint.DrawImageDisabled(mg, image, bounds.X, bounds.Y, Color.Transparent); else mg.DrawImage(image, bounds); }); /// /// Draws glowing text in the specified bounding rectangle with the option of overriding text color and applying other text formatting. /// /// The instance. /// The used to draw the text. /// A in which the text is drawn. /// The text to draw. /// Optional font override. /// Optionally, the color to draw text in overriding the default color for the theme. /// A bitwise combination of the values. /// The size of the glow. public static void DrawGlowingText(this VisualStyleRenderer rnd, IDeviceContext dc, Rectangle bounds, string text, Font font, Color? color, TextFormatFlags flags = TextFormatFlags.Default, int glowSize = 10) { var ht = new SafeHTHEME(rnd.Handle, false); dc.DrawViaDIB(bounds, (memoryHdc, b) => { // Create and select font using (new GdiObjectContext(memoryHdc, new SafeHFONT(font?.ToHfont() ?? IntPtr.Zero))) { // Draw glowing text var dttOpts = new DTTOPTS(null) { GlowSize = glowSize, AntiAliasedAlpha = true }; if (color != null) dttOpts.TextColor = color.Value; var textBounds = new RECT(4, 0, bounds.Right - bounds.Left, bounds.Bottom - bounds.Top); DrawThemeTextEx(ht, memoryHdc, rnd.Part, rnd.State, text, text.Length, FromTFF(flags), ref textBounds, dttOpts); } } ); } /// Draws text in the specified bounding rectangle with the option of applying other text formatting. /// The instance. /// The used to draw the text. /// A in which the text is drawn. /// The text to draw. /// A bitwise combination of the values. /// The . public static void DrawText(this VisualStyleRenderer rnd, IDeviceContext dc, ref Rectangle bounds, string text, TextFormatFlags flags, ref DTTOPTS options) { var rc = new RECT(bounds); var ht = new SafeHTHEME(rnd.Handle, false); using (var hdc = new SafeTempHDC(dc)) DrawThemeTextEx(ht, hdc, rnd.Part, rnd.State, text, text.Length, FromTFF(flags), ref rc, options); bounds = rc; } /// /// Gets the background image of the current visual style element within the specified background color. If /// is set, the resulting image will contain each of the state images side by side. /// /// The instance. /// The background color. This color cannot have an alpha channel. /// The optional list of states to render side by side. /// The background image. public static Bitmap GetBackgroundBitmap(this VisualStyleRenderer rnd, Color clr, int[]? states = null) { const int wh = 200; if (rnd == null) throw new ArgumentNullException(nameof(rnd)); rnd.SetParameters(rnd.Class, rnd.Part, 0); if (states == null || states.Length == 0) states = new[] { rnd.State }; var i = states.Length; // Get image size Size imgSz; using (var sg = Graphics.FromHwnd(IntPtr.Zero)) imgSz = rnd.GetPartSize(sg, new Rectangle(0, 0, wh, wh), ThemeSizeType.Draw); if (imgSz.Width == 0 || imgSz.Height == 0) imgSz = new Size(rnd.GetInteger(IntegerProperty.Width), rnd.GetInteger(IntegerProperty.Height)); if (imgSz.Width == 0 || imgSz.Height == 0) { using var sg = Graphics.FromHwnd(IntPtr.Zero); imgSz = MaxSize(rnd.GetPartSize(sg, new Rectangle(0, 0, wh, wh), ThemeSizeType.Minimum), imgSz); } var bounds = new Rectangle(0, 0, imgSz.Width * i, imgSz.Height); // Draw each background linearly down the bitmap using var memoryHdc = SafeHDC.ScreenCompatibleDCHandle; // Create a device-independent bitmap and select it into our DC var info = new BITMAPINFO(bounds.Width, -bounds.Height); using (memoryHdc.SelectObject(CreateDIBSection(HDC.NULL, info, DIBColorMode.DIB_RGB_COLORS, out var ppv))) { using (var memoryGraphics = Graphics.FromHdc(memoryHdc.DangerousGetHandle())) { // Setup graphics memoryGraphics.CompositingMode = CompositingMode.SourceOver; memoryGraphics.CompositingQuality = CompositingQuality.HighQuality; memoryGraphics.SmoothingMode = SmoothingMode.HighQuality; memoryGraphics.Clear(clr); // Draw each background linearly down the bitmap var rect = new Rectangle(0, 0, imgSz.Width, imgSz.Height); foreach (var state in states) { rnd.SetParameters(rnd.Class, rnd.Part, state); rnd.DrawBackground(memoryGraphics, rect); rect.X += imgSz.Width; } } // Copy DIB to Bitmap var bmp = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb); using (var primaryHdc = new SafeTempHDC(Graphics.FromImage(bmp))) BitBlt(primaryHdc, bounds.Left, bounds.Top, bounds.Width, bounds.Height, memoryHdc, 0, 0, RasterOperationMode.SRCCOPY); return bmp; } } /// Returns the value of the specified font property for the current visual style element. /// The instance. /// The used to draw the text. /// A value to return if the system has no font defined for this instance. /// /// A that contains the value of the property specified by the prop parameter for the current visual style element. /// public static Font? GetFont2(this VisualStyleRenderer rnd, IDeviceContext? dc = null, Font? defaultValue = null) { using var hdc = new SafeTempHDC(dc); return 0 != GetThemeFont(new SafeHTHEME(rnd.Handle, false), hdc, rnd.Part, rnd.State, 210, out var f) ? defaultValue : Font.FromLogFont(f); } private static DrawTextFlags FromTFF(TextFormatFlags tff) => (DrawTextFlags)(int)tff; private static long GetHashCode(this VisualStyleRenderer r) => (long)r.Class.GetHashCode() << 32 | ((uint)r.Part << 16 | (ushort)r.State); private static Size MaxSize(Size sz1, Size sz2) => new(Math.Max(sz1.Width, sz2.Width), Math.Max(sz1.Height, sz2.Height)); }