첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

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>

 

728x90

 

▶ 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
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요