Разработка своего компонента аналога ProgressBar. Часть 2
Продолжение статьи о продвинутом индикаторе процесса.
Давайте придадим нашему компоненту более привлекательный вид и добавим ему немного функционала. Продолжаем работать с предыдущим проектом.
У нас уже есть метод Increment, который увеличивает текущее значение на заданную величину. Напишем противоположный метод Decrement, который будет уменьшать текущее значение.
public void Decrement(int value)
{
currentvalue -= value;
if (currentvalue < minimum)
currentvalue = minimum;
if (currentvalue > maximum)
currentvalue = maximum;
Invalidate();
Update();
}
Как вы помните, у нас есть незадействованная переменная step, отвечающая за шаг изменения текущего значения индикатора процесса. Используем ее в парочке методов.
/// <summary>
/// Увеличение текущего значения на заданный шаг
/// </summary>
public void PerformStep()
{
Increment(step);
}
/// <summary>
/// Уменьшение текущего значения на заданный шаг
/// </summary>
public void PerformStepBack()
{
Decrement(step);
}
Думаю с этим все понятно. Теперь займемся, на мой взгляд самым интересным, — изменением внешнего вида.
Как видно из скриншота, нам необходимо реализовать закругленные углы и добавить немного теней, для большего эффекта.
Для реализации градиента и теней нам необходимо добавить несколько переменным к уже имеющимся. Окончательный список приватных переменных выглядит так. Назначение думаю понятно из названий переменных.
private int currentvalue = 0, minimum = 0, maximum = 100, step = 10; //Значения по умолчанию для всех цветов private Color bordercolor = Color.FromArgb(146, 137, 128); private Color backgroundcolor = Color.FromArgb(219, 211, 203); private Color barcolor1 = Color.FromArgb(255, 187, 115); private Color barcolor2 = Color.FromArgb(250, 163, 70); private Color barBorder = Color.FromArgb(206, 134, 58); private Color shadow1 = Color.FromArgb(198, 190, 182); private Color shadow2 = Color.FromArgb(207, 199, 191); private Color shadow3 = Color.FromArgb(215, 207, 199); private GraphicsPath gfxPath; //Путь для рисования закругленных углов private int cornerRadius = 3; //Радиус углов
Класс GraphicsPath используется для создания закругленных углов.
Перейдем к описанию метода OnPaint.
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
//Создаем фигуру
gfxPath = new GraphicsPath();
}
Создаем новую переменную gfxPath, которую, в последствии, уничтожим.
Задаем начальные координаты, от которых будем отталкиваться при рисовании, а также, «рабочую» ширину и высоту на 1 пиксель меньше чем ClientRectangle.Width и ClientRectangle.Height. Если не уменьшать, границы области закрашивания вылезут за границы и будут невидны.
int x = 0, y = 0; //начальные координаты //С учетом следующих значений определяется область отрисовки int width = ClientRectangle.Width - 1; //фактическая ширина int height = ClientRectangle.Height - 1; //и высота
Теперь рисуем прямоугольник с закругленными углами. Такой же прямоугольник мы уже рисовали в этой статье.
gfxPath.AddLine(x + cornerRadius, y, x + width - (cornerRadius * 2), y); gfxPath.AddArc(x + width - (cornerRadius * 2), y, cornerRadius * 2, cornerRadius * 2, 270, 90); gfxPath.AddLine(x + width, y + cornerRadius, x + width, y + height - (cornerRadius * 2)); gfxPath.AddArc(x + width - (cornerRadius * 2), y + height - (cornerRadius * 2), cornerRadius * 2, cornerRadius * 2, 0, 90); gfxPath.AddLine(x + width - (cornerRadius * 2), y + height, x + cornerRadius, y + height); gfxPath.AddArc(x, y + height - (cornerRadius * 2), cornerRadius * 2, cornerRadius * 2, 90, 90); gfxPath.AddLine(x, y + height - (cornerRadius * 2), x, y + cornerRadius); gfxPath.AddArc(x, y, cornerRadius * 2, cornerRadius * 2, 180, 90); gfxPath.CloseFigure();
Создаем новую кисть и закрашиваем только что созданную область цветом заднего плана.
SolidBrush bgBrush = new SolidBrush(backgroundcolor); //кисть с заданным цветом g.FillPath(bgBrush, gfxPath); //закрашиваем область bgBrush.Dispose(); //удаляем из памяти
Поверх заднего фона необходимо нарисовать тень, высота которой составляет 3 пикселя. В этом случае я решил не делать градиент, а просто нарисовать три линии разного цвета.
g.DrawLine(new Pen(shadow1), new Point(x + cornerRadius - 1, y + 1), new Point(x + width - (cornerRadius - 1), y + 1)); g.DrawLine(new Pen(shadow2), new Point(x + cornerRadius - 2, y + 2), new Point(x + width - (cornerRadius - 2), y + 2)); g.DrawLine(new Pen(shadow3), new Point(x + cornerRadius - 3, y + 3), new Point(x + width - (cornerRadius - 3), y + 3));
Компонент нарисован. Теперь необходимо нарисовать сам индикатор процесса. Перед рисованием нужно проверить пару условий, которые исключают работу последующего алгоритма в нашем методе.
//Если текущее значение минимально, ничего не отрисовываем и выходим из метода
if (maximum == minimum || currentvalue == minimum)
{
//Рисуем только бордюр
drawBorder(e.Graphics);
//И выходим из метода
return;
}
Теперь определяем ширину индикатора и выполняем отступ, с которым будем рисовать от левого края. Отступ делается для того, чтобы индикатор начинал рисоваться не с линии бордюра, иначе при размере компонента в 100 пикселей, вы не увидите 1%, потому что его перекроет бордюр. Такой же отступ делается и слева.
//Задаем отступ x += 1; //Определяем, какую ширину должен иметь бегунок процесса в пикселях //Без учета по одному пикселю слева и справа int fillWidth = ((width - x) * currentvalue) / (maximum - minimum);
Теперь самое, наверное, основное и сложное.
if (fillWidth > 0)
{
//Определяем область отрисовки индикатора процесса
GraphicsPath gfxPathBar = new GraphicsPath();
//В любом случае рисуем одну вертикальную левую линию снизу вверх
gfxPathBar.AddLine(x, y + height - (cornerRadius), x, y + cornerRadius);
//Только если ширина индикатора процесса больше 1 рисуем все остальное
if (fillWidth > 1)
{
gfxPathBar.AddArc(x, y, cornerRadius, cornerRadius, 180, 90);
gfxPathBar.AddLine(x + cornerRadius, y, x + fillWidth - cornerRadius, y);
gfxPathBar.AddArc(x + fillWidth - cornerRadius, y, cornerRadius, cornerRadius, 270, 90);
gfxPathBar.AddLine(x + fillWidth, y + cornerRadius, x + fillWidth, y + height - (cornerRadius * 2));
gfxPathBar.AddArc(x + fillWidth - (cornerRadius * 2), y + height - (cornerRadius * 2), cornerRadius * 2, cornerRadius * 2, 0, 90);
gfxPathBar.AddLine(x + fillWidth - (cornerRadius * 2), y + height, x + cornerRadius, y + height);
gfxPathBar.AddArc(x, y + height - (cornerRadius), cornerRadius, cornerRadius, 90, 90);
}
//Определяем область закрашивания
//В данном случае координаты значения не имеют
//Так как здесь создается размер кисти
Rectangle mainProgressRect = new Rectangle(0, 0, fillWidth, height - 1);
//Создаем градиентную кисть для закрашивания
Brush gradientBrush = new LinearGradientBrush(mainProgressRect, barcolor1, barcolor2, LinearGradientMode.Vertical);
g.FillPath(gradientBrush, gfxPathBar); //закрашиваем
gradientBrush.Dispose(); //уничтожаем
//Если не достигнут край, рисуем бордюр с правой стороны индикатора
//Не рисуем, если значение максимально
if (fillWidth != width - x && currentvalue != maximum)
g.DrawLine(new Pen(barBorder), x + fillWidth - 1, y + 1, x + fillWidth - 1, height - 1);
}
Я описал комментарии, поэтому думаю этого хватит для понимания алгоритма.
Остается только нарисовать бордюр и написать текст — drawBorder и drawText.
Алгоритм рисование бордюра немного отличается от того, что был представлен в предыдущей статье. Здесь используется сглаживание, чтобы смягчить закругления краев. Режим сглаживания можно задавать только здесь. Если его задать до отрисовки индикатора, то последний «вылазит» за границы компонента.
private void drawBorder(Graphics g)
{
//Задаем режим сглаживания
g.SmoothingMode = SmoothingMode.AntiAlias;
//Рисуем бордюр уже поверх всего остального, что было нарисовано ранее
g.DrawPath(new Pen(bordercolor), gfxPath);
//Уничтожаем область. Она нам больше не нужна
gfxPath.Dispose();
}
Рисуем текст.
private void drawText(Graphics g)
{
Font f = new System.Drawing.Font("Arial", 9, FontStyle.Bold);
StringFormat sf = new StringFormat();
//Формат выставляем по центру горизонтали и вертикали
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
//Отображаем текст на экране заданным цветом
g.DrawString(string.Format("{0}%", currentvalue), f, new SolidBrush(Color.FromArgb(73, 73, 73)), ClientRectangle, sf);
f.Dispose();
sf.Dispose();
}
Вот, в принципе, и все. Ваш компонент готов к работе.
Популярность: 6%
Если у вас возникли вопросы, вы можете оставить их в комментариях


