using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; using Vanara.Drawing; using Vanara.Extensions; using Vanara.PInvoke; using static Vanara.PInvoke.UxTheme; namespace Vanara.Windows.Forms { /// A button that displays an image and no text. [ToolboxItem(true), ToolboxBitmap(typeof(ThemedImageDraw), "ThemedImageButton.bmp")] public class ThemedImageDraw : CustomDrawBase { private const string defaultClass = "BUTTON"; private const int defaultPart = 1; private const string defaultText = ""; private const string defaultToolTip = ""; private readonly ToolTip toolTip; private string styleClass; private int stylePart; private bool supportGlass; private VisualTheme theme; /// Initializes a new instance of the class. public ThemedImageDraw() { SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true); SetTheme(defaultClass, defaultPart); toolTip = new ToolTip(); toolTip.SetToolTip(this, defaultToolTip); StyleClass = "BUTTON"; StylePart = 1; base.Text = defaultText; } /// Fired when button state needs to be translated. public event Func TranslateButtonState; /// Gets or sets the background color of the control. /// A value representing the background color. public override Color BackColor { get => OnGlass ? Color.Transparent : base.BackColor; set => base.BackColor = value; } /// Gets or sets the image that is displayed on a button control. /// The displayed on the button control. The default value is null. [DefaultValue(null)] public new Image Image { get => base.Image; set { if (value != null) { InitializeImageList(value.Size); ImageList.Images.Add(value); } else ImageList = null; base.Image = value; } } /// Gets or sets the style class. /// The style class. [DefaultValue(defaultClass), Category("Appearance")] public string StyleClass { get => styleClass; set { if (styleClass != value) { styleClass = value; ResetTheme(); } } } /// Gets or sets the style part. /// The style part. [DefaultValue(defaultPart), Category("Appearance")] public int StylePart { get => stylePart; set { if (stylePart != value) { stylePart = value; Invalidate(); } } } /// Gets or sets a value indicating whether this table supports glass (can be enclosed in the glass margin). /// true if supports glass; otherwise, false. [DefaultValue(false), Category("Appearance")] public bool SupportGlass { get => supportGlass; set { if (supportGlass != value) { supportGlass = value; Invalidate(); } } } /// Gets or sets the text associated with this control. /// The text associated with this control. [DefaultValue(defaultText), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), EditorBrowsable(EditorBrowsableState.Never)] public override string Text { get => base.Text; set => base.Text = value; } /// Gets or sets the tool tip text. /// The tool tip text. [DefaultValue(defaultToolTip), Category("Appearance")] public string ToolTipText { get => toolTip.GetToolTip(this); set => toolTip.SetToolTip(this, value); } /// Retrieves the default size for the control. /// /// The default of the control. protected override Size DefaultSize => new Size(30, 30); /// Gets a value indicating whether on glass. /// true if on glass; otherwise, false. private bool OnGlass => !this.IsDesignMode() && ThemingSupported && SupportGlass; private bool ThemingSupported => Application.RenderWithVisualStyles || DesktopWindowManager.CompositionEnabled; /// Retrieves the size of a rectangular area into which a control can be fitted. /// The custom-sized area for a control. /// An ordered pair of type representing the width and height of a rectangle. public override Size GetPreferredSize(Size proposedSize) => DefaultSize; /// Sets the image list images using an image strip. /// The image strip. /// The orientation of the strip. public void SetImageListImageStrip(Image imageStrip, Orientation orientation) { if (imageStrip == null) ImageList = null; else { var imageSize = orientation == Orientation.Vertical ? new Size(imageStrip.Width, imageStrip.Height / 4) : new Size(imageStrip.Width / 4, imageStrip.Height); InitializeImageList(imageSize); if (orientation == Orientation.Horizontal) ImageList.Images.AddStrip(imageStrip); else using (var bmp = new Bitmap(imageStrip)) for (var r = new Rectangle(Point.Empty, imageSize); r.Y < imageStrip.Height; r.Y += imageSize.Height) ImageList.Images.Add(bmp.Clone(r, bmp.PixelFormat)); } } /// Sets the theme using theme class information. /// Name of the theme class. /// The theme part. public void SetTheme(string className, int part) { styleClass = className; stylePart = part; ResetTheme(); } /// Raises the event. /// A that contains the event data. protected override void OnPaint(PaintEventArgs e) { if (!Visible) return; var g = e.Graphics; g.SmoothingMode = SmoothingMode.HighQuality; g.CompositingQuality = CompositingQuality.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; BufferedPaint.PaintAnimation(g, this, e.ClipRectangle, PaintAction, TranslateState(LastState), TranslateState(State), GetDuration); } /// Primary function for painting the button. This method should be overridden instead of OnPaint. /// The graphics. /// The bounds. /// The translated current state of the control. protected virtual void PaintButton(Graphics graphics, Rectangle bounds, int currentstate) { System.Diagnostics.Debug.WriteLine($"PaintButton: desMode:{this.IsDesignMode()};vsEnabled:{Application.RenderWithVisualStyles};vsOnOS:{ThemingSupported};btnState:{currentstate};enabled:{Enabled};imgCt:{ImageList?.Images.Count ?? 0}"); if (theme != null && ThemingSupported) { var bs = TranslateButtonState?.Invoke(State) ?? (int)State; var rtl = this.GetRightToLeftProperty() == RightToLeft.Yes; if (OnGlass) { graphics.DrawViaDIB(bounds, (hdc, r) => theme.DrawBackground(Graphics.FromHdc(hdc.DangerousGetHandle()), stylePart, bs, r, r, rtl)); } else { theme.DrawParentBackground(this, graphics, bounds); theme.DrawBackground(graphics, stylePart, bs, bounds, bounds, rtl); } } else { if (ImageList != null && ImageList.Images.Count > 0) { var idx = currentstate - 1; if (ImageList.Images.Count == 1) idx = 0; else if (ImageList.Images.Count == 2) idx = currentstate == TranslateState(ControlState.Disabled) ? 1 : 0; // TODO: Determine if this is needed //else if (ImageList.Images.Count == 3) // idx = currentstate == TranslateState(ControlState.Normal) ? 0 : idx - 1; var forceDisabled = !Enabled && ImageList.Images.Count == 1; if (OnGlass) { VisualStylesRendererExtension.DrawGlassImage(null, graphics, bounds, ImageList.Images[idx], forceDisabled); } else { if (!ThemingSupported) { var g = graphics.BeginContainer(); var translateRect = bounds; graphics.TranslateTransform(-bounds.Left, -bounds.Top); var pe = new PaintEventArgs(graphics, translateRect); InvokePaintBackground(Parent, pe); InvokePaint(Parent, pe); graphics.ResetTransform(); graphics.EndContainer(g); } else graphics.Clear(Parent.BackColor); if (forceDisabled) ControlPaint.DrawImageDisabled(graphics, ImageList.Images[idx], 0, 0, Color.Transparent); else { //base.ImageList.Draw(graphics, bounds.X, bounds.Y, bounds.Width, bounds.Height, idx); //VisualStyleRendererExtender.DrawGlassImage(null, graphics, bounds, base.ImageList.Images[idx], forceDisabled); // Not 7 graphics.DrawImage(ImageList.Images[idx], bounds, bounds, GraphicsUnit.Pixel); // Works on XP, not 7, with Parent.BackColor } } } /*else if (this.ImageList != null && this.ImageList.Images.Count > 1) { int idx = (int)currentstate - 1; if (this.ImageList.Images.Count == 2) idx = currentstate == PushButtonState.Disabled ? 1 : 0; if (this.ImageList.Images.Count == 3) idx = currentstate == PushButtonState.Normal ? 0 : idx - 1; if (rnd != null && !this.IsDesignMode() && DesktopWindowManager.IsCompositionEnabled()) rnd.DrawGlassIcon(graphics, bounds, this.ImageList, idx); else this.ImageList.Draw(graphics, bounds.X, bounds.Y, bounds.Width, bounds.Height, idx); }*/ // No image so draw standard button else { ButtonRenderer.DrawParentBackground(graphics, bounds, this); if (Enum.IsDefined(typeof(System.Windows.Forms.VisualStyles.PushButtonState), currentstate)) ButtonRenderer.DrawButton(graphics, bounds, (System.Windows.Forms.VisualStyles.PushButtonState)currentstate); else ButtonRenderer.DrawButton(graphics, bounds, System.Windows.Forms.VisualStyles.PushButtonState.Normal); } } if (Focused) ControlPaint.DrawFocusRectangle(graphics, bounds); } private int GetDuration(int oldstate, int newstate) => (int)(theme?.GetTransitionDuration(stylePart, oldstate - 1, newstate - 1)?.TotalMilliseconds ?? 0.0); private void InitializeImageList(Size imageSize) => ImageList = new ImageList() { ImageSize = imageSize, ColorDepth = ColorDepth.Depth32Bit, TransparentColor = Color.Transparent }; private void PaintAction(Graphics graphics, Rectangle bounds, int currentstate, int data) => PaintButton(graphics, bounds, currentstate); private void ResetTheme() { if (styleClass != null && ThemingSupported) { try { theme = new VisualTheme(Parent, styleClass, SupportGlass ? OpenThemeDataOptions.OTD_NONCLIENT : OpenThemeDataOptions.None); } catch { theme = null; } } else theme = null; Refresh(); } private int TranslateState(ControlState st) => TranslateButtonState?.Invoke(st) ?? (int)st; } }