첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
유용한 소스 코드가 있으면 icodebroker@naver.com으로 보내주시면 감사합니다.
블로그 자료는 자유롭게 사용하세요.

■ Canvas 클래스 : 사용자 선 그리기

------------------------------------------------------------------------------------------------------------------------


TestProject.zip



MainWindow.xaml

 

 

<Window x:Class="TestProject.MainWindow"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Width="800"

    Height="600"

    Title="Canvas 클래스 : 사용자 선 그리기"

    Loaded="Window_Loaded">

    <DockPanel>

        <Menu DockPanel.Dock="Top"

            FontFamily="나눔고딕코딩"

            FontSize="16">

            <MenuItem Header="_File">

                <MenuItem Header="_Open" Click="openMenuItem_Click" />

                <MenuItem Header="_New"  Click="newMenuItem_Click"  />

                <MenuItem Header="_Save" Click="saveMenuItem_Click" />

            </MenuItem>

        </Menu>

        <Grid>

            <Canvas Name="canvas">

                <Image Name="trashImage" Canvas.Left="0" Canvas.Top="0"

                    Stretch="Uniform"

                    Width="32"

                    Height="32"

                    Source="IMAGE/trash_empty.png" />

            </Canvas>

        </Grid>

    </DockPanel>

</Window>

 

 

MainWindow.xaml.cs

 

 

using Microsoft.Win32;

using System;

using System.Collections.Generic;

using System.IO;

using System.Windows;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Shapes;

using System.Xml.Serialization;

 

namespace TestProject

{

    /// <summary>

    /// 메인 윈도우

    /// </summary>

    public partial class MainWindow : Window

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// 객체 반경

        /// </summary>

        private const int OBJECT_RADIUS = 3;

 

        /// <summary>

        /// 거리 제곱

        /// </summary>

        private const int DISTANCE_SQUARED = OBJECT_RADIUS * OBJECT_RADIUS;

 

        /// <summary>

        /// 선택한 라인

        /// </summary>

        private Line selectedLine;

 

        /// <summary>

        /// 시작/종료 점 이동 여부

        /// </summary>

        private bool movingStartEndPoint = false;

 

        /// <summary>

        /// 오프셋 X

        /// </summary>

        private double offsetX;

        

        /// <summary>

        /// 오프셋 Y

        /// </summary>

        private double offsetY;

 

        /// <summary>

        /// 쓰레기통 너비

        /// </summary>

        private double trashWidth;

        

        /// <summary>

        /// 쓰레기통 높이

        /// </summary>

        private double trashHeight;

 

        /// <summary>

        /// 이동 상태

        /// </summary>

        private MoveStatus moveStatus = MoveStatus.NotDown;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - MainWindow()

 

        /// <summary>

        /// 생성자

        /// </summary>

        public MainWindow()

        {

            InitializeComponent();

 

            this.canvas.MouseDown += canvas_MouseDown;

            this.canvas.MouseMove += canvas_MouseMove;

            this.canvas.MouseUp   += canvas_MouseUp;

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        //////////////////////////////////////////////////////////////////////////////// Event

 

        #region 윈도우 로드시 처리하기 - Window_Loaded(sender, e)

 

        /// <summary>

        /// 윈도우 로드시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            this.trashWidth  = this.trashImage.ActualWidth;

            this.trashHeight = this.trashImage.ActualHeight;

 

            this.canvas.Background = Brushes.White;

        }

 

        #endregion

        #region Open 메뉴 항목 클릭시 처리하기 - openMenuItem_Click(sender, e)

 

        /// <summary>

        /// Open 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void openMenuItem_Click(object sender, RoutedEventArgs e)

        {

            OpenFileDialog dialog = new OpenFileDialog();

 

            dialog.DefaultExt = ".xml";

            dialog.Filter     = "XML (*.xml)|*.xml|All Files (*.*)|*.*";

 

            Nullable<bool> result = dialog.ShowDialog();

 

            if(result.Value != true)

            {

                return;

            }

 

            ClearCanvas();

 

            XmlSerializer serializer = new XmlSerializer(typeof(List<Segment>));

 

            using(FileStream stream = File.Open(dialog.FileName, FileMode.Open))

            {

                List<Segment> segmentList = (List<Segment>)serializer.Deserialize(stream);

 

                foreach(Segment segment in segmentList)

                {

                    Line line = new Line();

 

                    line.X1     = segment.X1;

                    line.Y1     = segment.Y1;

                    line.X2     = segment.X2;

                    line.Y2     = segment.Y2;

                    line.Stroke = Brushes.Black;

 

                    this.canvas.Children.Add(line);

                }

            }

        }

 

        #endregion

        #region New 메뉴 항목 클릭시 처리하기 - newMenuItem_Click(sender, e)

 

        /// <summary>

        /// New 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void newMenuItem_Click(object sender, RoutedEventArgs e)

        {

            ClearCanvas();

        }

 

        #endregion

        #region Save 메뉴 항목 클릭시 처리하기 - saveMenuItem_Click(sender, e)

 

        /// <summary>

        /// Save 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void saveMenuItem_Click(object sender, RoutedEventArgs e)

        {

            SaveFileDialog dialog = new SaveFileDialog();

 

            dialog.DefaultExt = ".xml";

            dialog.Filter     = "XML (*.xml)|*.xml|All Files (*.*)|*.*";

 

            Nullable<bool> result = dialog.ShowDialog();

 

            if(result.Value != true)

            {

                return;

            }

 

            List<Segment> segmentList = new List<Segment>();

 

            foreach(object child in canvas.Children)

            {

                if(child is Line)

                {

                    Line line = (Line)child;

 

                    segmentList.Add(new Segment(line.X1, line.Y1, line.X2, line.Y2));

                }

            }

 

            XmlSerializer serializer = new XmlSerializer(typeof(List<Segment>));

 

            using(FileStream stream = File.Create(dialog.FileName))

            {

                serializer.Serialize(stream, segmentList);

            }

        }

 

        #endregion

        #region 캔버스 마우스 DOWN 처리하기 - canvas_MouseDown(sender, e)

 

        /// <summary>

        /// 캔버스 마우스 DOWN 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void canvas_MouseDown(object sender, MouseButtonEventArgs e)

        {

            Point mousePoint = e.MouseDevice.GetPosition(this.canvas);

 

            if(IsMouseOverEndpoint(mousePoint, out this.selectedLine, out this.movingStartEndPoint))

            {

                this.moveStatus = MoveStatus.MovingEndPoint;

 

                Point hitLinePoint;

 

                if(this.movingStartEndPoint)

                {

                    hitLinePoint = new Point(this.selectedLine.X1, this.selectedLine.Y1);

                }

                else

                {

                    hitLinePoint = new Point(this.selectedLine.X2, this.selectedLine.Y2);

                }

 

                this.offsetX = hitLinePoint.X - mousePoint.X;

                this.offsetY = hitLinePoint.Y - mousePoint.Y;

            }

            else if(IsMouseOverLine(mousePoint, out this.selectedLine))

            {

                this.moveStatus = MoveStatus.MovingSegment;

 

                this.offsetX = this.selectedLine.X1 - mousePoint.X;

                this.offsetY = this.selectedLine.Y1 - mousePoint.Y;

            }

            else

            {

                this.moveStatus = MoveStatus.Drawing;

 

                this.selectedLine = new Line();

 

                this.selectedLine.Stroke = Brushes.Red;

                this.selectedLine.X1     = mousePoint.X;

                this.selectedLine.Y1     = mousePoint.Y;

                this.selectedLine.X2     = mousePoint.X;

                this.selectedLine.Y2     = mousePoint.Y;

 

                this.canvas.Children.Add(this.selectedLine);

            }

        }

 

        #endregion

        #region 캔버스 마우스 이동시 처리하기 - canvas_MouseMove(sender, e)

 

        /// <summary>

        /// 캔버스 마우스 이동시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void canvas_MouseMove(object sender, MouseEventArgs e)

        {

            Point mousePoint = e.MouseDevice.GetPosition(this.canvas);

 

            if(this.moveStatus == MoveStatus.NotDown)

            {

                Cursor cursor = Cursors.Cross;

 

                if(IsMouseOverEndpoint(mousePoint, out this.selectedLine, out this.movingStartEndPoint))

                {

                    cursor = Cursors.Arrow;

                }

                else if(IsMouseOverLine(mousePoint, out this.selectedLine))

                {

                    cursor = Cursors.Hand;

                }

 

                if(this.canvas.Cursor != cursor)

                {

                    this.canvas.Cursor = cursor;

                }

            }

            else if(this.moveStatus == MoveStatus.MovingEndPoint)

            {

                if(this.movingStartEndPoint)

                {

                    this.selectedLine.X1 = mousePoint.X + this.offsetX;

                    this.selectedLine.Y1 = mousePoint.Y + this.offsetY;

                }

                else

                {

                    this.selectedLine.X2 = mousePoint.X + this.offsetX;

                    this.selectedLine.Y2 = mousePoint.Y + this.offsetY;

                }

            }

            else if(this.moveStatus == MoveStatus.Drawing)

            {

                this.selectedLine.X2 = mousePoint.X;

                this.selectedLine.Y2 = mousePoint.Y;

            }

            else if(this.moveStatus == MoveStatus.MovingSegment)

            {

                double newX1 = mousePoint.X + this.offsetX;

                double newY1 = mousePoint.Y + this.offsetY;

 

                double deltaX = newX1 - this.selectedLine.X1;

                double deltaY = newY1 - this.selectedLine.Y1;

 

                this.selectedLine.X1 = newX1;

                this.selectedLine.Y1 = newY1;

 

                this.selectedLine.X2 += deltaX;

                this.selectedLine.Y2 += deltaY;

            }

        }

 

        #endregion

        #region 캔버스 마우스 UP 처리하기 - canvas_MouseUp(sender, e)

 

        /// <summary>

        /// 캔버스 마우스 UP 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void canvas_MouseUp(object sender, MouseEventArgs e)

        {

            if(this.moveStatus == MoveStatus.Drawing)

            {

                this.selectedLine.Stroke = Brushes.Black;

 

                if((this.selectedLine.X1 == this.selectedLine.X2) && (this.selectedLine.Y1 == this.selectedLine.Y2))

                {

                    this.canvas.Children.Remove(this.selectedLine);

                }

            }

            else if(this.moveStatus == MoveStatus.MovingSegment)

            {

                Point mousePoint = e.MouseDevice.GetPosition(canvas);

 

                if((mousePoint.X >= 0) && (mousePoint.X < this.trashWidth) && (mousePoint.Y >= 0) && (mousePoint.Y < this.trashHeight))

                {

                    if(MessageBox.Show("Delete this segment?", "Delete Segment?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)

                    {

                        this.canvas.Children.Remove(this.selectedLine);

                    }

                }

            }

 

            this.moveStatus = MoveStatus.NotDown;

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////// Function

 

        #region 캔버스 지우기 - ClearCanvas()

 

        /// <summary>

        /// 캔버스 지우기

        /// </summary>

        private void ClearCanvas()

        {

            for(int i = this.canvas.Children.Count - 1; i >= 0; i--)

            {

                if(this.canvas.Children[i] is Line)

                {

                    this.canvas.Children.RemoveAt(i);

                }

            }

        }

 

        #endregion

        #region 특정 포인트까지 제곱 거리 찾기 - FindDistanceSquaredToPoint(point1, point2)

 

        /// <summary>

        /// 특정 포인트까지 제곱 거리 찾기

        /// </summary>

        /// <param name="point1">포인트 1</param>

        /// <param name="point2">포인트 2</param>

        /// <returns>특정 포인트까지 제곱 거리</returns>

        private double FindDistanceSquaredToPoint(Point point1, Point point2)

        {

            double dx = point1.X - point2.X;

            double dy = point1.Y - point2.Y;

 

            return dx * dx + dy * dy;

        }

 

        #endregion

        #region 특정 세그먼트까지 제곱 거리 찾기 - FindDistanceSquaredToSegment(point, point1, point2, closestPoint)

 

        /// <summary>

        /// 특정 세그먼트까지 제곱 거리 찾기

        /// </summary>

        /// <param name="point">포인트</param>

        /// <param name="startPoint">시작 포인트</param>

        /// <param name="endPoint">종료 포인트</param>

        /// <param name="closestPoint">최근접 포인트</param>

        /// <returns>특정 세그먼트까지 제곱 거리</returns>

        private double FindDistanceSquaredToSegment(Point point, Point startPoint, Point endPoint, out Point closestPoint)

        {

            double deltaX = endPoint.X - startPoint.X;

            double deltaY = endPoint.Y - startPoint.Y;

 

            if((deltaX == 0) && (deltaY == 0))

            {

                closestPoint = startPoint;

 

                deltaX = point.X - startPoint.X;

                deltaY = point.Y - startPoint.Y;

 

                return deltaX * deltaX + deltaY * deltaY;

            }

 

            double temporary = ((point.X - startPoint.X) * deltaX + (point.Y - startPoint.Y) * deltaY) / (deltaX * deltaX + deltaY * deltaY);

 

            if(temporary < 0)

            {

                closestPoint = new Point(startPoint.X, startPoint.Y);

 

                deltaX = point.X - startPoint.X;

                deltaY = point.Y - startPoint.Y;

            }

            else if (temporary > 1)

            {

                closestPoint = new Point(endPoint.X, endPoint.Y);

 

                deltaX = point.X - endPoint.X;

                deltaY = point.Y - endPoint.Y;

            }

            else

            {

                closestPoint = new Point(startPoint.X + temporary * deltaX, startPoint.Y + temporary * deltaY);

 

                deltaX = point.X - closestPoint.X;

                deltaY = point.Y - closestPoint.Y;

            }

 

            return deltaX * deltaX + deltaY * deltaY;

        }

 

        #endregion

        #region 마우스 종점 상위 여부 구하기 - IsMouseOverEndpoint(mousePoint, hitLine, isStartPoint)

 

        /// <summary>

        /// 마우스 종점 상위 여부 구하기

        /// </summary>

        /// <param name="mousePoint">마우스 포인트</param>

        /// <param name="hitLine">히트 라인</param>

        /// <param name="isStartPoint">시작점 여부</param>

        /// <returns>마우스 종점 상위 여부</returns>

        private bool IsMouseOverEndpoint(Point mousePoint, out Line hitLine, out bool isStartPoint)

        {

            foreach(object child in canvas.Children)

            {

                if(child is Line)

                {

                    Line line = child as Line;

 

                    Point point = new Point(line.X1, line.Y1);

 

                    if(FindDistanceSquaredToPoint(mousePoint, point) < DISTANCE_SQUARED)

                    {

                        hitLine = line;

 

                        isStartPoint = true;

 

                        return true;

                    }

 

                    point = new Point(line.X2, line.Y2);

 

                    if(FindDistanceSquaredToPoint(mousePoint, point) < DISTANCE_SQUARED)

                    {

                        hitLine = line;

 

                        isStartPoint = false;

 

                        return true;

                    }

                }

            }

 

            hitLine = null;

 

            isStartPoint = false;

 

            return false;

        }

 

        #endregion

        #region 마우스 라인 상위 여부 구하기 - IsMouseOverLine(mousePoint, hitLine)

 

        /// <summary>

        /// 마우스 라인 상위 여부 구하기

        /// </summary>

        /// <param name="mousePoint">마우스 포인트</param>

        /// <param name="hitLine">히트 라인</param>

        /// <returns>마우스 라인 상위 여부</returns>

        private bool IsMouseOverLine(Point mousePoint, out Line hitLine)

        {

            foreach(object child in canvas.Children)

            {

                if(child is Line)

                {

                    Line line = child as Line;

 

                    Point lineClosestPoint;

                    Point lineStartPoint = new Point(line.X1, line.Y1);

                    Point lineEndPoint   = new Point(line.X2, line.Y2);

 

                    if(FindDistanceSquaredToSegment(mousePoint, lineStartPoint, lineEndPoint, out lineClosestPoint) < DISTANCE_SQUARED)

                    {

                        hitLine = line;

 

                        return true;

                    }

                }

            }

 

            hitLine = null;

 

            return false;

        }

 

        #endregion

    }

}

 

------------------------------------------------------------------------------------------------------------------------

Posted by 사용자 icodebroker

댓글을 달아 주세요