Элемент управления использующий GDI+

108

Зная построение специальных элементов управления, скорее всего, не терпится приступить к использованию GDI+ для создания собственных тщательно инкапсулированных специальных элементов управления. К сожалению, из-за способа внедрения изображений GDI+ в страницу ASP.NET не позволяет это делать достаточно легко.

Как уже было показано, для применения GDI+ придется создать отдельную веб-страницу. Затем через дескриптор <img> содержимое этой страницы можно встраивать в другую страницу. В результате просто поместить специальный элемент управления, использующий GDI+, на веб-страницу не получится. Но что можно сделать - так это создать специальный элемент управления, который содержит в себе дескриптор <img>.

Этот элемент управления может предоставить удобный интерфейс программирования, имеющий необходимые свойства, методы и события. Но специальный элемент управления не будет в действительности генерировать изображение. Он будет собирать данные из своих свойств, использовать их для построения строки запроса в URL-адресе, а затем визуализировать себя в виде дескриптора <img>, указывающего на страницу, которая генерирует динамическое изображение. Специальный элемент управления предоставляет контейнер более высокого уровня, который абстрагирует процесс передачи информации странице GDI+.

На рисунке ниже этот процесс показан для примера, который будет применяться с целью демонстрации данного метода. Подход со специальным элементом управления используется для создания простой метки, которая визуализируется на градиентном фоне. Специальный элемент управления имеет имя GradientLabel, а код GDI+ помещен в отдельную страницу, названную GradientLabel.aspx. Чтобы увидеть пример в действии, нужно запросить веб-страницу Default.aspx, которая содержит единственный экземпляр элемента управления GradientLabel:

Использование специальных элементов управления с GDI+

Чтобы не спутать реальные веб-страницы с веб-страницами, используемыми для предоставления рисунка GDI+, подумайте о применении специального обработчика HTTP для генерации изображения. Благодаря обработчику HTTP, генераторы изображений могут иметь специальное расширение и использовать по сути тот же код в методе ProcessRequest().

Класс специального элемента управления

Прежде всего, понадобится создать класс элемента управления. Как и с любым специальным элементом управления, его можно поместить это в папку App_Code веб-сайта или, в идеале, в отдельный проект библиотеки классов.

Класс специального элемента управления (по имени GradientLabel) унаследован от класса Control, а не WebControl. Причина в том, что он не сможет поддерживать развитый набор свойств стиля, т.к. визуализирует динамическую графику, а не HTML-дескриптор.

Класс GradientLabel предоставляет пять свойств, которые позволяют пользователю указывать текст, размер шрифта и цвета, используемые для градиентной заливки и текста. Свойства устанавливаются в определенные значения по умолчанию в конструкторе GradientLabel, как показано в следующем примере:

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;

namespace SimpleControls
{
    public class GradientLabel : Control
    {
        public GradientLabel()
        {
            Text = "";
            TextColor = Color.White;
            GradientColorStart = Color.Blue;
            GradientColorEnd = Color.DarkBlue;
            TextSize = 14;
        }

        public string Text
        {
            get { return (string)ViewState["Text"]; }
            set { ViewState["Text"] = value; }
        }

        public int TextSize
        {
            get { return (int)ViewState["TextSize"]; }
            set { ViewState["TextSize"] = value; }
        }

        public Color GradientColorStart
        {
            get { return (Color)ViewState["ColorStart"]; }
            set { ViewState["ColorStart"] = value; }
        }

        public Color GradientColorEnd
        {
            get { return (Color)ViewState["ColorEnd"]; }
            set { ViewState["ColorEnd"] = value; }
        }

        public Color TextColor
        {
            get { return (Color)ViewState["TextColor"]; }
            set { ViewState["TextColor"] = value; }
        }
    }
    
    // ...
}

Объект GradientLabel визуализируется в виде дескриптора <img>, который указывает на страницу GradientLabel.aspx. Именно эта страница содержит код рисования GDI+. При визуализации объекта GradientLabel он считывает информацию из всех свойств и предоставляет ее в строке запроса.

public class GradientLabel : Control
{
    // ...

    protected override void Render(HtmlTextWriter writer)
    {
            HttpContext context = HttpContext.Current;
            writer.Write("<img src='" + "GradientLabel.aspx?" +
              "Text=" + context.Server.UrlEncode(Text) +
              "&TextSize=" + TextSize.ToString() +
              "&TextColor=" + TextColor.ToArgb() +
              "&GradientColorStart=" + GradientColorStart.ToArgb() +
              "&GradientColorEnd=" + GradientColorEnd.ToArgb() +
              "'>");
    }
}

Визуализирующая страница

Прежде всего, страница GradientLabel.aspx должна извлечь свойства из строки запроса, как показано в следующем примере:

protected void Page_Load(object sender, EventArgs e)
{
        string text = Server.UrlDecode(Request.QueryString["Text"]);
        int textSize = Int32.Parse(Request.QueryString["TextSize"]);
        Color textColor = Color.FromArgb(
          Int32.Parse(Request.QueryString["TextColor"]));
        Color gradientColorStart = Color.FromArgb(
          Int32.Parse(Request.QueryString["GradientColorStart"]));
        Color gradientColorEnd = Color.FromArgb(
          Int32.Parse(Request.QueryString["GradientColorEnd"]));

        // ...
}

Перед страницей GradientLabel.aspx стоит интересная задача. Текст и размер шрифта предоставляются динамически, поэтому невозможно использовать фиксированный размер растрового изображения, не рискуя сделать его слишком маленьким (что приведет к усечению части текстового содержимого) или слишком большим (что повлечет за собой напрасные дополнительные расходы памяти сервера и увеличение времени пересылки изображения клиенту).

Один из возможных способов решения этой проблемы состоит в создании необходимого объекта Font и последующем вызове метода Graphics.MeasureString() для определения количества пикселей для отображения нужного текста. Единственная сложность здесь в том, чтобы не позволить растровому изображению стать слишком большим. Например, вряд ли имеет смысл создавать растровое изображение размером в сотни мегабайт, если пользователь отправляет строку с сотней-второй символов. Во избежание этого риска, код визуализации налагает ограничение на максимальную высоту и ширину, равное 800 пикселей.

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

Фрагмент кода рисования, который получает информацию строки запроса и измеряет текст, выглядит следующим образом:

protected void Page_Load(object sender, EventArgs e)
{
        // ...

        // Определить шрифт
        Font font = new Font("Tahoma", textSize, FontStyle.Bold);

        // Использовать тестовое изображение для измерения текста
        SizeF size;
        using (Bitmap image = new Bitmap(1, 1))
        {
            using (Graphics graphic = Graphics.FromImage(image))
            {
                size = graphic.MeasureString(text, font);
            }
        }

        // На основе этих измерений выбрать разумные размеры растрового изображения.
        // Если текст является большим, ограничьте размер некоторым максимумом,
        // чтобы предотвратить серьезное замедление работы сервера
        int width = (int)Math.Min(size.Width + 20, 800);
        int height = (int)Math.Min(size.Height + 20, 800);
        using (Bitmap image = new Bitmap(width, height))
        {
            using (Graphics graphic = Graphics.FromImage(image))
            {
                // ...
            }
        }
}

Легко заметить, что к каждому измерению добавляется 20 дополнительных пикселей кроме тех, что необходимы для текста. Это позволяет оставить с каждой стороны поля шириной в 10 пикселей. И, наконец, можно создать LinearGradientBrush, закрасить поверхность рисования, а затем добавить текст, как показано в следующем примере:

protected void Page_Load(object sender, EventArgs e)
{
        // ...
        
        using (Bitmap image = new Bitmap(width, height))
        {
            using (Graphics graphic = Graphics.FromImage(image))
            {
                LinearGradientBrush brush = new LinearGradientBrush(
                    new Rectangle(new Point(0, 0), image.Size),
                    gradientColorStart, gradientColorEnd, LinearGradientMode.ForwardDiagonal);

                // Нарисовать градиентный фон
                graphic.FillRectangle(brush, 0, 0, width, height);

                // Нарисовать текст метки
                graphic.DrawString(text, font, new SolidBrush(textColor), 10, 10);

                // Сохранить изображение в выходной поток
                image.Save(Response.OutputStream,
                    System.Drawing.Imaging.ImageFormat.Jpeg);
            }
        }
}

Чтобы протестировать метку, можно создать примерно такой дескриптор элемента управления на базовой странице:

<cc1:GradientLabel ID="GradientLabel1" runat="server" Text="Wuck Forld!"
    GradientColorStart="MediumSeaGreen" GradientColorEnd="Lime" />

Визуализированный результат показан на рисунке ниже:

Специальный элемент управления типа метки GDI+

Передача информации от одной страницы другой - удобный способ работы с GDI+, но ограничения, налагаемые на строку запроса, означают, что это подходит только для относительно небольших объемов данных. Для крупных объемов данных можно использовать коллекцию Session. Это приводит к большим издержкам, поскольку все, помещаемое в коллекцию Session, расходует память сервера, однако позволяет передавать любые сериализуемые данные.

Пройди тесты
Лучший чат для C# программистов