■ 제네릭 타입 트리 사용하기

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


TestProject.zip


ITreeNode.cs

 

 

using System.Drawing;

 

namespace TestProject

{

    /// <summary>

    /// 트리 노드 인터페이스

    /// </summary>

    public interface ITreeNode

    {

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

 

        #region 크기 구하기 - GetSize(graphics, font)

 

        /// <summary>

        /// 크기 구하기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        /// <param name="font">폰트</param>

        /// <returns>크기</returns>

        SizeF GetSize(Graphics graphics, Font font);

 

        #endregion

        #region 포인트 위치 여부 구하기 - IsAtPoint(graphics, font, centerPoint, targetPoint)

 

        /// <summary>

        /// 포인트 위치 여부 구하기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        /// <param name="font">폰트</param>

        /// <param name="centerPoint">중심 포인트</param>

        /// <param name="targetPoint">타겟 포인트</param>

        /// <returns>포인트 위치 여부</returns>

        bool IsAtPoint(Graphics graphics, Font font, PointF centerPoint, PointF targetPoint);

 

        #endregion

        #region 그리기 - Draw(x, y, graphics, pen, backgroundBrush, textBrush, font)

 

        /// <summary>

        /// 그리기

        /// </summary>

        /// <param name="x">X 좌표</param>

        /// <param name="y">Y 좌표</param>

        /// <param name="graphics">그래픽스</param>

        /// <param name="pen"></param>

        /// <param name="backgroundBrush">배경 브러시</param>

        /// <param name="textBrush">텍스트 브러시</param>

        /// <param name="font">폰트</param>

        void Draw(float x, float y, Graphics graphics, Pen pen, Brush backgroundBrush, Brush textBrush, Font font);

 

        #endregion

    }

}

 

 

TreeNode.cs

 

 

using System.Collections.Generic;

using System.Drawing;

 

namespace TestProject

{

    /// <summary>

    /// 트리 노드

    /// </summary>

    /// <typeparam name="TData">데이터 타입</typeparam>

    public class TreeNode<TData> where TData : ITreeNode

    {

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

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

 

        #region Field

 

        /// <summary>

        /// 데이터

        /// </summary>

        public TData Data;

 

        /// <summary>

        /// 자식 노드 리스트

        /// </summary>

        public List<TreeNode<TData>> ChildNodeList = new List<TreeNode<TData>>();

 

        /// <summary>

        /// 폰트

        /// </summary>

        public Font Font = null;

 

        /// <summary>

        ///

        /// </summary>

        public Pen Pen = Pens.Black;

 

        /// <summary>

        /// 폰트 브러시

        /// </summary>

        public Brush FontBrush = Brushes.Black;

 

        /// <summary>

        /// 배경 브러시

        /// </summary>

        public Brush BackgroundBrush = Brushes.White;

 

        /// <summary>

        /// 중심 포인트

        /// </summary>

        public PointF CenterPoint;

 

        #endregion

 

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

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

 

        #region Field

 

        /// <summary>

        /// 수평 오프셋

        /// </summary>

        private const float HORIZONTAL_OFFSET = 30;

 

        /// <summary>

        /// 수직 오프셋

        /// </summary>

        private const float VERTICAL_OFFSET = 20;

 

        #endregion

 

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

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

 

        #region 생성자 - TreeNode(data, font)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="data">데이터</param>

        /// <param name="font">폰트</param>

        public TreeNode(TData data, Font font)

        {

            Data = data;

            Font = font;

        }

 

        #endregion

        #region 생성자 - TreeNode(data)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="data">데이터</param>

        public TreeNode(TData data) : this(data, new Font("나눔고딕코딩", 12))

        {

            Data = data;

        }

 

        #endregion

 

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

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

 

        #region 자식 추가하기 - AddChild(childNode)

 

        /// <summary>

        /// 자식 추가하기

        /// </summary>

        /// <param name="childNode">자식 노드</param>

        public void AddChild(TreeNode<TData> childNode)

        {

            ChildNodeList.Add(childNode);

        }

 

        #endregion

        #region 배열하기 - Arrange(graphics, minimumX, minimumY)

 

        /// <summary>

        /// 배열하기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        /// <param name="minimumX">X 최소값</param>

        /// <param name="minimumY">Y 최소값</param>

        public void Arrange(Graphics graphics, ref float minimumX, ref float minimumY)

        {

            SizeF dataSize = Data.GetSize(graphics, Font);

 

            float x                 = minimumX;

            float biggestMinimumY   = minimumY + dataSize.Height;

            float childTreeMinimumY = minimumY + dataSize.Height + VERTICAL_OFFSET;

 

            foreach(TreeNode<TData> childNode in ChildNodeList)

            {

                float childMinimumY = childTreeMinimumY;

 

                childNode.Arrange(graphics, ref x, ref childMinimumY);

 

                if(biggestMinimumY < childMinimumY)

                {

                    biggestMinimumY = childMinimumY;

                }

 

                x += HORIZONTAL_OFFSET;

            }

 

            if(ChildNodeList.Count > 0)

            {

                x -= HORIZONTAL_OFFSET;

            }

 

            float childTreeWidth = x - minimumX;

 

            if(dataSize.Width > childTreeWidth)

            {

                x = minimumX + (dataSize.Width - childTreeWidth) / 2;

 

                foreach(TreeNode<TData> childNode in ChildNodeList)

                {

                    childNode.Arrange(graphics, ref x, ref childTreeMinimumY);

 

                    x += HORIZONTAL_OFFSET;

                }

 

                childTreeWidth = dataSize.Width;

            }

 

            CenterPoint = new PointF

            (

                minimumX + childTreeWidth  / 2,

                minimumY + dataSize.Height / 2

            );

 

            minimumX += childTreeWidth;

 

            minimumY = biggestMinimumY;

        }

 

        #endregion

        #region 트리 그리기 - DrawTree(graphics)

 

        /// <summary>

        /// 트리 그리기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        public void DrawTree(Graphics graphics)

        {

            DrawChildNodeLink(graphics);

 

            DrawChildNode(graphics);

        }

 

        #endregion

        #region 트리 그리기 - DrawTree(graphics, x, y)

 

        /// <summary>

        /// 트리 그리기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        /// <param name="x">X 좌표</param>

        /// <param name="y">Y 좌표</param>

        public void DrawTree(Graphics graphics, ref float x, float y)

        {

            Arrange(graphics, ref x, ref y);

 

            DrawTree(graphics);

        }

 

        #endregion

        #region 노드 구하기 - GetNode(graphics, targetPoint)

 

        /// <summary>

        /// 노드 구하기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        /// <param name="targetPoint">타겟 포인트</param>

        /// <returns>노드</returns>

        public TreeNode<TData> GetNode(Graphics graphics, PointF targetPoint)

        {

            if(Data.IsAtPoint(graphics, Font, CenterPoint, targetPoint))

            {

                return this;

            }

 

            foreach(TreeNode<TData> childNode in ChildNodeList)

            {

                TreeNode<TData> hitNode = childNode.GetNode(graphics, targetPoint);

 

                if(hitNode != null)

                {

                    return hitNode;

                }

            }

 

            return null;

        }

 

        #endregion

        #region 노드 삭제하기 - DeleteNode(targetNode)

 

        /// <summary>

        /// 노드 삭제하기

        /// </summary>

        /// <param name="targetNode">타겟 노드</param>

        /// <returns>삭제 결과</returns>

        public bool DeleteNode(TreeNode<TData> targetNode)

        {

            foreach(TreeNode<TData> childNode in ChildNodeList)

            {

                if(childNode == targetNode)

                {

                    ChildNodeList.Remove(childNode);

 

                    return true;

                }

 

                if(childNode.DeleteNode(targetNode))

                {

                    return true;

                }

            }

 

            return false;

        }

 

        #endregion

 

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

 

        #region 자식 노드 링크 그리기 - DrawChildNodeLink(graphics)

 

        /// <summary>

        /// 자식 노드 링크 그리기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        private void DrawChildNodeLink(Graphics graphics)

        {

            foreach(TreeNode<TData> childNode in ChildNodeList)

            {

                graphics.DrawLine(Pen, CenterPoint, childNode.CenterPoint);

 

                childNode.DrawChildNodeLink(graphics);

            }

        }

 

        #endregion

        #region 자식 노드 그리기 - DrawChildNode(graphics)

 

        /// <summary>

        /// 자식 노드 그리기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        private void DrawChildNode(Graphics graphics)

        {

            Data.Draw(CenterPoint.X, CenterPoint.Y, graphics, Pen, BackgroundBrush, FontBrush, Font);

 

            foreach(TreeNode<TData> childNode in ChildNodeList)

            {

                childNode.DrawChildNode(graphics);

            }

        }

 

        #endregion

    }

}

 

 

CircleNode.cs

 

 

using System.Drawing;

 

namespace TestProject

{

    /// <summary>

    /// 원 노드

    /// </summary>

    public class CircleNode : ITreeNode

    {

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

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

 

        #region Field

 

        /// <summary>

        /// 텍스트

        /// </summary>

        public string Text;

 

        #endregion

 

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

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

 

        #region 생성자 - CircleNode(text)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="text">텍스트</param>

        public CircleNode(string text)

        {

            Text = text;

        }

 

        #endregion

 

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

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

 

        #region 크기 구하기 - GetSize(graphics, font)

 

        /// <summary>

        /// 크기 구하기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        /// <param name="font">폰트</param>

        /// <returns>크기</returns>

        public SizeF GetSize(Graphics graphics, Font font)

        {

            return graphics.MeasureString(Text, font) + new SizeF(10, 10);

        }

 

        #endregion

        #region 그리기 - Draw(x, y, graphics, pen, backgroundBrush, textBrush, font)

 

        /// <summary>

        /// 그리기

        /// </summary>

        /// <param name="x">X 좌표</param>

        /// <param name="y">Y 좌표</param>

        /// <param name="graphics">그래픽스</param>

        /// <param name="pen"></param>

        /// <param name="backgroundBrush">배경 브러시</param>

        /// <param name="textBrush">텍스트 브러시</param>

        /// <param name="font">폰트</param>

        public void Draw(float x, float y, Graphics graphics, Pen pen, Brush backgroundBrush, Brush textBrush, Font font)

        {

            SizeF size = GetSize(graphics, font);

 

            RectangleF rectangle = new RectangleF

            (

                x - size.Width  / 2,

                y - size.Height / 2,

                size.Width,

                size.Height

            );

 

            graphics.FillEllipse(backgroundBrush, rectangle);

 

            graphics.DrawEllipse(pen, rectangle);

 

            using(StringFormat stringFormat = new StringFormat())

            {

                stringFormat.Alignment     = StringAlignment.Center;

                stringFormat.LineAlignment = StringAlignment.Center;

 

                graphics.DrawString(Text, font, textBrush, x, y, stringFormat);

            }

        }

 

        #endregion

        #region 포인트 위치 여부 구하기 - IsAtPoint(graphics, font, centerPoint, targetPoint)

 

        /// <summary>

        /// 포인트 위치 여부 구하기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        /// <param name="font">폰트</param>

        /// <param name="centerPoint">중심 포인트</param>

        /// <param name="targetPoint">타겟 포인트</param>

        /// <returns>포인트 위치 여부</returns>

        public bool IsAtPoint(Graphics graphics, Font font, PointF centerPoint, PointF targetPoint)

        {

            SizeF size = GetSize(graphics, font);

 

            targetPoint.X -= centerPoint.X;

            targetPoint.Y -= centerPoint.Y;

 

            float width  = size.Width / 2;

            float height = size.Height / 2;

 

            return targetPoint.X * targetPoint.X / width / width + targetPoint.Y * targetPoint.Y / height / height <= 1;

        }

 

        #endregion

    }

}

 

 

MainForm.cs

 

 

using System;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Text;

using System.Windows.Forms;

 

namespace TestProject

{

    /// <summary>

    /// 메인 폼

    /// </summary>

    public partial class MainForm : Form

    {

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

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

 

        #region Field

 

        /// <summary>

        /// 루트 노드

        /// </summary>

        private TreeNode<CircleNode> rootNode = new TreeNode<CircleNode>(new CircleNode("루트"));

 

        /// <summary>

        /// 선택 노드

        /// </summary>

        private TreeNode<CircleNode> selectedNode;

 

        #endregion

 

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

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

 

        #region 생성자 - MainForm()

 

        /// <summary>

        /// 생성자

        /// </summary>

        public MainForm()

        {

            InitializeComponent();

 

            #region 이벤트를 설정한다.

 

            Load                            += Form_Load;

            Resize                          += canvasPictureBox_Resize;

            this.canvasPictureBox.Paint     += canvasPictureBox_Paint;

            this.canvasPictureBox.MouseDown += canvasPictureBox_MouseDown;

            this.canvasPictureBox.MouseMove += canvasPictureBox_MouseMove;

            this.canvasPictureBox.Resize    += canvasPictureBox_Resize;

 

            #endregion

        }

 

        #endregion

 

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

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

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

 

        #region 폼 로드시 처리하기 - Form_Load(sender, e)

 

        /// <summary>

        /// 폼 로드시 처리하기

        /// </summary>

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

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

        private void Form_Load(object sender, EventArgs e)

        {

            TreeNode<CircleNode> node1 = new TreeNode<CircleNode>(new CircleNode("가"));

            TreeNode<CircleNode> node2 = new TreeNode<CircleNode>(new CircleNode("나"));

            TreeNode<CircleNode> node3 = new TreeNode<CircleNode>(new CircleNode("다"));

            TreeNode<CircleNode> node4 = new TreeNode<CircleNode>(new CircleNode("라"));

            TreeNode<CircleNode> node5 = new TreeNode<CircleNode>(new CircleNode("마"));

            TreeNode<CircleNode> node6 = new TreeNode<CircleNode>(new CircleNode("바"));

            TreeNode<CircleNode> node7 = new TreeNode<CircleNode>(new CircleNode("사"));

            TreeNode<CircleNode> node8 = new TreeNode<CircleNode>(new CircleNode("아"));

 

            rootNode.AddChild(node1);

            rootNode.AddChild(node2);

 

            node1.AddChild(node3);

            node1.AddChild(node4);

            node2.AddChild(node5);

            node2.AddChild(node6);

            node2.AddChild(node7);

            node5.AddChild(node8);

 

            ArrangeTree();

        }

 

        #endregion

        #region 캔버스 픽처 박스 크기 조정시 처리하기 - canvasPictureBox_Resize(sender, e)

 

        /// <summary>

        /// 캔버스 픽처 박스 크기 조정시 처리하기

        /// </summary>

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

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

        private void canvasPictureBox_Resize(object sender, EventArgs e)

        {

            ArrangeTree();

        }

 

        #endregion

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

 

        /// <summary>

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

        /// </summary>

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

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

        private void canvasPictureBox_MouseDown(object sender, MouseEventArgs e)

        {

            if(e.Button != MouseButtons.Right)

            {

                return;

            }

 

            FindNodeUnderMouse(e.Location);

 

            if(this.selectedNode != null)

            {

                this.deleteNodeMenuItem.Enabled = (this.selectedNode != rootNode);

 

                this.contextMenuStrip.Show(this, e.Location);

            }

        }

 

        #endregion

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

 

        /// <summary>

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

        /// </summary>

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

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

        private void canvasPictureBox_MouseMove(object sender, MouseEventArgs e)

        {

            FindNodeUnderMouse(e.Location);

 

            if(this.selectedNode == null)

            {

                this.statusLabel.Text = string.Empty;

            }

            else

            {

                this.statusLabel.Text = this.selectedNode.Data.Text;

            }

        }

 

        #endregion

        #region 캔버스 픽처 박스 페인트시 처리하기 - canvasPictureBox_Paint(sender, e)

 

        /// <summary>

        /// 캔버스 픽처 박스 페인트시 처리하기

        /// </summary>

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

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

        private void canvasPictureBox_Paint(object sender, PaintEventArgs e)

        {

            e.Graphics.SmoothingMode     = SmoothingMode.AntiAlias;

            e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;

 

            this.rootNode.DrawTree(e.Graphics);

        }

 

        #endregion

        

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

 

        #region 트리 배열하기 - ArrangeTree()

 

        /// <summary>

        /// 트리 배열하기

        /// </summary>

        private void ArrangeTree()

        {

            using(Graphics graphics = canvasPictureBox.CreateGraphics())

            {

                float minimumX = 0;

                float minimumY = 0;

 

                this.rootNode.Arrange(graphics, ref minimumX, ref minimumY);

 

                minimumX = (ClientSize.Width - minimumX) / 2;

                minimumY = 10;

 

                this.rootNode.Arrange(graphics, ref minimumX, ref minimumY);

            }

 

            this.canvasPictureBox.Refresh();

        }

 

        #endregion

        #region 마우스 아래 노드 찾기 - FindNodeUnderMouse(point)

 

        /// <summary>

        /// 마우스 아래 노드 찾기

        /// </summary>

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

        private void FindNodeUnderMouse(PointF point)

        {

            using(Graphics graphics = this.canvasPictureBox.CreateGraphics())

            {

                this.selectedNode = this.rootNode.GetNode(graphics, point);

            }

        }

 

        #endregion

        #region 자식 노드 추가 메뉴 항목 클릭시 처리하기 - addChildNodeMenuItem_Click(sender, e)

 

        /// <summary>

        /// 자식 노드 추가 메뉴 항목 클릭시 처리하기

        /// </summary>

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

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

        private void addChildNodeMenuItem_Click(object sender, EventArgs e)

        {

            AddChildNodeForm form = new AddChildNodeForm();

 

            if(form.ShowDialog() == DialogResult.OK)

            {

                TreeNode<CircleNode> childNode = new TreeNode<CircleNode>(new CircleNode(form.NodeText));

 

                this.selectedNode.AddChild(childNode);

 

                ArrangeTree();

            }

        }

 

        #endregion

        #region 노드 삭제하기 메뉴 항목 클릭시 처리하기 - deleteNodeMenuItem_Click(sender, e)

 

        /// <summary>

        /// 노드 삭제하기 메뉴 항목 클릭시 처리하기

        /// </summary>

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

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

        private void deleteNodeMenuItem_Click(object sender, EventArgs e)

        {

            if(MessageBox.Show("이 노드를 삭제하시겠습니까?", "확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)

            {

                this.rootNode.DeleteNode(this.selectedNode);

 

                ArrangeTree();

            }

        }

 

        #endregion

    }

}

 

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

Posted by 사용자 icodebroker

댓글을 달아 주세요