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

■ GroupBox 클래스를 사용해 익스팬더(Expander) 그룹 박스를 만드는 방법을 보여준다.

TestProject.zip
다운로드

▶ GraphicsHelper.cs

using System;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace TestProject
{
    /// <SUMMARY>
    /// 그래픽스 헬퍼
    /// </SUMMARY>
    public class GraphicsHelper
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 그래픽스
        /// </summary>
        private Graphics graphics;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 그래픽스 - Graphics

        /// <summary>
        /// 그래픽스
        /// </summary>
        public Graphics Graphics
        {
            get
            {
                return this.graphics;
            }
            set
            {
                this.graphics = value;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - GraphicsHelper(graphics)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        public GraphicsHelper(Graphics graphics)
        {
            this.graphics = graphics;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 캡슐 경로 구하기 - GetCapsulePath(rectangle)

        /// <summary>
        /// 캡슐 경로 구하기
        /// </summary>
        /// <param name="rectangle">사각형</param>
        /// <returns>캡슐 경로</returns>
        private GraphicsPath GetCapsulePath(RectangleF rectangle)
        {
            float diameter;

            RectangleF arcRectangle;

            GraphicsPath path = new GraphicsPath();

            try
            {
                if(rectangle.Width > rectangle.Height)
                {
                    diameter = rectangle.Height;

                    SizeF size = new SizeF(diameter, diameter);

                    arcRectangle = new RectangleF(rectangle.Location, size);

                    path.AddArc(arcRectangle, 90, 180);

                    arcRectangle.X = rectangle.Right-diameter;

                    path.AddArc(arcRectangle, 270, 180);
                }
                else if(rectangle.Width < rectangle.Height)
                {
                    diameter = rectangle.Width;

                    SizeF size = new SizeF(diameter, diameter);

                    arcRectangle = new RectangleF(rectangle.Location, size);

                    path.AddArc(arcRectangle, 180, 180);

                    arcRectangle.Y = rectangle.Bottom-diameter;

                    path.AddArc(arcRectangle, 0, 180);
                }
                else
                {
                    path.AddEllipse(rectangle);
                }
            }
            catch(Exception)
            {
                path.AddEllipse(rectangle);
            }
            finally
            {
                path.CloseFigure();
            }

            return path;
        }

        #endregion
        #region 라운드 사각형 경로 구하기 - GetRoundRectanglePath(rectangle, radius)

        /// <summary>
        /// 라운드 사각형 경로 구하기
        /// </summary>
        /// <param name="rectangle">사각형</param>
        /// <param name="radius">반지름</param>
        /// <returns>라운드 사각형 경로</returns>
        private GraphicsPath GetRoundRectanglePath(RectangleF rectangle, float radius)
        {
            if(radius <= 0.0f)
            {
                GraphicsPath graphicsPath = new GraphicsPath();

                graphicsPath.AddRectangle(rectangle);

                graphicsPath.CloseFigure();

                return graphicsPath;
            }

            if(radius >= (Math.Min(rectangle.Width, rectangle.Height)) / 2.0)
            {
                return GetCapsulePath(rectangle);
            }

            float diameter = radius * 2.0f;

            SizeF size = new SizeF(diameter, diameter);

            RectangleF arcRectangle = new RectangleF(rectangle.Location, size);

            GraphicsPath path = new GraphicsPath();

            path.AddArc(arcRectangle, 180, 90);

            arcRectangle.X = rectangle.Right-diameter;

            path.AddArc(arcRectangle, 270, 90);

            arcRectangle.Y = rectangle.Bottom-diameter;

            path.AddArc(arcRectangle, 0, 90);

            arcRectangle.X = rectangle.Left;

            path.AddArc(arcRectangle, 90, 90);

            path.CloseFigure();

            return path;
        }

        #endregion
        #region 라운드 사각형 채우기 - FillRoundRectangle(brush, x, y, width, height, radius)

        /// <summary>
        /// 라운드 사각형 채우기
        /// </summary>
        /// <param name="brush">브러시</param>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="width">너비</param>
        /// <param name="height">높이</param>
        /// <param name="radius">반지름</param>
        public void FillRoundRectangle(Brush brush, int x, int y, int width, int height, int radius)
        {
            float xFloat      = Convert.ToSingle(x);
            float yFloat      = Convert.ToSingle(y);
            float widthFloat  = Convert.ToSingle(width);
            float heightFloat = Convert.ToSingle(height);
            float radiusFloat = Convert.ToSingle(radius);

            FillRoundRectangle(brush, xFloat, yFloat, widthFloat, heightFloat, radiusFloat);
        }

        #endregion
        #region 라운드 사각형 채우기 - FillRoundRectangle(brush, x, y, width, height, radius)

        /// <summary>
        /// 라운드 사각형 채우기
        /// </summary>
        /// <param name="brush">브러시</param>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="width">너비</param>
        /// <param name="height">높이</param>
        /// <param name="radius">반지름</param>
        public void FillRoundRectangle(Brush brush, float x, float y, float width, float height, float radius)
        {
            RectangleF rectangle = new RectangleF(x, y, width, height);

            GraphicsPath graphicsPath = GetRoundRectanglePath(rectangle, radius);

            this.graphics.FillPath(brush, graphicsPath);
        }

        #endregion
        #region 라운드 사각형 그리기 - DrawRoundRectangle(pen, x, y, width, height, radius)

        /// <summary>
        /// 라운드 사각형 그리기
        /// </summary>
        /// <param name="pen">펜</param>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="width">너비</param>
        /// <param name="height">높이</param>
        /// <param name="radius">반지름</param>
        public void DrawRoundRectangle(Pen pen, float x, float y, float width, float height, float radius)
        {
            RectangleF rectangle = new RectangleF(x, y, width, height);

            GraphicsPath graphicsPath = GetRoundRectanglePath(rectangle, radius);

            this.graphics.DrawPath(pen, graphicsPath);
        }

        #endregion
        #region 라운드 사각형 그리기 - DrawRoundRectangle(pen, x, y, width, height, radius)

        /// <summary>
        /// 라운드 사각형 그리기
        /// </summary>
        /// <param name="pen">펜</param>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="width">너비</param>
        /// <param name="height">높이</param>
        /// <param name="radius">반지름</param>
        public void DrawRoundRectangle(Pen pen, int x, int y, int width, int height, int radius)
        {
            float xFloat      = Convert.ToSingle(x);
            float yFloat      = Convert.ToSingle(y);
            float widthFloat  = Convert.ToSingle(width);
            float heightFloat = Convert.ToSingle(height);
            float radiusFloat = Convert.ToSingle(radius);

            DrawRoundRectangle(pen, xFloat, yFloat, widthFloat, heightFloat, radiusFloat);
        }

        #endregion
    }
}

 

▶ Expander.cs

using System;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;

namespace TestProject
{
    /// <summary>
    /// 익스팬더
    /// </summary>
    [ToolboxBitmap(typeof(Expander))]
    public class Expander : GroupBox
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Delegate
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 익스팬더 버튼 클릭 이벤트 핸들러 - ExpanderButtonClickEventHandler(sender, e)

        /// <summary>
        /// 익스팬더 버튼 클릭 이벤트 핸들러
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        public delegate void ExpanderButtonClickEventHandler(object sender, EventArgs e);

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Event
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 익스팬더 버튼 클릭 이벤트 - ExpanderButtonClick

        /// <summary>
        /// 익스팬더 버튼 클릭 이벤트
        /// </summary>
        public event ExpanderButtonClickEventHandler ExpanderButtonClick;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 토글 사각형
        /// </summary>
        private Rectangle toggleRectangle = new Rectangle(8, 2, 11, 11);

        /// <summary>
        /// 테두리 색상
        /// </summary>
        private Color borderColor = Color.FromArgb(208, 208, 191);

        /// <summary>
        /// 텍스트 색상
        /// </summary>
        private Color textColor = Color.FromArgb(0, 70, 213);

        /// <summary>
        /// 축소 여부
        /// </summary>
        private bool isCollapsed = false;

        /// <summary>
        /// 크기 조정 여부
        /// </summary>
        private bool isResizing = false;

        /// <summary>
        /// 축소시 높이
        /// </summary>
        private const int COLLAPSED_HEIGHT = 20;

        /// <summary>
        /// 완전한 크기
        /// </summary>
        private Size fullSize = Size.Empty;

        /// <summary>
        /// 컨테이너
        /// </summary>
        private Container container = null;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 완전한 높이 - FullHeight

        /// <summary>
        /// 완전한 높이
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int FullHeight
        {
            get
            {
                return this.fullSize.Height;
            }
        }

        #endregion
        #region 축소시 높이 - CollapsedHeight

        /// <summary>
        /// 축소시 높이
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int CollapsedHeight
        {
            get
            {
                return COLLAPSED_HEIGHT;
            }
        }

        #endregion
        #region 축소 여부 - IsCollapsed

        /// <summary>
        /// 축소 여부
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [DefaultValue(false)]
        public bool IsCollapsed
        {
            get
            {
                return this.isCollapsed;
            }
            set
            {
                if(value != this.isCollapsed)
                {
                    this.isCollapsed = value;

                    if(!value)
                    {
                        Size = this.fullSize;
                    }
                    else
                    {
                        this.isResizing = true;

                        Height = COLLAPSED_HEIGHT;

                        this.isResizing = false;
                    }

                    foreach(Control control in Controls)
                    {
                        control.Visible = !value;
                    }

                    Invalidate();
                }
            }
        }

        #endregion

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

        #region 마이너스 이미지 - MinusImage

        /// <summary>
        /// 마이너스 이미지
        /// </summary>
        private Image MinusImage
        {
            get
            {
                return Image.FromStream
                (
                    Assembly.GetExecutingAssembly().GetManifestResourceStream("TestProject.RESOURCE.minus.bmp")
                );
            }
        }

        #endregion
        #region 플러스 이미지 - PlusImage

        /// <summary>
        /// 플러스 이미지
        /// </summary>
        private Image PlusImage
        {
            get
            {
                return Image.FromStream
                (
                    Assembly.GetExecutingAssembly().GetManifestResourceStream("TestProject.RESOURCE.plus.bmp")
                );
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - Expander()

        /// <summary>
        /// 생성자
        /// </summary>
        public Expander()
        {
            InitializeComponent();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Protected

        #region 마우스 UP 처리하기 - OnMouseUp(e)

        /// <summary>
        /// 마우스 UP 처리하기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            if(this.toggleRectangle.Contains(new Point(e.X, e.Y)))
            {
                Toggle();
            }
            else
            {
                base.OnMouseUp(e);
            }
        }

        #endregion
        #region 페인트시 처리하기 - OnPaint(e)

        /// <summary>
        /// 페인트시 처리하기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            SetFullSize();

            DrawGroupBox(e.Graphics);

            DrawExpanderButton(e.Graphics);
        }

        #endregion
        #region 리소스 해제하기 - Dispose(disposing)

        /// <summary>
        /// 리소스 해제하기
        /// </summary>
        /// <param name="disposing">리소스 해제 여부</param>
        protected override void Dispose(bool disposing)
        {
            if(disposing)
            {
                if(container != null)
                {
                    container.Dispose();
                }
            }

            base.Dispose(disposing);
        }

        #endregion

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

        #region 컴포넌트 초기화하기 - InitializeComponent()

        /// <summary>
        /// 컴포넌트 초기화하기
        /// </summary>
        private void InitializeComponent()
        {
            this.container = new Container();
        }

        #endregion
        #region 그룹 박스 그리기 - DrawGroupBox(graphics)

        /// <summary>
        /// 그룹 박스 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        private void DrawGroupBox(Graphics graphics)
        {
            Rectangle boundRectangle = new Rectangle
            (
                ClientRectangle.X,
                ClientRectangle.Y + 6,
                ClientRectangle.Width,
                ClientRectangle.Height - 6
            );

            int textLeft  = (boundRectangle.X + 8) + this.toggleRectangle.Width + 2;
            int textWidth = (int)graphics.MeasureString(Text, Font).Width;

            textWidth = textWidth < 1 ? 1 : textWidth;

            int textRight = textLeft + textWidth + 1;

            GraphicsHelper helper = new GraphicsHelper(graphics);

            using(Pen pen = new Pen(this.borderColor))
            {
                helper.DrawRoundRectangle(pen, 0, 6, Width - 1, Height - 7, 5f);
            }

            graphics.FillRectangle
            (
                SystemBrushes.Control,
                8,
                2,
                textRight - 8,
                12
            );

            using(SolidBrush brush = new SolidBrush(this.textColor))
            {
                graphics.DrawString(Text, this.Font, brush, textLeft, 0);
            }
        }

        #endregion
        #region 익스팬더 버튼 그리기 - DrawExpanderButton(graphics)

        /// <summary>
        /// 익스팬더 버튼 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        private void DrawExpanderButton(Graphics graphics)
        {
            if(IsCollapsed)
            {
                graphics.DrawImage(PlusImage, this.toggleRectangle);
            }
            else
            {
                graphics.DrawImage(MinusImage, this.toggleRectangle);
            }
        }

        #endregion
        #region 토글하기 - Toggle()

        /// <summary>
        /// 토글하기
        /// </summary>
        private void Toggle()
        {
            IsCollapsed = !IsCollapsed;

            ExpanderButtonClick?.Invoke(this, EventArgs.Empty);
        }

        #endregion
        #region 완전한 크기 설정하기 - SetFullSize()

        /// <summary>
        /// 완전한 크기 설정하기
        /// </summary>
        private void SetFullSize()
        {
            if(!this.isResizing && !this.isCollapsed)
            {
                this.fullSize = Size;
            }
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요