반응형

[그림 5-1] 추상 팩토리 패턴을 적용한 Header 생성 관련 Class Diagram

추상 팩토리 패턴의 클래스 생성을 책임지는 HeaderBuilder Class를 살펴보기로 하자.

HeaderBuilder Class는 GridType에 따른 HeaderGenerator를 상속 받은 클래스를 이용해서 Header를 그리기 위한 클래스를 생성하는 책임을 가지게 된다.

먼저 GridType에 대해서 살펴보자.

GridType은 차후에 다양한 형태의 Grid를 그릴 수 있도록 아래와 같이 3가지 타입을 정의했다. 현재는 DefaultType을 기반으로 먼저 기본 기능들을 구현할 것이다. DefaultType의 기능이 구현되고 나면 추후에 YearMonthWeekNoType과 YearMonthWeekNoDayType 등을 구현하도록 하자.

    public enum GridType
    {
        DefaultType,    //Default Type
        YearMonthWeekNoType,    //년, 월, 주차 유형
        YearMonthWeekNoDayType  //년월, 주차, 일 유형
    }

 

HeaderBuilder Class는 GridType을 설정하고 가져오기 위해서 GridType 변수와 GridType에 따른 HeaderGenerator를 설정하기 위한 HeaderGenerator 변수를 가지게 된다.

    private GridType gridType = GridType.DefaultType;   //GridType 변수 선언 및 DefaultType 설정
    private HeaderGenerator headGen;    //HeaderGenerator 변수 선언

 

설정한 GridType 값을 HeaderBuilder Class 외부에서 get/set 하기 위한 Property

    public GridType GridDisplayType
    {
        get { return gridType; }
        set { gridType = value; }
    }

 

HeaderBuilder Class의 생성자는 파라미터가 없는 Default 생성자와 GridType을 파라미터를 입력 받는 생성자를 제공

    public HeaderBuilder() { }
    public HeaderBuilder(GridType grdType)
    {
        this.gridType = grdType;
        if (gridType == GridType.DefaultType) //현재는 GridType이 DefaultType일 경우만 설정함.
        {
            if (headGen == null) 

                headGen = new DefaultHeaderGenerator(); //GridType이 DefaultType일 경우 DefaultHeaderGenerator를 설정
        }
    }

 

Grid Header의 Header정보를 추가하는 메소드

    public void AddHeader(Header header)
    {
        if (gridType == GridType.DefaultType) //현재는 GridType이 DefaultType일 경우만 설정함.
        {
            if (headGen == null) headGen = new DefaultHeaderGenerator();
            headGen.AddHeaders(header);
        }
    }

 

추가된 Header 목록을 가져오는 메소드

    public<List> GetHeaders()
    {
        return headGen.GetHeaders();
    }

 

HeaderGenerator를 반환하는 Property

    public HeaderGenerator HeaderGen
    {
        get { return headGen; }
    }

 

Header 추가 후 Header를 초기화하는 메소드에서 Header를 그리기 위해서 필요한 각 Header의 순서인 Index값과 왼쪽 시작 X 좌표값을 계산하게 된다. 

    public void InitializeHeader()
    {
        //헤더 설정이 되어져 있지 않을 경우
        if (headGen == null || headGen.GetHeaders().Count < 1) return;

        int index = 0;
        int left = 0;

       

        // 등록된 Header 정보에서 Header Index값과 Left 좌표 값을 계산한다.
        foreach (Header header in headGen.GetHeaders())
        {
            header.Index = index;
            header.Left = left;
            left += header.Width;
            index++;
        }
    }

 

Grid Header를 그리기 위해서 필요한 클래스들을 모두 만들어 보았다. 아래의 소스 코드를 실행해도 Grid Header가 그려지거나 하지는 않지만 Grid Header를 그리기 위한 준비는 되었다.

다음 시간에는 Grid Class를 만들어서 실제 Grid Header를 그리는 메소드를 구현해 보겠다.

MyProject02.zip
0.26MB

 

 

 

반응형
반응형

[그림 4-1] 추상 팩토리 패턴을 적용한 Header 생성 관련 Class Diagram

지난 번 강좌에서 보았던 추상 팩토리 패턴의 Header 생성 클래스에 대해서 살펴 보기로 하자.

 

공통 Header 정보를 관리하는 추상클래스 Header 클래스를 살펴보자.

추상 클래스인 Header에는 반드시 가져야 하는 공통 속성 정보들을 관리하도록 구현했다.

각 속성 정보들은 외부에서 getter, setter 할 수 있도록 Property 형태로 제공한다.

 

public abstract class Header
{
    #region 변수
    private int index;  //Index
    private string columnId;    //컬럼 ID
    private string title;   //컬럼 타이틀
    private int left;   //컬럼 시작위치
    private int width;  //컬럼 폭
    private HorizontalAlignment headAlign;  //컬럼 타이틀 정렬위치
    private HorizontalAlignment textAlign;  //컬럼 내용 정렬위치
    private bool visible = true;    //컬럼 Visible 여부
    #endregion 변수

    #region Property
    public int Index
    {
        get { return index; }
        set { index = value; }
    }
    public string ColumnId {
        get { return columnId; }
        set { columnId = value; }
    }
    public string Title
    {
        get { return title; }
        set { title = value; }
    }
    public int Left
    {
        get { return left; }
        set { left = value; }
    }
    public int Width
    {
        get { return width; }
        set { width = value; }
    }
    public HorizontalAlignment HeadAlign
    {
        get { return headAlign; }
        set { headAlign = value; }
    }
    public HorizontalAlignment TextAlign {
        get { return textAlign; }
        set { textAlign = value; }
    }
    public bool Visible
    {
        get { return visible; }
        set { visible = value; }
    }
    #endregion Property
}

 

Header Type에 따라 Grid Header의 모습이 다양하게 그려질 수 있도록 공통 속성 외에 추가 속성과 생성자를 정의할 수 있도록 Header 클래스를 상속 받아서 구현한 클래스를 만들 수 있도록 했다.

향 후 Grid Control의 모습이 어느 정도 완성이 되면 다른 형태의 Grid Header를 만들어야 할 경우 Header 클래스를 상속받아서 구현한 클래스를 만들게 될 것이다.

현재는 기본 Header를 그릴 수 있는 DefaultHeader라는 클래스를 만들었다. 

DefaultHeader 클래스는 추상 클래스를 상속받아서 생성자를 6개 형태로 만들었다. 생성자 호출 시에 속성 정보를 파라미터로 받아서 처리할 수 있도록 하기 위해서 이다.

 

    public class DefaultHeader : Header
    {        
        public DefaultHeader()
        {
            this.ColumnId = "";
            this.Title = "";
            this.Width = 90;
            this.HeadAlign = HorizontalAlignment.Center;
            this.TextAlign = HorizontalAlignment.Left;
            this.Visible = true;
        }

        public DefaultHeader(string fieldName, string title, int width, HorizontalAlignment headAlign, HorizontalAlignment txtAlign, bool visible)
        {
            this.ColumnId = fieldName;
            this.Title = title;
            this.Width = width;
            this.HeadAlign = headAlign;
            this.TextAlign = txtAlign;
            this.Visible = visible;
        }
        public DefaultHeader(string fieldName, string title, int width, HorizontalAlignment txtAlign, bool visible)
        {
            this.ColumnId = fieldName;
            this.Title = title;
            this.Width = width;
            this.HeadAlign = HorizontalAlignment.Center;
            this.TextAlign = txtAlign;
            this.Visible = visible;
        }

        public DefaultHeader(string fieldName, string title, HorizontalAlignment txtAlign, bool visible)
        {
            this.ColumnId = fieldName;
            this.Title = title;
            this.Width = 100;
            this.HeadAlign = HorizontalAlignment.Center;
            this.TextAlign = txtAlign;
            this.Visible = visible;
        }

        public DefaultHeader(string fieldName, string title, int width, bool visible)
        {
            this.ColumnId = fieldName;
            this.Title = title;
            this.Width = width;
            this.HeadAlign = HorizontalAlignment.Center;
            this.TextAlign = HorizontalAlignment.Center;
            this.Visible = visible;
        }

        public DefaultHeader(string fieldName, string title, int width, HorizontalAlignment txtAlign)
        {
            this.ColumnId = fieldName;
            this.Title = title;
            this.Width = width;
            this.HeadAlign = HorizontalAlignment.Center;
            this.TextAlign = txtAlign;
            this.Visible = true;
        }        
    }

 

Grid Header를 생성하기 위해서 Header 정보를 추가(Add)하고 그리기 위한 메소드를 지정하는 추상 클래스를 만든다.

    public abstract class HeaderGenerator
    {
        protected List _headers = new List(); 

        protected int topHeaderHeight = 20;   //Grid의 Header 높이
        protected int leftHeaderWidth = 22; //Grid의 맨 왼쪽의 첫번째 빈Column Width
        protected Font headerFont = new Font("맑은 고딕", 9);
        protected SolidBrush blackBrush = new SolidBrush(Color.Black);

        public int TopHeaderHeight
        {
            get { return topHeaderHeight; }
            set { topHeaderHeight = value; }
        }

        public int LeftHeaderWidth
        {
            get { return leftHeaderWidth; }
            set { leftHeaderWidth = value; }
        }

        public Font HeaderFont
        {
            get { return headerFont; }
            set { headerFont = value; }
        }

        public SolidBrush BlackBrush
        {
            get { return blackBrush; }
            set { blackBrush = value; }
        }

        public HeaderGenerator() { }

        public abstract void AddHeaders(object obj);
        public abstract List GetHeaders(); 

        public abstract void HeaderClear();                         

        public abstract void DrawHeaders(int firstVisibleCol, int lastVisibleCol, int controlWidth, Graphics graphics, Rectangle rect);

    }

 

이제 HeaderGenerator라는 추상 클래스를 상속받아서 Header Type별로 Header를 그리는 클래스를 만들어야 한다.

현재는 기본 Header인 DefaultHeader를 설정하고 그리기 위한 DefaultHeaderGenerator 클래스를 만들어야 한다.

DefaultHeaderGenerator 클래스는 Grid의 Header 영역을 직접 그리는 영역이다.

특히 주의 깊게 봐야 할 부분은 DrawHeaders 메소드 부분이다. 

Grid의 Row와 Column이 아무리 많이 있다고 하더라도 Grid Control 사이즈에 맞는 영역의 Row와 Column만 사용자에게 제공하면 된다.

따라서 스크롤바를 움직이거나 움직이지 않았을 때 Grid Control에서 보여줘야 할 Row와 Column을 계산해서 찾아내야 한다. Header의 경우 항상 Control의 최상단 영역에 그려지는 부분이기에 Grid Control 영역에 맞는 첫번재 컬럼과 마지막 컬럼을 찾아서 헤더 정보를 그리면 된다.

그렇기에 DrawHeaders메소드의 파라미터로 firstVisibleCol, lastVisibleCol를 받고 있는 것이다.

 

    public class DefaultHeaderGenerator : HeaderGenerator
    {        
        /// 컬럼 헤더 정보를 설정
        public override void AddHeaders(object obj)
        {
            this._headers.Add(obj as Header);            
        }

        /// 컬럼 헤더 정보를 반환
        public override List GetHeaders()
        {
            return this._headers;
        }

        /// 헤더 정보를 Clear한다.
        public override void HeaderClear()
        {
            this._headers.Clear();
        }

        /// Header 그리기                 
        public override void DrawHeaders(int firstVisibleCol, int lastVisibleCol, int controlWidth, Graphics graphics, Rectangle rect)
        {
            SolidBrush brush = new SolidBrush(SystemColors.ControlLight);
            Pen pen = new Pen(Color.LightGray);

            int columnStartX = 0;
            graphics.FillRectangle(brush, columnStartX + 1, 1, leftHeaderWidth, topHeaderHeight);
            graphics.DrawRectangle(pen, columnStartX + 1, 1, leftHeaderWidth, topHeaderHeight);
            
            for (int i = firstVisibleCol; i <= lastVisibleCol; i++)
            {
                columnStartX = this._headers[i].Left + leftHeaderWidth;
                int headerWidth = this._headers[i].Width;

                //보여지는 컬럼의 폭이 컨트롤의 폭 보다 클경우 
                if (this._headers[i].Left + headerWidth > controlWidth)
                {
                    headerWidth = controlWidth - this._headers[i].Left - leftHeaderWidth - 3;
                }
                //헤더 영역의 사각형을 채우고 테두리를 그린다.
                graphics.FillRectangle(brush, columnStartX + 1, 1, headerWidth, topHeaderHeight);
                graphics.DrawRectangle(pen, columnStartX + 1, 1, headerWidth, topHeaderHeight);
                //헤더 타이틀 정렬 방법 설정
                StringFormat sf = new StringFormat();
                sf.LineAlignment = StringAlignment.Center;
                sf.Alignment = StringUtil.GetStringAlignment(sf, HorizontalAlignment.Center);
                //헤더 타이틀을 그린다.
                Rectangle colRec = new Rectangle(columnStartX + 1, 1, headerWidth, topHeaderHeight);
                graphics.DrawString(this._headers[i].Title, headerFont, blackBrush, colRec, sf);
            }
        }
    }
}

WANI Grid.zip
0.25MB

 

Grid Header를 그리기 위한 기본 클래스들에 대해서 살펴 보았다.

지금까지의 설명을 담은 소스는 위의 첨부 파일을 참조하면 된다.

다음 강좌에서는 Grid Header 영역을 그리기 위한 부분들을 추가해서 헤더가 그려지는 부분을 완성하도록 해보겠다.

이 강좌는 Step By Step 형태로 조그마한 기능 하나 하나를 추가/보완해 가면서 전체 기능을 완성하는 과정을 보여주고 나 또한 단계별 학습을 통해서 기능을 하나씩 완성해 가고자 한다.

 

단계별 진행을 보면서 추가 또는 보완했으면 하는 아이디어가 있다면 언제든 댓글을 달아주시길....

반응형
반응형

앞에 만들었던 WANIGrid 솔루션에 Grid Control에 걸맞는 레이아웃을 잡도록한다.

Grid Control에 VScrollBar와 HScrollBar를 추가하고 Control 사이즈 변경에 따라 VScrollBar와 HScrollBar가 제 위치를 찾아 갈 수 있도록 레이아웃을 만들도록 한다.

Control 사이즈를 넘는 많은 Row와 Column의 데이터를 제공하기 위해서 가로/세로 스크롤 바를 추가해서 전체 데이터를 볼 수 있도록 해야 한다.

 

WANIGrid Control의 사이즈는 기본적으로 Width: 200, Height: 150 으로 설정한다.

[그림 1] Control 사이즈 설정

HScrollBar와 VScrollBar를 추가한다.

HScrollBar의 Name은 hScrollBar, VScrollBar의 Name은 vScrollBar로 한다.

[그림 2] 가로/세로 스크롤바 추가

Control 초기화 되거나 사이즈가 변경될 때 ScrollBar를 초기화 할 수 있는 함수를 만들도록 한다.

ScrollBar초기화 시에는 가로 스크롤바의 위치와 세로 스크롤바의 위치를 Control 사이즈 내에서 맨 아래와 맨 우측에 위치할 있도록 설정을 해야 한다.

 

또한 세로 스크롤바의 경우 Grid Header 영역 아래에서 부터 시작을 해야 하기에 Grid Header 영역을 20으로 잡아서 Control 영역 상단의 20만큼의 공간을 비워두고 스크롤이 위치되도록 한다.

이때 Grid의 Header 영역을 보관하는 변수를 WANIGrid.cs에 추가하도록 한다.

 

private int topHeaderHeight = 20;   //Grid의 Header 높이

 

Control 내의 가로/세로 스크롤바의 위치를 초기화하는 메소드를 만든다.

        /// 
        /// HScrollBar, VScrollBar 초기화
        /// 
        public void InitializeScollBar()
        {
            //가로 스크롤바 설정
            hScrollBar.Left = 1;
            hScrollBar.Width = Width - vScrollBar.Width - 2; 
            hScrollBar.Top = Height - hScrollBar.Height - 2;

            //세로 스크롤바 설정
            vScrollBar.Left = Width - vScrollBar.Width - 2;
            vScrollBar.Top = topHeaderHeight + 2;
            vScrollBar.Height = Height - topHeaderHeight - hScrollBar.Height - 4;
        }

 

이렇게 만들어진 메소드 InitializeScollBar()를 Form Load 이벤트, Resize 이벤트, 사이즈 변경 이벤트, Client 사이즈 변경 이벤트가 발생할 때 호출하도록 한다.

 

        private void WANIGrid_Load(object sender, EventArgs e)
        {
            InitializeScollBar();
        }

        private void WANIGrid_Resize(object sender, EventArgs e)
        {
            InitializeScollBar();
        }

        private void WANIGrid_SizeChanged(object sender, EventArgs e)
        {
            InitializeScollBar();
        }

        private void WANIGrid_ClientSizeChanged(object sender, EventArgs e)
        {
            InitializeScollBar();
        }

 

이렇게 함으로써 Form의 사이즈가 변경될 때마다 가로/세로 스크롤바의 위치가 항상 Control의 Bottom과 Right에 위치하게 된다.

이렇게 Control의 기본 Layout에 대한 간단한 설정을 마무리 했다.

다음 회차에서는 이렇게 만들어진 Grid Control 레이아웃에서 그려져야 할 Grid Header 영역에 대해서 이야기 하고자 한다. Grid Header 영역은 분량이 많은 관계로 여러 번에 걸쳐서 설명을 할 예정이다.

 

지금까지 설명한 소스코드와 WANIGrid Control를 삽입해서 테스트 할 수 있는 WinForm 프로젝트가 아래의 zip파일에 포함되어져 있다.

압축을 풀고 WANI Grid 폴더 아래의 솔루션 파일을 읽어 들이면 WANIGridTest 프로젝트도 같이 포함된다.

WANI Grid.zip
0.21MB

반응형
반응형

Grid란 격자 모양을 의미하며, 격자 모양의 공간 즉 Cell 내부에 데이터를 보여주거나 입력하게 되는 컨트롤을 의미한다.

인터넷 상에는 Grid Control과 관련된 많은 소스와 방법들이 있다.

이미 많이 구현되어져 있고, 지금까지 내가 많이 사용해 왔던 Control이기에 앞으로 기본 기능부터 하나씩 구현하면서 점차 고유의 기능을 덧붙이고 기존의 방법과는 다른 방법을 제공하거나 제공되지 않았던 기능을 추가하면서 Grid Control을 발전시켜 나갈 계획이다.

가장 단순한 기능 부터 개발하면서 하나씩 기능을 추가해보는 경험을 함으로써 Windows Control 개발에 대한 경험과 고민, 생각들을 정리하고 이 글을 읽는 독자들과 함께 공유하고자 한다.

 

단계별로 하나씩 개발하면서 생각했던 사항과 구현된 소스를 같이 리뷰하며 보다 나은 방법을 고민하는 시간을 가질 수 있을 것으로 생각한다.

 

먼저  Grid Control에 대한 기본 구성은 

  • 헤더(Header) - Column에 대한 타이틀
  • Cell - 특정 Row와 Column으로 선택된 영역
  • 컬럼(Column) - 헤더(Header)에 종속되는 영역
  • 로우(Row) - 컬럼(Column)들로 구성된 하나의 행

으로 정의할 수 있다.

 

먼저 Grid Control의 Layout를 만들기 위해서 필요한 사항 부터 정리를 해보자.

  1. User Control을 생성 (WANIGrid)
  2. User Control에 데이터가 Grid Control 사이즈를 초과할 경우 필요한 VScrollBar와 HScrollBar 추가
  3. Control 사이즈에 맞추어 VScrollBar와 HScrollBar가 Bottom과 Right 영역에 위치하도록 설정

앞에서 이야기한 3개의 기능을 구현하기 위한 솔루션을 만들어 보기로 하자.

1. 프로젝트 또는 솔루션 열기를 선택

2. 새 프로젝트 만들기에서 Windows Forms 컨트롤 라이브러리(.Net Framework)를 선택하고 다음 버튼을 클릭한다.

3. 새 프로젝트 구성에서 프로젝트 이름과 폴더 위치를 선택해서 만들기 버튼을 클릭.

이렇게 해서 Grid Control을 만들기 위한 솔루션 및 프로젝트 구성은 완료되었다.

 

다음 과정에서는 만들어진 User Control 상에서 VScrollBar와 HScrollBar를 위치 시켜서 만들어보기로 하자.

여기까지 만든 소스 코드는 아래의 파일을 다운로드한다.

WANI Grid.zip
0.17MB

반응형
반응형

오랜 기간 Java와 Spring Framework으로 Web 기반의 어플리케이션을 주로 개발 및 운영을 해왔던 나로서는 C#으로 시작하는 Windows 기반의 프로그램 개발이 굉장히 생소했다.

개발 언어로만 본다면 문법과 예약어 등은 크게 어려울 것이 없었으나, Windows 기반의 경험이 신입사원 시절에 다루어본 Visual Basic 6 외에는 전무했던 상황에서 C# 언어로 Windows 기반의 WinForm 어플리케이션에서 사용하는 Control 개발 및 유지보수는 상당히 버거운 작업이었다.

1년 반 정도의 고된 시간을 보냈고 지금도 고된 시간을 버티며 하나씩 깨우쳐 나가는 나의 모습을 기록해보고자 한다.

데이터를 기반으로 시각화 된 컨트롤 객체와 그래픽 기반의 UI 생성 및 유지보수는 생소하고 낯선 영역이었기에 정말 너무나 힘든 부분이었던 것 같다.

지금은 조금 나아져서 어느 정도 이해와 왜 이렇게 해야만 하는지를 조금씩 알게 되었지만 이전에는 왜 이렇게 해야 하는지 어떻게 해야 하는지를 예측이나 가늠할 수 없었던 시기가 있었다.

 

나의 Windows 프로그램 개발 경험은 1997년 입사해서 Visual Basic 6 으로 개발을 시작했지만 2000년 접어 들면서 Java 기반의 Web 개발로 업무가 바뀌게 되었고 그 이후로 주욱 Java 기반의 Web 개발 및 운영을 해왔다.

개발 언어로서 Java를 25년 이상 사용해오다 2017년 하반기 부터 C#을 주요 개발 언어로 사용하기 시작했다.

이때 맡게된 시스템의 운영과 개발 업무를 시작하면서 부딪히게 되는 숱한 난관들에 얼마나 많은 좌절과 의기소침의 시간을 보냈는지 모른다.

 

C# 이라는 언어는 익혔지만 이걸로 뭘 할지...업무에 적용해야 하는 컨트롤들은 어떻게 만들어야 할지...상용/오픈소스 기반의 Grid Control 들은 어떻게 만들 수 있는지... 등등 

숱한 궁금증과 더불어 내가 생각하는 Grid Control을 만들어 보면 어떨까하는 생각에 시작을 하게 되었다.

먼저 간단한 기능 중심의 Grid Control을 만들면서 분석/설계에 대한 내용과 향 후 개선해야 할 점 등을 정리하고 점차 개선되어져 가는 Grid Control을 소개할 예정이다.

만들어진 Grid Control 소스는 공개를 할 것이며, 누구나 자유롭게 이용할 수 있도록 할 것이다.

 

이 블로그는 C# 개발자로 이제 막 시작하는 초보 개발자인 내가 C#과 Windows에 대해서 익히고 생각했던 내용들을 정리하면서 나 스스로가 향상되고 있음을 느끼고 조금씩 성장하는 나의 모습의 기록이기도 하다, 

이제 막 개발의 길로 들어선 초보개발자나 나와 같은 상황에서 C# 기반의 Windows 프로그램을 시작하는 이들에게 조금이나마 내 글이 도움이 될 수 있었으면 한다.

 

 

반응형

+ Recent posts