Слияние кода завершено, страница обновится автоматически
За последние два дня в проекте возникла необходимость использовать компонент для отображения данных с нулевой отметкой центра. Например, при использовании аналогового мультиметра для измерения напряжения или магнитометра для измерения магнитной интенсивности требуется возможность корректировки нуля как для положительных, так и для отрицательных значений. В результате был создан такой компонент.
Если вам понравится данное решение, вы можете интегрировать его в свой проект.
Мои художественные способности недостаточно высоки, чтобы провести адаптацию и тестирование.
Результат представлен на следующем рисунке:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace TestMiddleProgress
{
public class CustomProcess : Control
{
private float minValue = 0;
private float maxValue = 100;
private float middleValue = 50;
private float value = 0;
private const int verticalMargin = 14; // вертикальный отступ сверху и снизу
private const int horizontalMargin = 14; // горизонтальный отступ слева и справа
private bool isLog = false; // использовать логарифмическое значение
public bool IsLog
{
get { return isLog; }
set
{
isLog = value;
Invalidate(); // перерисовать контрол при изменении значения свойства
}
}
public float MinValue
{
get { return minValue; }
set
{
minValue = value;
Invalidate(); // перерисовать контрол при изменении значения свойства
}
}
public float MaxValue
{
get { return maxValue; }
set
{
maxValue = value;
Invalidate();
}
}
public float MiddleValue
{
get { return middleValue; }
set
{
middleValue = value;
Invalidate();
}
}
public float Value
{
get { return value; }
set
{
this.value = value;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
Rectangle rect = ClientRectangle;
float _maxValue = isLog ? AsLog(maxValue) : maxValue;
float _minValue = isLog ? AsLog(minValue) : minValue;
float _middleValue = isLog ? AsLog(middleValue) : middleValue;
float _value = isLog ? AsLog(value) : value;
// Отключаем заполнение фона
//g.FillRectangle(Brushes.LightGray, rect);
// Вычисляем диапазон значений
float range = _maxValue - _minValue;
float valueRange;
// Верно вычисляем диапазон между value и middleValue
if (_value >= _middleValue)
{
valueRange = _value - _middleValue;
}
else
{
valueRange = _middleValue - _value;
}
float percentage = valueRange / range;
// Вычисляем область для отрисовки
int height = rect.Height - 2 * verticalMargin;
int width = (int)((rect.Width - 2 * horizontalMargin) * percentage);
Rectangle drawRect;
int valueX;
if (_value >= _middleValue)
{
// Вычисляем позицию X для среднего значения
int middleValueX = (int)((_middleValue - _minValue) / range * (rect.Width - 2 * horizontalMargin)) + horizontalMargin;
drawRect = new Rectangle(rect.Left + middleValueX, rect.Top + verticalMargin, width, height);
}
else
{
// Вычисляем позицию X для текущего значения
valueX = (int)((_value - _minValue) / range * (rect.Width - 2 * horizontalMargin)) + horizontalMargin;
drawRect = new Rectangle(rect.Left + valueX, rect.Top + verticalMargin, width, height);
}
if (drawRect.Width > 0 && drawRect.Height > 0)
{
// Заливаем область градиентом
using (LinearGradientBrush brush = new LinearGradientBrush(drawRect, Color.LightBlue, Color.DarkGreen, LinearGradientMode.Horizontal))
{
g.FillRectangle(brush, drawRect);
}
}
// Отрисовываем текст максимального значения и стрелку
string maxText = maxValue.ToString();
SizeF maxTextSize = g.MeasureString(maxText, Font);
maxTextSize = new SizeF(maxTextSize.Width + 6, maxTextSize.Height); // коррекция размера текста
int maxValueX = (int)((_maxValue - _minValue) / range * (rect.Width - 2 * horizontalMargin)) + horizontalMargin;
g.DrawString(maxText, Font, Brushes.Black, rect.Left + maxValueX - maxTextSize.Width / 2, rect.Top);
DrawYArrow(g, Color.Gray, rect.Left + maxValueX, rect.Top + verticalMargin, 4);
// Отрисовываем текст минимального значения и стрелку
string minText = minValue.ToString();
SizeF minTextSize = g.MeasureString(minText, Font);
minTextSize = new SizeF(minTextSize.Width + 6, minTextSize.Height); // коррекция размера текста
int minValueX = (int)((_minValue - _minValue) / range * (rect.Width - 2 * horizontalMargin)) + horizontalMargin;
g.DrawString(minText, Font, Brushes.Black, rect.Left + minValueX - minTextSize.Width / 2, rect.Top);
DrawYArrow(g, Color.Gray, rect.Left + minValueX, rect.Top + verticalMargin, 4);
// Отрисовываем текст среднего значения и стрелку
string midText = middleValue.ToString();
SizeF midTextSize = g.MeasureString(midText, Font); // для среднего значения коррекция размера текста не требуется
int midValueX = (int)((_middleValue - _minValue) / range * (rect.Width - 2 * horizontalMargin)) + horizontalMargin;
g.DrawString(midText, Font, Brushes.Black, rect.Left + midValueX - midTextSize.Width / 2, rect.Top);
DrawXArrow(g, Color.Gray, rect.Left + midValueX, rect.Top + verticalMargin, rect.Left + midValueX, rect.Bottom - verticalMargin, 4);
// Отрисовываем текст текущего значения и стрелку
string valueText = value.ToString();
SizeF valueTextSize = g.MeasureString(valueText, Font);
if (_value >= _middleValue)
{
valueX = (int)((_value - _minValue) / range * (rect.Width - 2 * horizontalMargin)) + horizontalMargin;
}
else
{
valueX = (int)((_value - _minValue) / range * (rect.Width - 2 * horizontalMargin)) + horizontalMargin;
}
g.DrawString(valueText, Font, Brushes.Black, rect.Left + valueX - valueTextSize.Width / 2, rect.Bottom - valueTextSize.Height);
//DrawYArrow(g,Color.Red, rect.Left + valueX, rect.Bottom - verticalMargin, 4,true); // отрисовка после указания значения приводит к плохому совпадению
}
private float AsLog(float value)
{
if (value == 0)
{
return 0;
}
return (float)Math.Log(Math.Abs(value)) * (value > 0 ? 1 : -1);
}
}
``````csharp
/// <summary>
/// Draws a double arrow for the Y-axis.
/// </summary>
/// <param name="g"></param>
/// <param name="x1">the X-coordinate of the first point</param>
/// <param name="y1">the Y-coordinate of the first point</param>
/// <param name="size">arrow size</param>
private void DrawYArrow(Graphics g, Color color, int x1, int y1, int size, bool IsUp = false)
{
using (Pen pen = new Pen(color, 2))
{
if (!IsUp)
{
g.DrawLine(pen, x1, y1, x1 - size, y1 - size);
g.DrawLine(pen, x1, y1, x1 + size, y1 - size);
g.DrawLine(pen, x1, y1 - 4, x1 - size, y1 - size - 4);
g.DrawLine(pen, x1, y1 - 4, x1 + size, y1 - size - 4);
}
else
{
g.DrawLine(pen, x1, y1, x1 - size, y1 + size);
g.DrawLine(pen, x1, y1, x1 + size, y1 + size);
g.DrawLine(pen, x1, y1 + 4, x1 - size, y1 + size + 4);
g.DrawLine(pen, x1, y1 + 4, x1 + size, y1 + size + 4);
}
}
}
/// <summary>
/// Draws a double arrow for the X-axis.
/// </summary>
/// <param name="g"></param>
/// <param name="x1">the X-coordinate of the first point</param>
/// <param name="y1">the Y-coordinate of the first point</param>
/// <param name="x2">the X-coordinate of the second point</param>
/// <param name="y2">the Y-coordinate of the second point</param>
/// <param name="size">arrow size</param>
private void DrawXArrow(Graphics g, Color color, int x1, int y1, int x2, int y2, int size)
{
using (Pen pen = new Pen(color, 2))
{
g.DrawLine(pen, x1, y1, x1 - size, y1 - size);
g.DrawLine(pen, x1, y1, x1 + size, y1 - size);
g.DrawLine(pen, x1, y1, x2, y2);
g.DrawLine(pen, x2, y2, x2 - size, y2 + size);
g.DrawLine(pen, x2, y2, x2 + size, y2 + size);
}
}
}