Элемент управления использующий GDI+
108ASP.NET --- Веб-сайты ASP.NET --- Элемент управления использующий GDI+
Зная построение специальных элементов управления, скорее всего, не терпится приступить к использованию GDI+ для создания собственных тщательно инкапсулированных специальных элементов управления. К сожалению, из-за способа внедрения изображений GDI+ в страницу ASP.NET не позволяет это делать достаточно легко.
Как уже было показано, для применения GDI+ придется создать отдельную веб-страницу. Затем через дескриптор <img> содержимое этой страницы можно встраивать в другую страницу. В результате просто поместить специальный элемент управления, использующий GDI+, на веб-страницу не получится. Но что можно сделать - так это создать специальный элемент управления, который содержит в себе дескриптор <img>.
Этот элемент управления может предоставить удобный интерфейс программирования, имеющий необходимые свойства, методы и события. Но специальный элемент управления не будет в действительности генерировать изображение. Он будет собирать данные из своих свойств, использовать их для построения строки запроса в URL-адресе, а затем визуализировать себя в виде дескриптора <img>, указывающего на страницу, которая генерирует динамическое изображение. Специальный элемент управления предоставляет контейнер более высокого уровня, который абстрагирует процесс передачи информации странице GDI+.
На рисунке ниже этот процесс показан для примера, который будет применяться с целью демонстрации данного метода. Подход со специальным элементом управления используется для создания простой метки, которая визуализируется на градиентном фоне. Специальный элемент управления имеет имя GradientLabel, а код GDI+ помещен в отдельную страницу, названную GradientLabel.aspx. Чтобы увидеть пример в действии, нужно запросить веб-страницу Default.aspx, которая содержит единственный экземпляр элемента управления GradientLabel:
Чтобы не спутать реальные веб-страницы с веб-страницами, используемыми для предоставления рисунка 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+, но ограничения, налагаемые на строку запроса, означают, что это подходит только для относительно небольших объемов данных. Для крупных объемов данных можно использовать коллекцию Session. Это приводит к большим издержкам, поскольку все, помещаемое в коллекцию Session, расходует память сервера, однако позволяет передавать любые сериализуемые данные.