Форум программистов, компьютерный форум, киберфорум
Наши страницы
ashsvis
Войти
Регистрация
Восстановить пароль
"Орешек знаний тверд, Но все же мы не привыкли отступать..." (с)
Оценить эту запись

Шаг второй (работа над ошибками)

Запись от ashsvis размещена 08.11.2018 в 10:37

Благодаря замечаниям в комментариях к статье от Storm23, я внёс необходимые исправления в код классов Stroke и Fill,
а также немного переработал код класса Drawing, который переименован в Figure.

Изменения в классе Stroke:

Оставлен один конструктор, без параметров, в котором создаётся закрытое поле Pen _pen, которое используется
в геттерах/сеттерах свойств.
C#
1
2
3
4
5
6
7
8
9
private readonly Pen _pen;
 
/// <summary>
/// Конструктор без параметров, со свойствами по умолчанию
/// </summary>
public Stroke()
{
    _pen = new Pen(Color.Black);
}
В класс добавлена поддержка интерфейса IDisposable, а также добавлен деструктор класса
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// <summary>
/// Деструктор объекта
/// </summary>
~Stroke()
{
    Dispose();
}
 
/// <summary>
/// Реализация интерфейса IDisposable
/// </summary>
public void Dispose()
{
    if (_pen != null) _pen.Dispose();
}
Свойства класса Stroke теперь выглядят так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/// <summary>
/// Цвет линии фигуры
/// </summary>
public Color Color
{
    get { return _pen.Color; }
    set { _pen.Color = value; }
}
        
/// <summary>
/// Ширина линии фигуры
/// </summary>
public Single Width
{
    get { return _pen.Width; }
    set { _pen.Width = value; }
}
        
/// <summary>
/// Стиль линии фигуры
/// </summary>
public DashStyle DashStyle
{
    get { return _pen.DashStyle; }
    set { _pen.DashStyle = value; }
}
        
/// <summary>
/// Яркость:
/// 0 - полностью прозрачный,
/// 255 - полноцветный
/// </summary>
public int Alpha
{
    get { return _pen.Color.A; }
    set { _pen.Color = Color.FromArgb(value, _pen.Color); }
}
        
/// <summary>
/// Стиль начала линии
/// </summary>
public LineCap StartCap
{
    get { return _pen.StartCap; }
    set { _pen.StartCap = value; }
}
        
/// <summary>
/// Стиль конца линии
/// </summary>
public LineCap EndCap
{
    get { return _pen.EndCap; }
    set { _pen.EndCap = value; }
}
        
/// <summary>
/// Стиль соединения двух отрезков линии
/// </summary>
public LineJoin LineJoin
{
    get { return _pen.LineJoin; }
    set { _pen.LineJoin = value; }
}
Метод Pen() заменён свойством для чтения GetPen и сильно упрощён:
C#
1
2
3
4
5
6
7
/// <summary>
/// Свойство возвращает "карандаш", настроенный по текущим свойствам stroke
/// </summary>
public Pen GetPen
{
    get { return _pen; }
}
Изменения в классе Fill:

Оставлен один конструктор, без параметров, в котором создаётся закрытое поле SolidBrush _brush, которое используется
в геттерах/сеттерах свойств.
C#
1
2
3
4
5
6
7
8
9
private readonly SolidBrush _brush;
 
/// <summary>
/// Конструктор без параметров, с цветом по умолчанию
/// </summary>
public Fill()
{
    _brush = new SolidBrush(Color.White);
}
В класс добавлена поддержка интерфейса IDisposable, а также добавлен деструктор класса
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// <summary>
/// Деструктор объекта
/// </summary>
~Fill()
{
    Dispose();
}
 
/// <summary>
/// Реализация интерфейса IDisposable
/// </summary>
public void Dispose()
{
    if (_brush != null) _brush.Dispose();
}
Свойства класса Fill теперь выглядят так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// <summary>
/// Цвет заливки
/// </summary>
public Color Color
{
    get { return _brush.Color; }
    set { _brush.Color = value; }
}
 
/// <summary>
/// Яркость:
/// 0 - полностью прозрачный,
/// 255 - полноцветный
/// </summary>
public int Alpha
{
    get { return _brush.Color.A; }
    set { _brush.Color = Color.FromArgb(value, _brush.Color); }
}
Я решил убрать поддержку других кистей, кроме SolidBrush. На данном этапе излишняя функциональность пока не нужна.

Метод Brush() заменён свойством для чтения GetBrush и сильно упрощён:
C#
1
2
3
4
5
6
7
8
/// <summary>
/// Свойство возвращает "кисть", настроенный по текущим свойствам fill
/// </summary>
/// <returns>Настроенная кисть для заполнения контура фигуры</returns>
public Brush GetBrush
{
    get { return _brush; }
}
Изменения в классе Figure (теперь будет вместо Drawing):

Оставлен один безпараметрический конструктор, в котором создаются два объекта-спутника:
C#
1
2
3
4
5
6
7
8
/// <summary>
///  Конструктор без параметра с настройкой по умолчанию
/// </summary>
protected Figure()
{
    Stroke = new Stroke();
    Fill = new Fill();
}
Изменён метод рисования, согласно изменениям в классах Stroke и Fill:
C#
1
2
3
4
5
6
7
8
9
10
11
12
/// <summary>
/// Метод рисования фигуры по точкам базового списка
/// </summary>
/// <param name="graphics">"холст" - объект для рисования</param>
public virtual void DrawFigure(Graphics graphics)
{
    var points = Points.ToArray();
    // заливаем фон кистью
    graphics.FillPolygon(Fill.GetBrush, points);
    // рисуем контур карандашом
    graphics.DrawPolygon(Stroke.GetPen, points);
}
Полный код классов Stroke, Fill и Figure здесь:
Кликните здесь для просмотра всего текста

Класс Stroke
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace SimpleVectorGraphicsEditor
{
    /// <summary>
    /// Описание класса хранения свойств для рисования контура фигуры
    /// </summary>
    [Serializable]
    public class Stroke: IDisposable
    {
        private readonly Pen _pen;
 
        /// <summary>
        /// Конструктор без параметров, со свойствами по умолчанию
        /// </summary>
        public Stroke()
        {
            _pen = new Pen(Color.Black);
        }
        
        /// <summary>
        /// Деструктор объекта
        /// </summary>
        ~Stroke()
        {
            Dispose();
        }
 
        /// <summary>
        /// Цвет линии фигуры
        /// </summary>
        public Color Color
        {
            get { return _pen.Color; }
            set { _pen.Color = value; }
        }
        
        /// <summary>
        /// Ширина линии фигуры
        /// </summary>
        public Single Width
        {
            get { return _pen.Width; }
            set { _pen.Width = value; }
        }
        
        /// <summary>
        /// Стиль линии фигуры
        /// </summary>
        public DashStyle DashStyle
        {
            get { return _pen.DashStyle; }
            set { _pen.DashStyle = value; }
        }
        
        /// <summary>
        /// Яркость:
        /// 0 - полностью прозрачный,
        /// 255 - полноцветный
        /// </summary>
        public int Alpha
        {
            get { return _pen.Color.A; }
            set { _pen.Color = Color.FromArgb(value, _pen.Color); }
        }
        
        /// <summary>
        /// Стиль начала линии
        /// </summary>
        public LineCap StartCap
        {
            get { return _pen.StartCap; }
            set { _pen.StartCap = value; }
        }
        
        /// <summary>
        /// Стиль конца линии
        /// </summary>
        public LineCap EndCap
        {
            get { return _pen.EndCap; }
            set { _pen.EndCap = value; }
        }
        
        /// <summary>
        /// Стиль соединения двух отрезков линии
        /// </summary>
        public LineJoin LineJoin
        {
            get { return _pen.LineJoin; }
            set { _pen.LineJoin = value; }
        }
        
        /// <summary>
        /// Свойство возвращает "карандаш", настроенный по текущим свойствам stroke
        /// </summary>
        public Pen GetPen
        {
            get { return _pen; }
        }
 
        /// <summary>
        /// Реализация интерфейса IDisposable
        /// </summary>
        public void Dispose()
        {
            if (_pen != null) _pen.Dispose();
        }
    }
}
Класс Fill
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
using System;
using System.Drawing;
 
namespace SimpleVectorGraphicsEditor
{
    /// <summary>
    /// Описание класса хранения свойств для закрашивания поверхности фигуры
    /// </summary>
    [Serializable]
    public class Fill : IDisposable
    {
        private readonly SolidBrush _brush;
 
        /// <summary>
        /// Конструктор без параметров, с цветом по умолчанию
        /// </summary>
        public Fill()
        {
            _brush = new SolidBrush(Color.White);
        }
 
        /// <summary>
        /// Деструктор объекта
        /// </summary>
        ~Fill()
        {
            Dispose();
        }
 
        /// <summary>
        /// Цвет заливки
        /// </summary>
        public Color Color
        {
            get { return _brush.Color; }
            set { _brush.Color = value; }
        }
 
        /// <summary>
        /// Яркость:
        /// 0 - полностью прозрачный,
        /// 255 - полноцветный
        /// </summary>
        public int Alpha
        {
            get { return _brush.Color.A; }
            set { _brush.Color = Color.FromArgb(value, _brush.Color); }
        }
 
        /// <summary>
        /// Свойство возвращает "кисть", настроенный по текущим свойствам fill
        /// </summary>
        /// <returns>Настроенная кисть для заполнения контура фигуры</returns>
        public Brush GetBrush
        {
            get { return _brush; }
        }
 
        /// <summary>
        /// Реализация интерфейса IDisposable
        /// </summary>
        public void Dispose()
        {
            if (_brush != null) _brush.Dispose();
        }
    }
}
Класс Figure
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace SimpleVectorGraphicsEditor
{
    public enum DrawingKind
    {
        Polygon,
        Polyline
    }
 
    /// <summary>
    /// Описание класса базовой фигуры 
    /// </summary>
    [Serializable]
    public abstract class Figure
    {
        // контейнер для хранения точек фигуры
        protected readonly List<PointF> Points = new List<PointF>();
 
        /// <summary>
        ///  Конструктор без параметра с настройкой по умолчанию
        /// </summary>
        protected Figure()
        {
            Stroke = new Stroke();
            Fill = new Fill();
        }
 
        /// <summary>
        /// Свойство определяющее вид фигуры
        /// </summary>
        public DrawingKind Kind { get; set; }
 
        /// <summary>
        /// Базовая точка, обычно левый верхний угол прямоугольника,
        /// описывающего фигуру
        /// </summary>
        public virtual PointF Location { get; set; }
 
        /// <summary>
        /// Размер фигуры, ширина и высота прямоугольника,
        /// описывающего фигуру
        /// </summary>
        public virtual SizeF Size { get; set; }
 
        /// <summary>
        /// Прямоугольник, описывающий фигуру
        /// </summary>
        public RectangleF BoundsRect
        {
            get
            {
                return new RectangleF(Location, Size);
            }
        }
 
        /// <summary>
        /// Карандаш для рисования контура фигуры
        /// </summary>
        public Stroke Stroke { get; set; }
 
        /// <summary>
        /// Кисть для заливки контура фигуры
        /// </summary>
        public Fill Fill { get; set; }
 
        /// <summary>
        /// Метод рисования фигуры по точкам базового списка
        /// </summary>
        /// <param name="graphics">"холст" - объект для рисования</param>
        public virtual void DrawFigure(Graphics graphics)
        {
            var points = Points.ToArray();
            // заливаем фон кистью
            graphics.FillPolygon(Fill.GetBrush, points);
            // рисуем контур карандашом
            graphics.DrawPolygon(Stroke.GetPen, points);
        }
 
        /// <summary>
        /// Метод проверяет принадлежность точки фигуре
        /// </summary>
        /// <param name="point">проверяемая точка</param>
        /// <returns>True - точка принадлежит фигуре</returns>
        public virtual Boolean PointInFigure(PointF point)
        {
            switch (Kind)
            {
                case DrawingKind.Polygon:
                    using (var gp = new GraphicsPath())
                    {
                        gp.AddPolygon(GetPoints());
                        return (gp.IsVisible(point));
                    }
                case DrawingKind.Polyline:
                    using (var gp = new GraphicsPath())
                    {
                        var ps = GetPoints();
                        for (var i = 1; i < ps.Length; i++)
                        {
                            gp.StartFigure();
                            gp.AddPolygon(Onebuff(ps[i - 1], ps[i]));
                            gp.CloseFigure();
                        }
                        return (gp.IsVisible(point));
                    }
            }
            return false;
        }
 
        /// <summary>
        /// Вспомогательный метод, строит прямоугольник на перпендикулярах
        /// на концах отрезка между двух точек
        /// </summary>
        /// <param name="p1">первая точка</param>
        /// <param name="p2">вторая точка</param>
        /// <returns>массив точек четырёхугольника, охватывающего отрезок</returns>
        private PointF[] Onebuff(PointF p1, PointF p2)
        {
            var selWidth = Stroke.Width * 0.5F;
            if ((selWidth < 4F) || (Stroke.DashStyle == DashStyle.Custom)) selWidth = 4F;
            var x1 = p1.X;
            var y1 = p1.Y;
            var x2 = p2.X;
            var y2 = p2.Y;
            var k = (y2 - y1) / (x2 - x1);
            var fi1 = (Single)(Math.Atan(k) + Math.PI * 0.5);
            var fi2 = (Single)(Math.Atan(k) - Math.PI * 0.5);
            var buff = new PointF[4];
            buff[0].X = (Single)Math.Round(selWidth * Math.Cos(fi1) + x1);
            buff[0].Y = (Single)Math.Round(selWidth * Math.Sin(fi1) + y1);
            buff[1].X = (Single)Math.Round(selWidth * Math.Cos(fi2) + x1);
            buff[1].Y = (Single)Math.Round(selWidth * Math.Sin(fi2) + y1);
            buff[2].X = (Single)Math.Round(selWidth * Math.Cos(fi2) + x2);
            buff[2].Y = (Single)Math.Round(selWidth * Math.Sin(fi2) + y2);
            buff[3].X = (Single)Math.Round(selWidth * Math.Cos(fi1) + x2);
            buff[3].Y = (Single)Math.Round(selWidth * Math.Sin(fi1) + y2);
            return (buff);
        }
 
        /// <summary>
        /// Свойство возвращает реальный прямоугольник, охватывающий точки фигуры
        /// </summary>
        public virtual RectangleF GetBounds
        {
            get
            {
                using (var gp = new GraphicsPath())
                {
                    gp.AddPolygon(GetPoints());
                    var rect = gp.GetBounds();
                    // если фигура очень узкая по горизонтали
                    if (Math.Abs(rect.Width - 0) < float.Epsilon)
                    {
                        rect.X -= 2; 
                        rect.Width += 4;
                    }
                    // если фигура очень узкая по вертикали
                    if (Math.Abs(rect.Height - 0) < float.Epsilon)
                    {
                        rect.Y -= 2;
                        rect.Height += 4;
                    }
                    return (rect);
                }
            }
        }
 
        /// <summary>
        /// Метод возвращает массив точек фигуры
        /// </summary>
        /// <returns>Массив, копия списка точек фигуры</returns>
        public virtual PointF[] GetPoints()
        {   
            // возвращает массив точек линии
            var ps = new PointF[Points.Count];
            Points.CopyTo(ps);
            return (ps);
        }
    }
}

На следующем шаге я планировал описать класс DrawingEditor, который всё таки будет назван Picture,
в котором будут применены все наши наработки на этот момент и который будет управлять размещением и размерами потомков базового класса Figure.

Будем, наконец, двигать фигурами и изменять их размер мышкой при помощи маркеров.
Всего комментариев 2
Комментарии
  1. Старый комментарий
    Аватар для Storm23
    Ну и видно, что Stroke и Fill просто переприсваивают свойства для Pen и Brush. То есть ничего не делают. Они не нужны.

    И что это за извращение с Onebuff? Это вообще зачем?
    У GraphicsPath (который кстати я предлагал сделать внешним свойством и рисовать именно его, а не поточечно) есть метод IsOutlibeVisible который может проверять как точки лежащие внутри замкнутой фигуры, так и точки находящиеся на границе фигуры или линии.


    Да и еще, зачем же свойства называть GetPen? Это просто Pen.
    Это методы должны быть с глаголом, а свойства - не обязательно.


    А вообще, заметьте, вы сделали три класса. Но они почти ничего не делают. Вот увидите, после рефакторинга, все что останется от ваших классов будет что-то типа:
    C#
    1
    2
    3
    4
    5
    
    abstract class Polygon
    {
        public List<PointF> Points {get;}
        public abstract GraphicsPath {get;}
    }
    И все. Потому что это реально что делают ваши классы - хранят точки. Они больше ничего не делают, и не нужно им так много кода.
    А вы пишите уже третью статью. Хотя могли просто написать четыре строчки выше и пойти дальше.
    KISS
    Запись от Storm23 размещена 08.11.2018 в 18:19 Storm23 на форуме
    Обновил(-а) Storm23 08.11.2018 в 18:28
  2. Старый комментарий
    Аватар для ashsvis
    Цитата:
    Stroke и Fill ... ничего не делают. Они не нужны
    Ну это сейчас они ничего не делают, а когда потребуется применить более "мощные" настройки, например, для кисти.
    Ведь благодаря упрощению я уже потерял часть функционала с градиентной заливкой. Для градиентной кисти используется
    другой класс, не SolidBrush, а в объекте Fill я мог бы их совместить.
    Цитата:
    что это за извращение с Onebuff?
    Мдя, смотрится довольно коряво, это мой метод попадания курсора на тонкую линию...

    Да, будем исправлять. Большое спасибо за критический взгляд со стороны! Это поможет мне сделать продукт качественнее.
    Запись от ashsvis размещена 08.11.2018 в 18:36 ashsvis вне форума
    Обновил(-а) ashsvis 08.11.2018 в 18:38
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru