IT/C#

[C#] 윈도우 그림판 기능 추가해보기 (PPT처럼 도형과 도형을 직선으로 잇기)

Ella.J 2021. 3. 31. 19:15
728x90
반응형

어떤 분이 댓글로 도형과 도형을 클릭하면 직선으로 선이 이어지게 되는 것을 어떻게 구현하면 될지 질문해 주셨다.
그래서 도전해봤다.

컨셉으로 생각한 것은 다음과 같다.
PPT처럼 도형 위에 마우스를 가져가면 도형의 각 테두리 4개의 포인트가 표시가 되고,
마우스 클릭을 통해 두 개의 도형을 직선으로 이으면 될 것이라고 생각했다.

(이전 글에 이어서 구현했기 때문에, 아래 첨부된 코드에서 중략된 부분 및 전체 코드는 이전 글을 참고해 주세요.)

2020.10.16 - [IT] - [C#] 윈도우 그림판 기능 구현해보기 (PictureBox 그리기 기능, Undo/Redo 기능, 단축키 사용 방법)

[C#] 윈도우 그림판 기능 구현해보기 (PictureBox 그리기 기능, Undo/Redo 기능, 단축키 사용 방법)

그림판에 여러 기능들 중에서 크게 두 가지 기능을 구현해보겠습니다. 1. 도형(사각형, 원형, 선형직선) 그리기 기능과 2. 실행 취소(Undo)/다시 실행(Redo) 기능 두 가지 입니다. 그리고 흔히들 사용

ella-devblog.tistory.com

그림판 기능 미리보기
아래의 동영상을 통해 우리가 구현한 코드가 어떻게 동작하는지 확인해 보자.
(윈도우 자체 동영상 녹화 기능에서 콤보박스가 1도 안 나와서 어떻게 바꾸는지는 모르겠지만,,,
일단 동영상을 봐주시면 감사하겠습니다.)

 

 

대략적인 컨셉을 그려봤습니다 : )

 

먼저, 마우스가 움직일 때 (MouseMove 이벤트) 마우스 위치에 있는 도형을 찾는 작업을 했다.
이전 글에서 도형(사각형, 원형 등)을 그릴 때 listRect에 저장하게 되어있었기 때문에,
마우스 위치를 확인해서 어떤 Rectangle 라인 위에 있는지 확인하고 listRect에서 몇 번째 도형인지 찾는다.
그러기 위해서 전역 변수로 위 사진과 같이 rectNum을 선언해서 사용했다.

(-1을 Default로 선언. 0은 array에서 사용하기 때문.)
FindRectangle 함수에서 rectNum을 찾고, 찾은 도형에 4개의 포인트를 표시하고 마우스 커서를 변경해주는 작업을 했다.
굳이 표시 안 하고, 마우스 커서도 그대로 쓰고 싶다면 rectNum만 찾고 다음 단계로 넘어가도 된다.
그리고, 직선 그리기 일 때만 도형과 도형을 잇기 위해서 toolType 이 DrawLine일 경우에만 도형을 찾게 했다.

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
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        //...중략
    }
 
    if (toolType == PaintTools.DrawLine) //마우스 위치에 있는 Rectangle 도형 파악하기
    {
        //마우스 위치의 도형 리스트에서 찾기
        rectNum = FindRectangle(listRect, new Point(e.X, e.Y));
        if (rectNum != -1)
        {
            //도형 Edge 4개 포인트 표시
            DrawEdgeRectangle(listRect[rectNum], listTool[rectNum]);
            //마우스 위치 확인하기 (도형 위에 있는지)
            UpdateMouseEdgeProperties(listRect[rectNum], new Point(e.X, e.Y));
            //마우스 커서 변경하기
            UpdateMouseCursor(pictureBox1);
        } else
        {
            pictureBox1.Refresh();
            DrawBitmap();
        }
    } else
    {
        //이 외의 경우 Default 값 -1로 표시
        rectNum = -1;
    }
}
 
private int FindRectangle(List<Rectangle> r, Point e)
{
    for (int i = 0; i < r.Count; i++)
    {
        if (e.X > (r[i].X - 7&& e.X < (r[i].X + r[i].Width + 7))
        {
            if (e.Y > (r[i].Y - 7&& e.Y < (r[i].Y + r[i].Height + 7))
            {
                //마우스가 i번째 도형 위에 위치함
                return i;
            }
        }
    }
    //마우스가 어느 도형 위에도 위치하지 않음
    return -1;
}
 
 
cs

각각의 함수는 주석을 참고해 주세요 : )

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
//마우스 위치를 파악하기 위한 boolean 변수
public static bool MouseIsInLeftCenter { get; set; }
public static bool MouseIsInRightCenter { get; set; }
public static bool MouseIsInTopCenter { get; set; }
public static bool MouseIsInBottomCenter { get; set; }
 
private void DrawEdgeRectangle(Rectangle r, PaintTools t)
{
    //마우스가 도형 위에 위치하는 경우 도형 4면 Edge에 표시
    Pen pnP = new Pen(Color.Red);
    pnP.DashStyle = DashStyle.Solid;
    Graphics g = pictureBox1.CreateGraphics();
    g.DrawRectangle(pnP, r.X - 2, r.Y - 2 + r.Height / 244);
    g.DrawRectangle(pnP, r.X - 2 + r.Width / 2, r.Y - 244);
    g.DrawRectangle(pnP, r.X - 2 + r.Width / 2, r.Y - 2 + r.Height, 44);
    g.DrawRectangle(pnP, r.X - 2 + r.Width, r.Y - 2 + r.Height / 244);
}
 
private static void UpdateMouseEdgeProperties(Rectangle r, Point e)
{
    //마우스가 도형 위의 어느 포인트에 위치하는지 파악
    MouseIsInLeftCenter = (Math.Abs(e.X - r.X) <= 7&& (e.Y > r.Y) && (e.Y < r.Y + r.Height);
    MouseIsInRightCenter = (Math.Abs(e.X - r.X - r.Width) <= 7&& (e.Y > r.Y) && (e.Y < r.Y + r.Height);
    MouseIsInTopCenter = (Math.Abs(e.Y - r.Y) <= 7&& (e.X > r.X) && (e.X < r.X + r.Width);
    MouseIsInBottomCenter = (Math.Abs(e.Y - r.Y - r.Height) <= 7&& (e.X > r.X) && (e.X < r.X + r.Width);
}
 
private static void UpdateMouseCursor(Control control)
{
    //만약 도형위에 마우스가 위치하면 마우스 커서를 십자가 모양으로 변경
    if (MouseIsInLeftCenter || MouseIsInRightCenter || MouseIsInTopCenter || MouseIsInBottomCenter)
    {
        control.Cursor = Cursors.Cross;
    }
    else
    {
        control.Cursor = Cursors.Default;
    }
}
cs

그런 다음 도형과 도형을 직선으로 잇자.
먼저, MouseDown 이벤트에서 처음 선택한 도형의 포인트를 찾는다.
도형 위에서 클릭할 때만 처음 clickPoint를 변경하기 위해 rectNum이 -1이 아닐 때를 찾는다.
FindPoint에서 도형의 4개 포인트 중 현재 마우스 위치와 가장 가까운 포인트를 찾는다.
그리고 마우스를 다음 도형에서 떼는데, MouseUp 이벤트에서 처음 위치 찾듯이 마지막 도형 포인트를 찾는다.
그러면 위의 동영상과 같이 도형과 도형을 잇는 직선이 그려진다.

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
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        //마우스 클릭 위치 저장
        clickPoint = new Point(e.X, e.Y);
        first = true;
        //마우스 도형 클릭 시 도형 위 4개의 포인트로 clickPoint 변경
        if (rectNum != -1)
        {
            clickPoint = FindPoint(listRect[rectNum], clickPoint);
        }
    }
}
 
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        //...중략
        if (toolType == PaintTools.DrawRectangle)
        {
            //...중략
        }
        else if (toolType == PaintTools.DrawLine) //선형 그리기
        {
            Point lastPoint = new Point();
            //lastPoint도 clickPoint와 마찬가지로 도형 클릭 시 도형 위 4개의 포인트로 변경
            if (rectNum != -1)
            {
                lastPoint = FindPoint(listRect[rectNum], new Point(e.X, e.Y));
                rect = new Rectangle(clickPoint.X, clickPoint.Y, clickPoint.X + lastPoint.X, clickPoint.Y + lastPoint.Y);
            }
            else
            {
                rect = new Rectangle(clickPoint.X, clickPoint.Y, clickPoint.X + e.X, clickPoint.Y + e.Y);
            }
        }
 
        //...중략
    }
}
 
private Point FindPoint(Rectangle r, Point e)
{
    //마우스가 도형 위 4개 포인트에서 제일 가까운 도형 위 포인트 리턴
    Point rectPoint = new Point();
    rectPoint.X = Math.Abs(e.X - r.X) > Math.Abs(e.X - (r.X + r.Width / 2)) ? r.X + r.Width / 2 : r.X;
    if (Math.Abs(e.X - r.X) < Math.Abs(e.X - (r.X + r.Width / 2))) rectPoint.X = r.X;
    else if (Math.Abs(e.X - (r.X + r.Width / 2)) < Math.Abs(e.X - (r.X + r.Width))) rectPoint.X = r.X + r.Width / 2;
    else rectPoint.X = r.X + r.Width;
    if (Math.Abs(e.Y - r.Y) < Math.Abs(e.Y - (r.Y + r.Height / 2))) rectPoint.Y = r.Y;
    else if (Math.Abs(e.Y - (r.Y + r.Height / 2)) < Math.Abs(e.Y - (r.Y + r.Height))) rectPoint.Y = r.Y + r.Height / 2;
    else rectPoint.Y = r.Y + r.Height;
    return rectPoint;
}
cs

 

 급하게 포스팅 올리느라 설명이 충분하지 않을 수 있습니다,,ㅎㅎ 궁금하신 점은 댓글로 마구 질문해주세요 : )
그럼 도움이 되셨다면 공감 꾹 눌러주시면 감사하겠습니다❣️

728x90
반응형