Graphics.DrawImage не работает для прозрачных изображений C # - PullRequest
0 голосов
/ 28 июня 2019

Я пытаюсь создать пользовательский элемент управления, в котором я просто рисую изображение на кнопке. Размер изображения изменяется, чтобы заполнить область кнопки, сохраняя при этом исходный масштаб изображения. Я продолжаю получать очень странное поведение в дизайне и режиме запуска.

В конструкторе большую часть времени прозрачные части изображения являются черными. Если я сделаю элемент управления достаточно маленьким, прозрачные области начнут заполняться любой случайной вещью, которую они могут найти на моем экране. В режиме запуска прозрачность всегда заполняется черным (см. Изображения ниже).

Я чувствую, что могу неправильно использовать элементы управления winforms, но у меня мало опыта с этим. Я перепробовал все предложения, найденные здесь: Использование Graphics.DrawImage () для рисования изображения с прозрачностью / альфа-каналом и некоторые другие, которые я нашел в Интернете, безрезультатно.

using System.Windows.Forms;
using System.Drawing;

namespace Tools
{
    public class CustomButton : Button
    {
        public CustomButton()
        {
            Image = (Image)Properties.Resources.ResourceManager.GetObject("Custom-Logo-Horiz-RGB");
            ForeColor = BackColor = Color.FromArgb(88, 88, 88);
            DoubleBuffered = true;
        }

        protected override void OnPaint(PaintEventArgs pevent)
        {
            DrawCustomImage(pevent.Graphics);
        }

        private void DrawCustomImage(Graphics graphics)
        {
            float baseHeight = Image.Height;
            float baseWidth = Image.Width;
            float maxHeight = (Height - borderWidth * 2);
            float maxWidth = (Width - borderWidth * 2);

            float newWidth = maxWidth;
            float heightToWidth = baseHeight / baseWidth;
            float newHeight = heightToWidth * newWidth;

            if (newHeight > maxHeight)
            {
                newHeight = maxHeight;
                float widthToHeight = 1 / heightToWidth;
                newWidth = widthToHeight * newHeight;
            }

            graphics.DrawImage(Image, new RectangleF(Width / 2 - newWidth / 2, Height / 2 - newHeight / 2, newWidth, newHeight));
        }

        #region Settings

        private float borderWidth = 6.0F;
        public float BorderWidth
        {
            get { return borderWidth; }
            set { borderWidth = value; }
        }

        #endregion
    }
}

Изображения:

  1. В режиме разработки: Режим проектирования. Черный фон, проблема
  2. В режиме разработки: Режим проектирования. Странное поведение
  3. В режиме работы: Режим работы на черном фоне

1 Ответ

0 голосов
/ 28 июня 2019

Я предложил вызвать base.OnPaint(e) в переопределении OnPaint, потому что, если только Button FlatStyle (или, лучше, стиль класса ButtonBase , из которого происходит Button)имеет тип FlatStyle.System , элемент управления Button считается OwnerDrawn.Как следствие, он создается с ControlStyles.UserPaint .Это имеет ряд последствий в том, как элемент управления рисуется диспетчерами класса ButtonBase, полученными из ButtonBaseAdapter , которые определяют стиль рендеринга и действия внутренних методов PaintWorker .

Плюс, как вы можете видеть в конструкторе ButtonBase , кнопки создаются в стиле ControlStyles.Opaque (и вы также можете видеть, что ControlStyles.OptimizedDoubleBuffer стиль используется).Это означает, что класс Button не рисует свой фон, это PaintWorker, который вызывает PaintThemedButtonBackground (если Application.RenderWithVisualStyles = true, в противном случае стандартный фон), используя тот же PaintEventArgs сгенерировано для класса Button (вы также можете определить, что DoubleBuffering включено по умолчанию).

Как следствие, вам нужно вызвать base.OnPaint(e) в переопределении, если вы хотитеконтроль, чтобы сделать правильно.

Вызов base.OnPaint(e) также рисует растровое изображение, назначенное свойству Image, если оно есть.
Поэтому я предложил назначить собственное растровое изображение полю (или другому пользовательскому свойству), не устанавливаяСвойство изображения.Если вы это сделаете, изображение будет нарисовано дважды: одно на ваших условиях, а другое - PaintWorker.

Об удалении неуправляемого объекта :
Если вы получаете пользовательский элемент управления из элемента управления .Net, вам не нужно особо беспокоиться о самом элементе управления.Это все обрабатывается внутри.Вы можете видеть в коде, который я разместил здесь, что используется protected override void Dispose(bool disposing): я поставил его там, чтобы вы могли видеть, что этот метод вызывается только при закрытии приложения;также он вызывается с параметром disposing, установленным в false: его вызывает Finalizer, объект уже удален, его ресурсы вместе с ним,

Возможно, вы захотите позаботиться о создаваемом вами объекте, особенно об объекте Graphics, при его создании: немедленно избавьтесь от этих объектов, вызвав для них Dispose () или объявив ихобъекты с с помощью оператора , который под капотом создаст блок try/finally, располагающий объект в секции finally.

В коде, размещенном здесь, вы можете видеть, что при установке нового изображения старый утилизируется сразу.
Метод OnHandledDistroyed переопределяется, чтобы избавиться от него.текущего объекта, назначенного полю, которое содержит растровое изображение, отображаемое вашей кнопкой.Это потому, что это растровое изображение исходит от встроенного ресурса, лучше избавьтесь от него, как только он больше не нужен.

Если вместо этого вы создаете класс, который использует неуправляемые ресурсы, который не является производным от другого, который уже обрабатывает сборку мусора, то реализуйте интерфейс IDisposable .

Некоторые документы на эту тему:

Серия Эрика Липперта о сборке мусора и финализаторах: Если все, что вы знаете, неверно, часть первая
MSDN: Реализация метода Dispose (и следующие страницы).

Вот модифицированный класс, который реализует некоторые из предложений:

  • Обратите внимание, что частное поле используется для замены свойства Image: свойство Image будет нулевым (и не будет окрашено)в то время как свойство все еще доступно в Designer, и вы можете назначить другое изображение, не ставя под угрозу результат.
  • Старое изображение, если оно есть, удаляется каждый раз, когда заменяется новым.
  • Свойство BackgroundImage скрыто.

using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

[DesignerCategory("code")]
public class PayceButton : Button
{
    private Image myImage = null;
    private float borderWidth = 6.0F;
    public PayceButton() => InitializeComponent();
    private void InitializeComponent()
    {
        this.myImage = Properties.[A Resource Image Name];
        this.BackColor = Color.FromArgb(88, 88, 88);
    }

    public float BorderWidth {
        get => borderWidth;
        set { borderWidth = value; this.Invalidate(); }
    }

    public override string Text {
        get => string.Empty;
        set => base.Text = string.Empty;
    }

    public new Image Image {
        get => this.myImage;
        set { this.myImage?.Dispose();
              this.myImage = value;
              Invalidate();
        }
    }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public override Image BackgroundImage {
        get => base.BackgroundImage;
        set => base.BackgroundImage = null;
    }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public override ImageLayout BackgroundImageLayout {
        get => base.BackgroundImageLayout;
        set => base.BackgroundImageLayout = ImageLayout.None;
    }

    protected override void OnPaint(PaintEventArgs e) {
        base.OnPaint(e);
        DrawPayceImage(e.Graphics);
    }

    private void DrawPayceImage(Graphics g)
    {
        float scale = (Math.Min(this.Height, this.Width) - (borderWidth * 4)) / 
                       Math.Min(myImage.Height, myImage.Width);
        var scaledImageSize = new SizeF(this.myImage.Width * scale, myImage.Height * scale);
        var imageLocation = new PointF((this.Width - scaledImageSize.Width) / 2,
                                       (this.Height - scaledImageSize.Height) /2);
        g.DrawImage(myImage,
            new RectangleF(imageLocation, scaledImageSize),
            new RectangleF(PointF.Empty, myImage.Size), GraphicsUnit.Pixel);
    }

    protected override void OnHandleDestroyed(EventArgs e) {
        this.myImage?.Dispose();
        base.OnHandleDestroyed(e);
    }

    protected override void Dispose(bool disposing) {
        if (disposing) { this.myImage?.Dispose(); }
        base.Dispose(disposing);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...