반응형

이전 시간에 일/주차/년월 정보의 Header를 구성하는 YearMonthWeekNoDayHeader.cs 파일 설정과 내용을 살펴 보았다. 오늘은 실제 일/주차/년월 정보의 Header를 그리는 YearMonthWeekNoDayHeaderGenerator.cs 파일을 살펴보기로 하자.

실제 WANIGrid의 Header 영역을 직접 생성하는 클래스 중 기본 WANIGrid의 Header를 생성하는 클래스인  DefaultHeaderGenerator.cs를 살펴 보았다.

이번에는 일/주차/년월 Header를 생성하는 YearMonthWeekNoDayHeaderGenerator.cs를 만들어 볼 것이다.

YearMonthWeekNoDayHeaderGenerator클래스는 HeaderGenerator클래스를 상속 받아서 구현한다.

YearMonthWeekNoDayHeaderGenerator클래스에는 고정 컬럼이 있을 경우와 없을 경우의 DrawHeaders라는 메소드외에 몇몇 메소드들을 재정의해서 Header영역을 그리는 부분을 구현해야 한다.

 

    public class YearMonthWeekNoDayHeaderGenerator : HeaderGenerator
    {        
        #region 생성자
        public YearMonthWeekNoDayHeaderGenerator()
        {
            TopHeaderHeight = 60;
            this._headers.Clear();
        }
        #endregion 생성자       

        public override void AddHeaders(object obj)
        {
            this._headers.Add(obj as Header);
        }

        public override List GetHeaders()
        {
            return this._headers;
        }

        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;
            int calendarStartX = 0;            

            graphics.FillRectangle(brush, columnStartX + 1, 1, leftHeaderWidth, topHeaderHeight);
            graphics.DrawRectangle(pen, columnStartX + 1, 1, leftHeaderWidth, topHeaderHeight);

            columnStartX += leftHeaderWidth;  //첫 시작컬럼의 폭을 leftHeaderWidth 만큼 설정 

            //주차 기간을 저장하기 위한 Dictionary - width 계산을 위해 필요
            Dictionary<int, List> dicWeekNo = new Dictionary<int, List>();
            //년월 기간을 저장하기 위한 Dictionary - width 계산을 위해 필요
            Dictionary<string, List> dicYearMonth = new Dictionary<string, List>();

            for (int i = firstVisibleCol; i <= lastVisibleCol; i++)
            {
                if (!this._headers[i].Visible) continue;
                int headerWidth = this._headers[i].Width;   //i 번째 컬럼의 폭을 설정                                    

                //보여지는 컬럼의 폭이 컨트롤의 폭 보다 클경우
                if (columnStartX + headerWidth > controlWidth)
                {
                    headerWidth = controlWidth - columnStartX - 3;
                    if (lastVisibleCol == (this._headers.Count - 1)) IsLargeLastCol = true;
                }
                else
                {
                    IsLargeLastCol = false;
                }

                YearMonthWeekNoDayHeader yearMonthHeader = this._headers[i] as YearMonthWeekNoDayHeader;
                if (yearMonthHeader.GetDateTime <= DateTime.MinValue)
                {
                    //Grid Header를 그린다.
                    DrawTextUtil.DrawGridHeaderRectangleAndText(graphics, brush, blackBrush, pen, this._headers, headerFont,

                                                                              i, columnStartX, headerWidth, topHeaderHeight);
                }
                else
                {
                    if (calendarStartX == 0) calendarStartX = columnStartX;

                    //Calendar 날짜 형태의 컬럼 Header 그리기(일)
                    DrawTextUtil.DrawGridHeaderCalendarRectangleAndText(graphics, brush, blackBrush, pen, 

                                                                                         this._headers, headerFont, i, columnStartX, headerWidth);
                    if (!dicWeekNo.ContainsKey(yearMonthHeader.WeekNumber)) dicWeekNo.Add(yearMonthHeader.WeekNumber, new List());
                    if (yearMonthHeader.Visible) dicWeekNo[yearMonthHeader.WeekNumber].Add(yearMonthHeader.Width);
                    if (!dicYearMonth.ContainsKey(yearMonthHeader.YearMonth)) dicYearMonth.Add(yearMonthHeader.YearMonth, new List());
                    if (yearMonthHeader.Visible) dicYearMonth[yearMonthHeader.YearMonth].Add(yearMonthHeader.Width);
                }
                columnStartX += headerWidth;
            }
            //Calendar 날짜 형태의 컬럼 Header 그리기(주차/년월)
            DrawTextUtil.DrawGridHeaderCalendarWeekNumberRectangleAndText(graphics, brush, blackBrush, pen, 

                                                                                                   headerFont, dicWeekNo, calendarStartX);
            DrawTextUtil.DrawGridHeaderCalendarYearMonthRectangleAndText(graphics, brush, blackBrush, pen, 

                                                                                                headerFont, dicYearMonth, calendarStartX);            
        }
        //고정컬럼이 있을 경우 Header 그리기 
        public override void DrawHeaders(int colFixed, int firstVisibleCol, int lastVisibleCol, int controlWidth, Graphics graphics, 

                                                      Rectangle rect, bool fixedColEditable)
        {
            SolidBrush brush = new SolidBrush(SystemColors.ControlLight);
            Pen pen = new Pen(Color.LightGray);

            int columnStartX = 0;
            int calendarStartX = 0;
            graphics.FillRectangle(brush, columnStartX + 1, 1, leftHeaderWidth, topHeaderHeight);
            graphics.DrawRectangle(pen, columnStartX + 1, 1, leftHeaderWidth, topHeaderHeight);

            columnStartX += leftHeaderWidth;  //첫 시작컬럼의 폭을 leftHeaderWidth 만큼 설정             
            int fixCol = this.GetLastFixedCol(colFixed);
            //주차 기간을 저장하기 위한 Dictionary - width 계산을 위해 필요
            Dictionary<int, List> dicWeekNo = new Dictionary<int, List>();
            //년월 기간을 저장하기 위한 Dictionary - width 계산을 위해 필요
            Dictionary<string, List> dicYearMonth = new Dictionary<string, List>();
            
            for (int i = 0; i <= fixCol; i++)
            {
                if (!this._headers[i].Visible) continue;
                int headerWidth = this._headers[i].Width;   //i 번째 컬럼의 폭을 설정  

                //고정컬럼 수정여부 확인
                if (!fixedColEditable) this._headers[i].Editable = false;

                //보여지는 컬럼의 폭이 컨트롤의 폭 보다 클경우
                if (columnStartX + headerWidth > controlWidth)
                {
                    headerWidth = controlWidth - columnStartX - 3;
                    if (lastVisibleCol == (this._headers.Count - 1)) IsLargeLastCol = true;
                }
                else
                {
                    IsLargeLastCol = false;
                }

                YearMonthWeekNoDayHeader yearMonthHeader = this._headers[i] as YearMonthWeekNoDayHeader;            
                DrawTextUtil.DrawGridHeaderRectangleAndText(graphics, brush, blackBrush, pen, this._headers, headerFont, 

                                                                          i, columnStartX, headerWidth, topHeaderHeight);
                
                columnStartX += headerWidth;                
            }            

            for (int i = firstVisibleCol + fixCol + 1; i <= lastVisibleCol && i < this._headers.Count; i++)
            {
                if (!this._headers[i].Visible) continue;
                int headerWidth = this._headers[i].Width;   //i 번째 컬럼의 폭을 설정                                    

                //보여지는 컬럼의 폭이 컨트롤의 폭 보다 클경우
                if (columnStartX + headerWidth > controlWidth)
                {
                    headerWidth = controlWidth - columnStartX - 3;
                    if (lastVisibleCol == (this._headers.Count - 1)) IsLargeLastCol = true;
                }
                else
                {
                    IsLargeLastCol = false;
                }                

                YearMonthWeekNoDayHeader yearMonthHeader = this._headers[i] as YearMonthWeekNoDayHeader;
                if (yearMonthHeader.GetDateTime <= DateTime.MinValue)
                {
                    //Grid Header를 그린다.
                    DrawTextUtil.DrawGridHeaderRectangleAndText(graphics, brush, blackBrush, pen, this._headers, headerFont, 

                                                                              i, columnStartX, headerWidth, topHeaderHeight);
                }
                else //Calendar 날짜 형태의 컬럼을 그리기 위한 사전 준비
                {
                    if (calendarStartX == 0) calendarStartX = columnStartX;                    
                    if (!dicWeekNo.ContainsKey(yearMonthHeader.WeekNumber)) dicWeekNo.Add(yearMonthHeader.WeekNumber, new List());
                    if (yearMonthHeader.Visible) dicWeekNo[yearMonthHeader.WeekNumber].Add(yearMonthHeader.Width);
                    if (!dicYearMonth.ContainsKey(yearMonthHeader.YearMonth)) dicYearMonth.Add(yearMonthHeader.YearMonth, new List());
                    if (yearMonthHeader.Visible) dicYearMonth[yearMonthHeader.YearMonth].Add(yearMonthHeader.Width);

                    //Calendar 날짜 형태의 컬럼 Header 그리기(일)
                    DrawTextUtil.DrawGridHeaderCalendarRectangleAndText(graphics, brush, blackBrush, pen, this._headers, headerFont, 

                                                                                         i, columnStartX, headerWidth);
                }

                columnStartX += headerWidth;
            }
            //Calendar 날짜 형태의 컬럼 Header 그리기(주차/년월)
            DrawTextUtil.DrawGridHeaderCalendarWeekNumberRectangleAndText(graphics, brush, blackBrush, pen, 

                                                                                                   headerFont, dicWeekNo, calendarStartX);
            DrawTextUtil.DrawGridHeaderCalendarYearMonthRectangleAndText(graphics, brush, blackBrush, pen, 

                                                                                               headerFont, dicYearMonth, calendarStartX);
        }        
    }

 

실제 Header 영역에 text를 출력하기 위한 DrawTextUtil이라는 클래스를 추가한다.

DrawTextUtil 클래스는 Header영역의 텍스트를 출력하기 위한 클래스로 헤드 영역의 Rectangle 사이즈에 맞는 폰트 사이즈를 계산해서 적정한 크기의 Header Title을 출력하는 역할을 한다.

 

    public static class DrawTextUtil
    {
        /// Grid Header를 그린다.
        public static void DrawGridHeaderRectangleAndText(Graphics graphics, SolidBrush brush, SolidBrush blackBrush, Pen pen, 

             List headers, Font headerFont, int index, int columnStartX, int headerWidth, int topHeaderHeight)
        {
            if (headerWidth == 0) return;
            //헤더영역의 사각형을 채우고 테두리를 그린다.
            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);
            if (headers == null)
            {
                graphics.DrawString("", headerFont, blackBrush, colRec, sf);
            }
            else
            {
                graphics.DrawString(headers[index].Title, headerFont, blackBrush, colRec, sf);
            }
        }

        /// Grid Calendar Day Header를 그린다.
        public static void DrawGridHeaderCalendarRectangleAndText(Graphics graphics, SolidBrush brush, SolidBrush blackBrush, Pen pen,
                 List headers, Font headerFont, int index, int columnStartX, int headerWidth)
        {
            //일자 출력
            if (headerWidth == 0) return;
            int dayYLocation = 32;
            int dayHeight = 28;

            //헤더영역의 사각형을 채우고 테두리를 그린다.
            graphics.FillRectangle(brush, columnStartX + 1, dayYLocation + 1, headerWidth, dayHeight);
            graphics.DrawRectangle(pen, columnStartX + 1, dayYLocation + 1, headerWidth, dayHeight);

            //헤더 타이틀 정렬 방법 설정
            StringFormat sf = new StringFormat();
            sf.LineAlignment = StringAlignment.Center;
            sf.Alignment = StringUtil.GetStringAlignment(sf, HorizontalAlignment.Center);            

            //헤더 타이틀을 그린다.
            Rectangle colRec = new Rectangle(columnStartX + 1, dayYLocation + 1, headerWidth, dayHeight);
            Rectangle colRec1 = new Rectangle(columnStartX + 1, dayYLocation + 1, headerWidth, dayHeight / 2);
            Rectangle colRec2 = new Rectangle(columnStartX + 1, dayYLocation + (dayHeight / 2) + 1, headerWidth, dayHeight / 2);
            if (headers == null)
            {
                graphics.DrawString("", headerFont, blackBrush, colRec, sf);
            }
            else
            {                
                if (headers[index].Width >= 5 && headers[index].Width < 15)
                {
                    Font dayFont = CalculateFontSize(graphics, headers[index].Title.Substring(0, 1), headerFont, columnStartX, 

                                                                  dayYLocation, headerWidth, dayHeight);
                    if (dayFont.Size > 8.3) dayFont = new Font(headerFont.Name, 8.4f);
                    graphics.DrawString(headers[index].Title.Substring(0,1), dayFont, blackBrush, colRec1, sf);
                    graphics.DrawString(headers[index].Title.Substring(1,1), dayFont, blackBrush, colRec2, sf);
                } else
                {
                    Font calHeadFont = CalculateFontSize(graphics, headers[index].Title, headerFont, columnStartX, dayYLocation, 

                                                                       headerWidth, dayHeight);
                    if (calHeadFont.Size > 8.3) calHeadFont = new Font(headerFont.Name, 8.4f);
                    graphics.DrawString(headers[index].Title, calHeadFont, blackBrush, colRec, sf);                    
                }
            }                        
        }

        /// Grid Calendar Week Number Header를 그린다.
        public static void DrawGridHeaderCalendarWeekNumberRectangleAndText(Graphics graphics, SolidBrush brush, SolidBrush blackBrush,

                      Pen pen, Font headerFont, Dictionary<int, List> dicWeekNo, int columnStartX)
        {
            int dayYLocation = 16;
            int weekNoHeight = 16;
            foreach (KeyValuePair<int, List> items in dicWeekNo)
            {
                int weekWidth = items.Value.Sum();
                //헤더영역의 사각형을 채우고 테두리를 그린다.
                graphics.FillRectangle(brush, columnStartX + 1, dayYLocation + 1, weekWidth, weekNoHeight);
                graphics.DrawRectangle(pen, columnStartX + 1, dayYLocation + 1, weekWidth, weekNoHeight);

                //헤더 타이틀 정렬 방법 설정
                StringFormat sf = new StringFormat();
                sf.LineAlignment = StringAlignment.Center;
                sf.Alignment = StringUtil.GetStringAlignment(sf, HorizontalAlignment.Center);

                //헤더 타이틀을 그린다.
                Rectangle colRec = new Rectangle(columnStartX + 1, dayYLocation + 1, weekWidth, weekNoHeight);
                //적절한 폰트 사이즈를 계산
                Font calHeadFont = CalculateFontSize(graphics, items.Key.ToString(), headerFont, columnStartX, dayYLocation, 

                                                                   weekWidth, weekNoHeight);
                graphics.DrawString(items.Key.ToString(), calHeadFont, blackBrush, colRec, sf);

                columnStartX += weekWidth;
            }                        
        }

        /// Grid Calendar Year Month Header를 그린다.
        public static void DrawGridHeaderCalendarYearMonthRectangleAndText(Graphics graphics, SolidBrush brush, SolidBrush blackBrush,

                   Pen pen, Font headerFont, Dictionary<string, List> dicYearMonth, int columnStartX)
        {
            int dayYLocation = 0;
            int weekNoHeight = 16;
            foreach (KeyValuePair<string, List> items in dicYearMonth)
            {
                int yearMonthWidth = items.Value.Sum();
                //헤더영역의 사각형을 채우고 테두리를 그린다.
                graphics.FillRectangle(brush, columnStartX + 1, dayYLocation + 1, yearMonthWidth, weekNoHeight);
                graphics.DrawRectangle(pen, columnStartX + 1, dayYLocation + 1, yearMonthWidth, weekNoHeight);

                //헤더 타이틀 정렬 방법 설정
                StringFormat sf = new StringFormat();
                sf.LineAlignment = StringAlignment.Center;
                sf.Alignment = StringUtil.GetStringAlignment(sf, HorizontalAlignment.Center);

                //헤더 타이틀을 그린다.
                Rectangle colRec = new Rectangle(columnStartX + 1, dayYLocation + 1, yearMonthWidth, weekNoHeight);

                Font calHeadFont = CalculateFontSize(graphics, items.Key.ToString(), headerFont, columnStartX, dayYLocation, 

                                                                   yearMonthWidth, weekNoHeight);
                graphics.DrawString(items.Key.ToString(), calHeadFont, blackBrush, colRec, sf);                

                columnStartX += yearMonthWidth;
            }
        }

        /// 사각형 사이즈에 맞는 폰트 크기를 계산한다.
        public static Font CalculateFontSize(Graphics g, string header, Font headerFont, int columnStartX, int columnStartY,

                     int headerWidth, int headerHeight)
        {
            Single Factor, FactorX, FactorY;
            Rectangle rec = new Rectangle(columnStartX + 1, columnStartY + 1, headerWidth, headerHeight);
            SizeF sz = g.MeasureString(header, headerFont);

            FactorX = headerWidth / sz.Width;
            FactorY = headerHeight / sz.Height;

            if (FactorX > FactorY) Factor = FactorY;
            else Factor = FactorX;

            if (Factor > 0)
                return new Font(headerFont.Name, headerFont.SizeInPoints * Factor);
            else
                return new Font(headerFont.Name, headerFont.SizeInPoints * 0.1f);
        }        
    }

 

추가된 YearMonthWeekNoDayHeaderGenerator 클래스를 반영하기 위해 Grid 클래스(Grid.cs)에서 추가 및 보완해야 할 부분을 살펴보자.

YearMonthWeekNoDayHeaderGenerator 영역에서 헤더 영역의 주차/년월 정보를 그리기 위해서 생성했던 Dictionary 변수를 Grid클래스를 통해서 접근할 수 있도록 변수 및 속성(Property)를 추가한다.

    private Dictionary<int, List> dicWeekDay = null; //주차
    private Dictionary<string, List> dicMonthDay = null; //년월

 

    public Dictionary<int, List> DicWeekDay
    {
        get { return dicWeekDay; }
        set { dicWeekDay = value; }
    }

    public Dictionary<string, List> DicMonthDay
    {
        get { return dicMonthDay; }
        set { dicMonthDay = value; }
    }

 

WANIGrid클래스에서 Header 영역을 그리기 위해 호출하는 DrawHeader 메소드의 파라미터를 추가한다.

추가된 파라미터는 고정컬럼을 수정할지/수정을 하지 못하도록 할 것인지를 결정하는 bool 값이다.

고정컬럼이 지정되어져 있을 경우, 고정컬럼에 대한 수정 여부를 넘겨주는 값이다.

    public void DrawHeader(Graphics graphics, Rectangle rect, int clientWidth, int colFixed, bool fixedColEditable)
    {
        if (headerGen != null)
        {
            if (colFixed < 0) colFixed = 0;
            if (colFixed == 0)
            {                    
                headerGen.DrawHeaders(firstVisibleCol, lastVisibleCol, clientWidth, graphics, rect);
            }
            else
            {
                headerGen.DrawHeaders(colFixed, firstVisibleCol, lastVisibleCol, clientWidth, graphics, rect, fixedColEditable);
            }
        }
    }

 

지금까지는 새로 추가된 HeaderGenerator인 YearMonthWeekNoDayHeaderGenerator클래스에 대해서 살펴 보았다.

다음에는 이렇게 추가한 Header영역의 클래스를 기반으로 WANIGrid클래스(WANIGrid.cs)에서의 추가/보완 사항들을 하나씩 살펴보기로 하자.

반응형
반응형

보통 데이터베이스에서 가져온 데이터를 Grid Control에 보여주게 된다.

WANIGrid Control의 DataSource Property에 DataTable을 할당할 수 있도록 기능을 추가하도록 하겠다.

Oracle 또는 MS SQL 등에서 쿼리나 프로시저 등을 이용해서 데이터를 가져올 때 DataTable 또는 DataSet으로 결과 값을 할당해서 반환하게 된다.

데이터베이스에서 가져온 값, 즉 DataTable을 바로 WANIGrid Control의 Property인 DataSource에 할당함으로써 DB에서 가져온 값을 WANIGrid Control에 Display할 수 있게 된다.

 

먼저 Header.cs 파일에서 컬럼(Column)에 대한 수정 여부를 설정할 수 있는 변수와 Property를 추가하도록 한다.

    private bool editable = true;   //컬럼 편집여부

      :

    public bool Editable
    {
        get { return editable; }
        set { editable = value; }
    }

 

그리고 DefaultHeader.cs 파일에 Editable 값을 파라미터 받는 생성자를 하나 더 추가하도록 한다.

    public DefaultHeader(string fieldName, string title, int width, HorizontalAlignment headAlign, HorizontalAlignment txtAlign, 

                               bool visible, bool editable)
    {
        this.ColumnId = fieldName;
        this.Title = title;
        this.Width = width;
        this.HeadAlign = headAlign;
        this.TextAlign = txtAlign;
        this.Visible = visible;
        this.Editable = editable;
    }

 

WANIGrid.Method.cs 파일을 열어서 기능 추가 및 보완을 하도록 한다.

먼저 앞서 추가한 Editable Property의 값에 따라 특정 컬럼(Column)의 값을 변경할 수 있게 또는 변경할 수 없도록 체크 로직을 BeginEdit 메소드에 추가하도록 한다.

아래의 파란색 부분이 새롭게 추가된 영역이다.

    private void BeginEdit()
    {
        if (readOnly) return;
        if (grid.GridHeaderList[ActiveCell.Col].Editable == false) return;
        if (ActiveCell.Col != -1 && ActiveCell.Row != -1)
        {
            string tempStr = "";
            if (rows[ActiveCell.Row].DataRow[grid.GridHeaderList[ActiveCell.Col].ColumnId] != null)
            {
                tempStr = rows[ActiveCell.Row].DataRow[grid.GridHeaderList[ActiveCell.Col].ColumnId].ToString();
            }
            //TextBox에 입력된 값을 설정하고 TextBox 속성의 값을 설정한다.
            editBox.Text = tempStr;
            Rectangle r = GetSelectedCellRect(ActiveCell.Row, ActiveCell.Col);
            editBox.Left = r.Left + 3;
            editBox.Top = r.Top + 3;
            editBox.Height = r.Height;
            editBox.Width = r.Width - 3;
            editBox.Visible = true;
            editBox.TextAlign = grid.GridHeaderList[ActiveCell.Col].TextAlign;  //컬럼의 정렬형태에 따라 Text를 정렬
            editBox.Focus();
            ActiveCell_ActvieCol = ActiveCell.Col; //ActivieCell_ActiveCol 값을 설정   
        }
    }

 

DataBase에서 가져온 DataTable을 WANIGrid Control의 DataSource Property에 설정하는 메소드를 추가한다.

    public void SetDataTable(DataTable dt)
    {
        if (dt == null) return;
        if (dataSource.Rows.Count > 0)
        {             
            rows.Clear();
            allRowsHeight = 0;
        }
        int count = 0;
        foreach (DataRow row in dt.Rows)
        {
            Row r = new Row(row);
            rows.Insert(count, r);
            if (rowHeight == 0) rowHeight = Font.Height + 4;
            allRowsHeight += rowHeight;
            count++;
        }
        Invalidate();
    }

 

WINIGrid.cs파일을 열어서 SetDataTable 메소드를 호출해야 하는 DataSource Propery의 set 메소드에 아래와 같이 코드를 추가하도록 한다.

    public DataTable DataSource
    {
        get { return dataSource; }
        set {                
            dataSource = value;
            SetDataTable(dataSource);
        }
    }

 

DataTable 가져오기 기능을 통해서 WANIGrid Control에 실제 값들이 보여지도록 하고 보니 세로 스크롤바의 로직을 추가로 보완하게 되었다.

WANIGrid.Event.cs파일을 열어서 VScrollBar_Scroll 메소드를 아래와 같이 변경한다.

아래의 파란색 부분이 변경된 영역이다.

    private void VScrollBar_Scroll(object sender, ScrollEventArgs e)
    {
        EndEdit();
        firstVisibleRow = e.NewValue / rowHeight;            

        if (firstVisibleRow > (allRowsHeight / rowHeight) - (Height / rowHeight) + 5)
        {
            firstVisibleRow = (allRowsHeight / rowHeight) - (Height / rowHeight) + 5;
            grid.FirstVisibleRow = firstVisibleRow;
            vScrollBar.Value = vScrollBar.Maximum;
        }
        else
        {
            grid.FirstVisibleRow = firstVisibleRow;
            vScrollBar.Value = firstVisibleRow * rowHeight;                
        }

        CalcVisibleRange();
        ReCalcScrollBars();
        Invalidate();
    }

 

추가로 Mouse_Wheel 메소드 또한 아래의 파란색 부분과 같이 변경하도록 한다

    private void Mouse_Wheel(object sender, MouseEventArgs e)
    {
        EndEdit();
        //Control Key를 누르고 Wheel을 돌렸을 경우는 HScrollBar와 동일
        if (ModifierKeys == Keys.Control)
        {
            if ((e.Delta / 120) > 0)  //업의 경우에는 좌측으로 이동
            {
                firstVisibleCol -= 2;
                if (firstVisibleCol < 0) firstVisibleCol = 0;
                grid.FirstVisibleCol = firstVisibleCol;
                hScrollBar.Value = firstVisibleCol;
            }
            else //다운의 경우에는 우측으로 이동
            {
                firstVisibleCol += 2;
                if (firstVisibleCol >= (grid.GridHeaderList.Count - 1)) firstVisibleCol = grid.GridHeaderList.Count - 3;
                grid.FirstVisibleCol = firstVisibleCol;
                hScrollBar.Value = hScrollBar.Maximum;
            }
        }
        else //Wheel만 움직였을 경우에는 VScrollBar와 동일
        {
            if (firstVisibleRow < 0) return;

            if ((e.Delta / 120) > 0) //업의 경우 위쪽으로 이동
            {
                firstVisibleRow -= 2;
                if (firstVisibleRow < 0)
                {
                    firstVisibleRow = 0;
                    grid.FirstVisibleRow = firstVisibleRow;
                    vScrollBar.Value = 0;
                }
                else
                {
                    grid.FirstVisibleRow = firstVisibleRow;
                    vScrollBar.Value = firstVisibleRow * rowHeight;
                }
            }
            else //다운의 경우에는 아래쪽으로 이동
            {
                if (rowsCount == 0) return; // rowsCount가 0일 경우는 Row의 Height가 Control Height를 넘지 않았음
                firstVisibleRow += 2;
                if (firstVisibleRow > (allRowsHeight / rowHeight) - (Height / rowHeight) + 5)
                {
                    firstVisibleRow = rowsCount - (Height / rowHeight) + 5;
                    grid.FirstVisibleRow = firstVisibleRow;
                    vScrollBar.Value = vScrollBar.Maximum;
                }
                else
                {
                    grid.FirstVisibleRow = firstVisibleRow;
                    vScrollBar.Value = firstVisibleRow * rowHeight;
                }
            }
        }
        CalcVisibleRange();
        ReCalcScrollBars();
        Invalidate();
    }

 

WANIGridTest 프로젝트의 WANIGridTest.cs 파일을 열어서 DataTable에 데이터를 생성하기 위한 makeDataTable() 메소드를 추가하고, [DataTable 가져오기] 버튼을 추가한다.

WANIGridTest.cs 의 소스는 아래에 첨부한 소스를 참조하도록 하자.

[DataTable 가져오기] 버튼을 클릭하면 아래와 같이 WANIGrid Control에 데이터를 보여주게 된다.

[그림 21-1] DataTable 가져오기

지금까지의 소스는 아래의 파일을 참조하자.

WANI Grid_20190916.zip
0.49MB

 

반응형
반응형

기본적인 Grid Control의 기능은 만들었지만 왼쪽의 특정 열들을 고정하고자 할 경우의 기능을 추가해 보기로 하겠다.

좌측 컬럼(Column)의 갯수를 지정하면 지정된 컬럼들은 움직이지 않고 고정되도록 한다.

아래의 그림 처럼 가로 스크롤바를 움직였을 때 고정컬럼 개수를 2개를 지정했을 때 컬럼 헤드 속성의 Visible이 True인 2개의 컬럼을 움직이지 않고 고정되도록 처리하는 것을 말한다.

[그림 19-1] 컬럼 고정 첫 화면
[그림 19-2] 컬럼 고정 두 번째
[그림 19-3] 컬럼 고정 세 번째

WANIGrid.cs 파일에 컬럼 고정을 관리하기 위한 변수를 선언한다.

private int colFixed = 0;   //고정 컬럼 개수

private SolidBrush colFixBrush = new SolidBrush(SystemColors.ControlLight);  //고정컬럼의 배경색

 

그리고 고정 컬럼의 개수를 외부에서 지정 및 변경할 수 있도록 Property를 작성한다.

    /// <summary>
    /// 고정 컬럼 개수
    /// </summary>
    public int ColFixed
    {
        get { return colFixed; }
        set { colFixed = value; }
    }

 

HeaderGenerator.cs 파일을 열어서 고정 컬럼이 지정되었을 때 호출되는 DrawHeaders 추상메소드를 하나 더 추가한다.

기존의 DrawHeaders 추상메소드와의 차이점은 파라미터로 int colFixed가 하나 더 추가된 것이다.

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

 

HeaderGenerator.cs에 GetLastFixedCol(int colFixed) 메소드를 추가한다.

고정 컬럼 갯수를 파라미터로 입력하면 입력된 헤더 정보 중 Visible 값이 true인 컬럼만 확인해서 고정 컬럼의 마지막 인덱스 값을 반환하는 메소드이다.

    /// <summary>

    /// FixedCol 수에 맞는 Header의 Index값을 리턴
    /// fixedCol 2이지만 실제 Header Column의 Visible 값이 false인 경우 제외하고 실제 Header Column의 Index를 구한다.

    /// </summary>

    protected int GetLastFixedCol(int colFixed)
    {
        int lastFixedCol = 0;
        int startIndex = 0;
        foreach (Header head in _headers)
        {
            if (startIndex == colFixed) break;
            if (head.Visible)
            {
                lastFixedCol = head.Index;
                startIndex++;
            }
        }
        return lastFixedCol;
    }

 

Grid.cs 파일의 DrawHeader 메소드에 기존의 DrawHeaders 메소드 외에 파라미터로 colFixed가 추가된 DrawHeader를 구현하면 된다.

고정 컬럼의 폭 만큼은 남겨두고 고정 컬럼 이 후의 컬럼들이 가로 스크롤바로 움직였을 경우 사용자에게 보여지도록 로직을 추가해서 만들면 된다.

좀 더 자세히 설명을 한다면

  • 고정 컬럼의 헤더를 그린다
  • 고정 컬럼 이후의 컬럼들에 대해서는 고정 컬럼 전체의 폭 이후 부터 그리도록 처리한다.

이 부분의 코드는 아래의 소스를 참고하기 바란다.

 

지금까지 작성한 코드에 많은 부분이 미진하지만 필자의 눈에 보완이 필요한 부분과 버그 부분을 조치하고자 한다. 지금 눈에 밟히는 미진함과 버그를 빨리 처리하지 않으면 뒤에 가서 쏟아 부어야 할 노력이 더 많다는 걸 알기에 생각 날때 가능한 빨리 조치하는 것이 정신건강에 좋다.

배우는 학생의 입장에서 WinForm기반의 User Control을 만들어가는 과정을 세세하게 기록해보고자 하는 바램도 있었기에 부끄럽지만 내부의 문제와 버그를 밖으로 드러내 처리한 결과를 같이 공유하고 나의 기록으로서 남기고자 한다.

 

기존에는 고정 컬럼에 대한 고려를 하지 않은 상태에서 코드를 작성하다 보니 고정 컬럼을 적용했을 때 많은 부분에서 문제가 되는 부분이 있었다.

주로 발생한 영역이 WANIGrid.Event.cs/WANIGrid.Method.cs 파일이었다.

물론 기존에도 문제가 되었지만 모르고 있다가 이번에 고정 컬럼 부분이 추가되면서 더 눈에 띄게 된 경우도 있다.

 

WANIGrid.Even.cs 파일에서는 화면을 출력하는 메소드인 WANIGrid_Paint 메소드의 DrawHeaders 메소드 호출 시에 입력 파라미터가 하나 더 추가되어 변경되었다.

    private void WANIGrid_Paint(object sender, PaintEventArgs e)
    {
        CalcVisibleRange();
        ReCalcScrollBars();
        DrawBackground(e.Graphics, rc);
        DrawHeaders(e.Graphics, rc, colFixed);
        DrawContent(e.Graphics, rc, this.ClientRectangle.Width);
        DrawActiveCell(e.Graphics);
    }

 

그리고 가로 스크롤바를 움직였을 때 호출되는 HScrollBar_Scroll 메소드의 로직의 보완되었다.

입력된 스크롤바의 값을 기반으로 firstVisibleCol과 lastVisibleCol의 값을 설정하는 부분을 보완했다. 이 부분도 첨부된 소스를 참고하자.

변경 메소드 명 변경 내용
WANIGrid_Paint(object sender, PaintEventArgs e) 화면 출력하는 분분의 DrawHeaders 메소드 호출 부분의 입력 파라미터 추가로 인해 변경
WANIGrid_MouseMove(object sender, MouseEventArgs e) Grid Header 영역에서 컬럼과 컬럼 경계에서 마우스 좌측 버튼을 누른 상태에서 이동 시에 고정 컬럼 유무에 따라 리사이즈되는 필드의 정보를 제어하기 위해서 보완
HScrollBar_Scroll(object sender, ScrollEventArgs e) 가로 스크롤바 이동 시 마지막 컬럼이 보여지면 스크롤이 더 이상 되지 않도록 보완

 

 

가장 많은 변화가 있었던 WANIGrid.Method.cs 파일에서 눈여겨 봐야 할 부분들의 메소드 명으로 표기했다.

각 메소드의 변경 내역은 첨부된 소스를 참고하기 바라며, 개략적인 변경 내용 만을 기록하겠다.

변경 메소드 명 변경 내용
GetColFromX(int X) 마우스 포인터에 해당하는 Grid Column Index를 가져오는 부분에 고정 컬럼이 있고 없고에 따라 Column Index를 반환하도록 보완 
CalcVisibleRange() 화면에 보여져야 할 영역 계산하는 부분에 고정 컬럼의 유무에 따라 처리하도록 보완
DrawBackground(Graphics g, Rectangle rc) 선택된 컬럼/행에 대한 백그라운드 색상을 입히는 부분에 고정 컬럼 유무에 따라 처리 로직을 달리하도록 보완
SelectedRowChangeBackground(Graphics g, int lastFixedCol) DrawBackground 메소드 내의 선택된 행(Row)의 Backgroud를 그리는 기능을 메소드로 분리해서 추가
SelectedColChangeBackground(Graphics g, int lastFixedCol) DrawBackground 메소드 내의 선택된 열(Column)의 Background를 그리는 기능을 메소드로 분리해서 추가
DrawHeaders(Graphics g, Rectangle rc, int colFixed) 입력 파라미터 추가 (int colFixed)
DrawContent(Graphics g, Rectangle rc, int controlWidth) WANIGrid 내용을 그리는 영역에 고정 컬럼 유무에 대한 처리를 추가 보완
GetLastFixedCol() 고정 컬럼의 마지막 Index 값을 가져온다. (신규 추가)
GetFixedColWidth() 고정 컬럼의 전체 폭을 가져온다. (신규 추가)
GetSelectedCellRect(int row, int col) 선택한 셀(Cell) 영역을 반환하는 부분에 고정 컬럼 유무에 따른 처리를 추가

생각 보다 많은 부분의 변경을 요했고, 나름 테스트를 한다고 했지만 내가 발견하지 못한 버그도 많을 것으로 예상된다.

소스를 보고 개선이 필요한 부분이나 내가 미처 확인하지 못한 버그가 있다면 알려주기 바란다.

 

지금까지의 작업은 아래의 소스를 참고하기 바란다.

WANI Grid_20190903.zip
0.43MB

반응형
반응형

지금까지 작성한 코드의 라인이 벌써 1,000라인이 넘어 가고 있다. 

앞으로도 많은 기능들을 하나씩 하나씩 붙여 나갈 것인데, WinForm파일 인 WANIGrid.cs파일의 라인 수는 시간이 지날 수록 점점 더 커지게 될 것이다.

소스의 라인이 많아지면 가독성도 떨어지고 소스의 유지보수에도 많은 불편함이 따르게 된다.

하나의 WinForm파일인 WANIGrid.cs 파일 내에 사용자 UI에 관련되는 부분, 일반 Method, Event 처리 Method 등이 뒤섞여 있게 되고, 코드 라인이 길어지다 보면 아래 위로 스크롤해서 찾아 가는 것이 점점 불편하게 느껴지게 될 것이다.

애플리케이션 개발에 있어서 개발 완료가 끝이 아니라 개발 완료 이 후에 더 많은 일들이 있게 된다.

개발 완료 후 발생하는 여러 가지 오류 처리와 함께 사용자가 애플리케이션을 사용하면서 추가 개선 및 변경 요청 사항들을 접수받아 처리하는 것 또한 초기 개발하는 것 만큼이나 많은 노력을 요하게 된다.

이러한 일들을 보다 효율적으로 처리하기 위해서 유형별 기능별로 소스 코드를 분할해서 편리하게 관리할 수 있다면 일의 능률도 오르고 소스에 대한 가독성 또한 높일 수 있을 것이다.

잘 정리 정돈되지 않은 어마무시하게 긴 소스코드를 바라보며 막막함을 느끼지 않게되어 더 좋을 것이다.

 

WANIGrid 프로젝트 내의 WinForm인 WANIGrid.cs파일을 3가지 형태로 구분하고자 한다.

  • WANIGrid.cs - WANIGrid Control 내에서 사용하는 변수, 초기화, Property, 생성자 등 포함
  • WANIGrid.Event.cs - WANIGrid Control 내의 Event 처리 로직을 포함
  • WANIGrid.Method.cs - WANIGrid Control 내에서 사용되는 Method들을 포함

WANI Grid Project에서 마우스 우측 버튼을 클릭해서 [추가(D)] > [클래스(C)]를 선택해서 2개의 클래스 파일인 WANIGrid.Event.cs, WANIGrid.Method.cs 클래스 파일을 추가한다.

두 파일의 클래스 선언 시 클래스명을 WANIGrid로 변경한다.

public partial class WANIGrid

클래스 파일 추가 및 수정 후 Visual Studio를 종료한다.

 

다음에는 WANI Grid.csproj 파일을 편집기로 열어서 새롭게 추가된 WANIGrid.Event.cs, WANIGrid.Method.cs의 설정 값을 수정하자.

아래의 파란 색 부분을 변경하면 된다.

<Compile Include="WANIGrid.cs">

    <SubType>UserControl</SubType>

</Compile>

<Compile Include="WANIGrid.Designer.cs">

    <SubType>UserControl</SubType>

    <DependentUpon>WANIGrid.cs</DependentUpon>

</Compile>

<Compile Include="WANIGrid.Event.cs">

    <SubType>UserControl</SubType>

    <DependentUpon>WANIGrid.cs</DependentUpon>

</Compile>

<Compile Include="WANIGrid.Method.cs">

    <SubType>UserControl</SubType>

    <DependentUpon>WANIGrid.cs</DependentUpon>

</Compile>

 

Visual Studio를 실행해서 WANI Grid 솔루션 파일을 열면 아래와 같이 WANIGrid.cs 파일 아래에 WANIGrid.Event.cs, WANIGrid.Method.cs 파일 추가 되어져 있는 것을 알 수 있다.

[그림 18-1] 솔루션 탐색기

 

이 상태에서 각각의 영역에 맞게 기능들을 분배하면 된다.

소스를 총 3개의 파일로 분배를 하고 나면 소스를 관리하기가 좀 더 손쉬워 질 것이다.

WinForm에서 계속 소스의 라인이 늘어나는 문제가 개발자들 사이에서 많은 이슈가 되었고 가능한 기능별 또는 역할별로 파일을 쪼개서 사용하는 방법에 대해서 많은 논의가 있었던 것 같다.

그 중 하나의 방법으로 Partial Class를 이용한 WinForm의 소스 관리 방법을 적용해 보았다.

 

이렇게 주요 기능별로 쪼갠 결과 파일은 아래에서 다운 받아서 확인하면 된다.

WANI Grid_20190827.zip
0.41MB

반응형
반응형

WANIGrid Control은 지금까지의 작업으로 Grid Control의 가장 기본적인 기능들을 구현해 보았다.

Grid Header의 컬럼(Column)과 컬럼(Column) 사이의 경계 부분을 마우스 좌측 버튼 클릭 후 좌/우로 이동하면서 컬럼의 폭을 조정할 수 있는 기능을 구현해 보기로 하자.

이 기능을 구현하기 위해서 필요한 기능들을 크게 나열해 보면

  • 마우스 이동 시에 WANIGrid의 Header 영역의 X좌표를 체크해서 컬럼과 컬럼 경계선 범위에 마우스가 위치할 경우 마우스 커서의 모양을 Cursors.VSplit 으로 변경
  • 마우스 커스가 Cursors.VSplit으로 변경 상태에서 마우스 좌측 버튼이 눌러졌을 때 컬럼(Column) 폭 변경이 시작됨을 설정
  • 마우스 커서의 모양이 Cursors.VSplit 으로 변경 후 마우스 좌측 버튼을 누른 상태에서 마우스 이동 후 좌측 버튼을 놓았을 때 마우스 X 좌표를 계산해서 컬럼(Column)의 Width 값을 재계산
  • 마우스 커서를 Cursors.Default 다시 설정

마우스를 WANIGrid Header 영역의 컬럼과 컬럼 경계선에 가져가면 아래의 그림과 같이 마우스 커서가 변경된다.

[그림 16-1] 마우스 커스 변경 모습

이렇게 변경된 상태에서 마우스 좌측 버튼을 눌러서 컬럼의 폭을 원하는 사이즈 만큼 늘리거나 줄이면 된다.

이러한 작업을 하기위해서 기존의 소스에 추가 및 변경/보완 해야 할 부분들을 살펴보자.

먼저 추가해야 할 변수들을 살펴보기로 하자.

    private bool vSpliteLineMouseDown = false;  //컬럼 경계선 상에서 마우스 좌측버튼이 눌러졌는지를 저장하기 위한 변수
    private int resizeCol = 0;  //사이즈 변경이 발생한 컬럼을 저장하기 위한 변수
    private Point lastMousePoint = new Point(0, 0); //마우스 좌측 버튼을 누른 상태에서 마지막 이동 Point를 저장하기 위한 변수

 

마우스 이동 시에 WANIGrid Header 영역의 컬럼과 컬럼 경계선에 마우스가 위치하는지 체크하는 로직을 추가한다.

마우스 좌측 버튼을 누른 상태에서는 선택한 컬럼과 컬럼 경계선 상에 수직선을 그려서 현재 선택된 컬럼의 끝 부분을 가리키게끔 하는 기능과 마우스 좌측 버튼이 눌러지지 않았을 때 이동하는 마우스의 X좌표를 체크해서 마우스 커서를 변경하는 부분으로 구분된다.

MouseMove 이벤트를 생성해서 아래의 코드를 추가한다.

    private void WANIGrid_MouseMove(object sender, MouseEventArgs e)
    {
        //vSpliteLineMouseDown이 true 이면 마우스 버튼이 눌러진 상태에서 이동 중인 상태임.
        if (vSpliteLineMouseDown)
        {
            if (Math.Abs(e.X - lastMousePoint.X) > 4) DrawVSpliteLine(new Point(e.X, e.Y));
            return;
        }
        //WANIGrid Header영역의 마우스 위치를 체크

        //컬럼과 컬럼 사이의 경계선에 위치하면 Cursors.VSplit로 변경하고 resizeCol을 확인한다.
        Cursor = Cursors.Default;
        if (grid.GridHeaderList.Count > 0 && e.Y < topHeaderHeight)
        {
            int colLine = leftHeaderWidth; //각 컬럼이 끝나는 지점의 X좌표를 저장하는 변수
            for (int i = firstVisibleCol; i <= lastVisibleCol; i++)
            {
                if (!grid.GridHeaderList[i].Visible) continue;
                colLine += grid.GridHeaderList[i].Width;
                //Header의 컬럼과 컬럼 간의 경계선 상에 마우스 포인트가 위치했을 경우
                if (e.X > colLine - 2 && e.X < colLine + 2)
                {
                    Cursor = Cursors.VSplit;
                    resizeCol = i;
                    break;
                }                    
            }
        }
    }

 

마우스 좌측 버튼을 눌렀을 경우 선택된 경계 영역에 수직선을 그리는 메소드인 DrawVSpliteLine를 작성한다.

컬럼 사이즈 변경을 위해 Header영역의 컬럼과 컬럼 경계선을 선택하고 마우스 좌측 버튼을 누른 상태에서 마우스를 좌/우로 이동할 때 표시되는 세로선을 그리는 메소드이다.

    private void DrawVSpliteLine(Point pos)
    {
        int allHeight = topHeaderHeight + allRowsHeight;
        Graphics g = Graphics.FromHwnd(this.Handle);
        Rectangle rc = new Rectangle(lastMousePoint.X - 1, 1, 2, allHeight - (firstVisibleRow * rowHeight));
        Invalidate(rc);
        g.DrawLine(new Pen(Color.Gray, 2), pos.X, 1, pos.X, allHeight - (firstVisibleRow * rowHeight));
        lastMousePoint = pos;
        g.Dispose();
    }

 

최종적으로 컬럼 폭을 조정하고 난 후에 마우스 버튼을 놓았을 때 발생하는 MouseUp 이벤트에 컬럼의 변경 폭 값을 계산해서 Header 정보에 저장하고 화면을 다시 그리는 작업 및 마우스 포인트 값을 설정하는 부분을 추가한다.

    private void WANIGrid_MouseUp(object sender, MouseEventArgs e)
    {
        //컬럼 사이즈 변경이 완료 되었을 경우 현재 컬럼의 Width 값을 다시 계산해서 저장한다.
        if (vSpliteLineMouseDown)
        {
            int width = e.X - mousePoint.X;
            grid.GridHeaderList[resizeCol].Width += width;
            Invalidate();

            mousePoint.X = 0;
            mousePoint.Y = 0;
        }
        //vSpliteLineMouseDown 값을 false, Cursor를 Cursors.Default로 설정
        vSpliteLineMouseDown = false;
        Cursor = Cursors.Default;
    }

 

기존의 WANIGrid_MouseDown 이벤트에 마우스 Y좌표가 topHeaderHeight보다 작을 경우에 Cursor값을 체크해서 Cursor.VSplit인 경우에 vSpliteLineMouseDown 값을 true 그렇지 않을 경우 false를 설정하는 로직을 추가한다.

    private void WANIGrid_MouseDown(object sender, MouseEventArgs e)
    {
        //마우스 우측 버튼 클릭 시 Context 메뉴 제공
        if (e.Button == MouseButtons.Right)
        {
            //WANIGrid Header 영역이 선택되어졌을 경우에는 메뉴 제공하지 않음.
            if (e.Y < grid.TopHeaderHeight) return;
            //마우스 우측 버튼이 클릭된 좌표 값을 얻어온다.
            Point p = new Point(e.X, e.Y);

            rightClickMenu.Show(this, p);
        }
        else if (e.Button == MouseButtons.Left)
        {
            //WANIGrid Top Header 영역을 마우스 좌측 버튼으로 클릭했을 때
            if (e.Y < topHeaderHeight)
            {
                //Cursor가 Cursors.VSplite로 변경되었을 경우 vSpliteLineMouseDown을 true로 변경
                if (Cursor == Cursors.VSplit)
                {
                    vSpliteLineMouseDown = true;
                }
                else
                {
                    vSpliteLineMouseDown = false;
                    MouseLeftButtonClickInTopHeadHeight(sender, e);
                    Invalidate();
                }
                //Header 영역에서 왼쪽 마우스 버튼이 눌려졌을 때 마우스 포인트 저장.
                mousePoint.X = e.X;
                mousePoint.Y = e.Y;
            } 
            else //WANIGrid의 Top Header 영역을 제외한 영역에서 마우스 좌측 버튼을 클릭했을 때
            {
                MouseLeftButtonClickInContents(sender, e);
                Invalidate();
            }                
        }
    }

 

그리고 MouseMouseLeftButtonClickInTopHeadHeight, MouseLeftButtonClickInContents 메소드 내부에 있었던 Invalidate()를 위의 소스에서 처럼 바깥으로 가져왔다.

mousePoint를 설정하는 부분도 MouseMouseLeftButtonClickInTopHeadHeight 메소드 내부에서 바깥으로 가져왔다.

 

WANIGrid Control의 사이즈가 변경 되었을 경우, 선택 셀(Cell) Rectangle 부분도 Clear할 수 있도록 처리했다.

WANIGrid_Resize, WANIGrid_SizeChanged, WANIGrid_ClientSizeChanged 메소드에 ActiveCell.Clear() 호출을 추가.

 

    private void WANIGrid_Resize(object sender, EventArgs e)
    {
        rc = new Rectangle(0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height);
        ActiveCell.Clear();
        InitializeScollBar();
        ReCalcScrollBars();
        Invalidate();
    }

 

    private void WANIGrid_SizeChanged(object sender, EventArgs e)
    {
        rc = new Rectangle(0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height);
        ActiveCell.Clear();
        InitializeScollBar();
        ReCalcScrollBars();
        Invalidate();
    }

    private void WANIGrid_ClientSizeChanged(object sender, EventArgs e)
    {
        rc = new Rectangle(0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height);
        ActiveCell.Clear();
        InitializeScollBar();
        ReCalcScrollBars();
        Invalidate();
    }

 

이렇게 해서 WANIGrid Control에 컬럼(Column)의 폭(Width)을 조정할 수 있는 기능까지 구현을 해보았다.

지금까지의 작업으로 이젠 제법 그럴싸한 Grid Control의 모습을 갖추기 시작한 것 같다. 앞으로 가야할 길이 멀긴 하지만 조급해하지 않고 하나씩 만들어 간다는 기분으로 진행할 예정이다.

 

지금까지의 과정을 보고 추가 개선 및 신규 개발이 필요한 항목이 있거나 직접 보완 및 개발한 독자가 있다면 해당 소스를 같이 공유해 주길 바란다.

서로의 생각과 경험으로 지금 보다 나은 WANIGrid Control이 될 것이라 믿기에....주저하지 말고 의견 및 소스를 같이 공유하는 양방향 소통이 이루어지길 기원해 본다.

 

지금까지의 소스는 아래의 파일을 참조하기 바란다.

WANI Grid_20190813.zip
0.38MB

반응형
반응형

지금까지 화면에 WANIGrid Control 내부의 각 셀(Cell)에 데이터를 보여주기 위한 모든 준비를 마쳤다.

이제는 행(Row)별 DataRow에 저장된 값을 화면에 보여주기 위한 방법을 구현해 보기로 하자.

먼저 컬럼(Column) 정렬 방법에 따라 각 컬럼(Column)의 값들을 정렬해서 화면에 그리기 위한 메소드 DrawStringAlignment를 작성해 보자.

    private void DrawStringAlignment(string txt, Rectangle rec, Graphics g, HorizontalAlignment align)
    {
        StringFormat sf = new StringFormat();
        sf.Alignment = StringUtil.GetStringAlignment(sf, align); //Header에서 정렬방식을 체크해서 StringFormat의 Alignment 반환
        sf.LineAlignment = StringAlignment.Center; //세로 정렬은 Center로 설정
        g.DrawString(txt, Font, blackBrush, rec, sf);  //문자를 그린다.
    }

 

WANIGrid의 내용을 그리는 메소드 DrawContent를 아래와 같이 수정 및 추가하도록 한다.

    private void DrawContent(Graphics g, Rectangle rc, int controlWidth)
    {
        SolidBrush brush = new SolidBrush(SystemColors.ControlLight);
        Pen pen = new Pen(Color.LightGray);

        if (rows.Count <= 0) return;
        g.Clip = new Region(new Rectangle(1, topHeaderHeight, Width - ysclWidth + 2, 

                                    Height - xsclHeight - topHeaderHeight));
        try
        {
            int columnStartY = topHeaderHeight;
            for (int i = firstVisibleRow; i <= lastVisibleRow && i < rows.Count; i++)
            {
                int columnStartX = 0;
                int columnWidth = 0;

                for (int j = firstVisibleCol; j <= lastVisibleCol && j < grid.GridHeaderList.Count; j++)
                {                        
                    Col col = new Col(this.grid.HeaderGen.GetHeaders(), rows[i].DataRow);
                    string content = col.GetColText(j);
                    if (j == firstVisibleCol)
                    {
                        g.FillRectangle(brush, 1, columnStartY + 1, leftHeaderWidth, rowHeight);
                        g.DrawRectangle(pen, 1, columnStartY + 1, leftHeaderWidth, rowHeight);                            
                        columnStartX += leftHeaderWidth;  //첫 시작컬럼의 폭을 leftHeaderWidth 만큼 설정                                         
                    }

                    if (!grid.GridHeaderList[j].Visible) continue;  //Header의 Column Visible 체크 

                    //보여지는 컬럼의 폭이 컨트롤의 폭 보다 클경우
                    if (columnStartX + grid.GridHeaderList[j].Width > controlWidth)
                    {
                        columnWidth = controlWidth - columnStartX - 3;
                        g.DrawRectangle(pen, columnStartX + 1, columnStartY + 1, columnWidth, rowHeight);
                        Rectangle rec = new Rectangle(columnStartX + 2, columnStartY + 2, columnWidth - 2, rowHeight);
                        DrawStringAlignment(content, rec, g, col.Alignment);                            
                    }
                    else
                    {
                        columnWidth = grid.GridHeaderList[j].Width;
                        g.DrawRectangle(pen, columnStartX + 1, columnStartY + 1, columnWidth, rowHeight);                            
                        Rectangle rec = new Rectangle(columnStartX + 2, columnStartY + 2, columnWidth - 2, rowHeight);
                        DrawStringAlignment(content, rec, g, col.Alignment);                            
                    }
                    columnStartX += columnWidth;
                }
                columnStartY += rowHeight;
            }
        }
        catch (Exception e)
        {
            MessageBox.Show(e.Message);
        }
    }

 

이렇게 해서 각 컬럼(Column)에 데이터를 입력하고, 입력된 데이터를 WANIGrid의 Content 영역에 보여주는 부분까지 완성했다.

 

추가한 내용을 테스트하기 위해서 WANIGridTest 프로젝트의 WANIGridTest.cs 파일을 아래와 같이 변경하도록 한다.

각 컬럼(Column)의 정렬 방법을 다양하게 설정했고, 컬럼(Column)의 Visible값도 false를 추가했다.

    private void WANIGridTest_Load(object sender, EventArgs e)
    {
        HeaderBuilder builder = new HeaderBuilder(this.waniGrid.GridDisplayType);
        builder.AddHeader(new DefaultHeader("Col01", "Column 01", 80, HorizontalAlignment.Center, HorizontalAlignment.Left, true));
        builder.AddHeader(new DefaultHeader("Col02", "Column 02", 80, HorizontalAlignment.Center, HorizontalAlignment.Right, true));
        builder.AddHeader(new DefaultHeader("Col03", "Column 03", 90, HorizontalAlignment.Center, HorizontalAlignment.Left, false));
        builder.AddHeader(new DefaultHeader("Col04", "Column 04", 100, HorizontalAlignment.Center, HorizontalAlignment.Center, true));
        builder.AddHeader(new DefaultHeader("Col05", "Column 05", 110, HorizontalAlignment.Center, HorizontalAlignment.Left, true));
        builder.AddHeader(new DefaultHeader("Col06", "Column 06", 120, HorizontalAlignment.Center, HorizontalAlignment.Right, true));
        builder.AddHeader(new DefaultHeader("Col07", "Column 07", 110, HorizontalAlignment.Center, HorizontalAlignment.Right, true));
        builder.AddHeader(new DefaultHeader("Col08", "Column 08", 100, HorizontalAlignment.Center, HorizontalAlignment.Left, true));
        builder.AddHeader(new DefaultHeader("Col09", "Column 09", 80, HorizontalAlignment.Center, HorizontalAlignment.Center, true));
        builder.AddHeader(new DefaultHeader("Col10", "Column 10", 90, HorizontalAlignment.Center, HorizontalAlignment.Left, true));
        builder.InitializeHeader();
        this.waniGrid.SetHeader(builder);
    }

 

지금까지의 소스를 컴파일해서 실행한 화면은 아래와 같다.

[그림 14-1] 셀(Cell) 데이터 입력 후 Display (1/2)
[그림 14-2] 셀(Cell) 데이터 입력 후 Display
[그림 14-3] 셀(Cell) 데이터 입력 시

지금까지의 작업으로 WANIGrid Control이 Grid Control의 일반적인 기능들을 제공할 수 있게 되었다.

아직도 가야 할 길이 멀기는 하지만 매일 조금씩 기능을 추가해 가면서 지금 보다는 더 완성된 모습의 WANIGrid가 될 수 있도록 계속해서 보완해 나갈 것이다.

 

이 글을 읽고 있는 독자들도 지금의 WAINGrid Control에서 구현 되었으면 또는 추가 보완 되었으면 하는 기능이 있다면 아래의 댓글로 알려주거나, 내가 제공한 소스에서 해당 기능을 추가해서 같이 공유해 주기 바란다.

혼자의 아이디어 보다는 많은 사람들의 아이디어와 생각/경험을 기반으로 보다 완성도 높은 WANIGrid Control이 될 수 있도록 많은 사람들의 동참을 바라며...

 

지금까지의 소스는 아래의 파일을 참조하기 바람.

WANI Grid_20190811.zip
0.37MB

반응형
반응형

지금까지는 WANIGrid의 겉 모습을 구현해 왔다. 이제는 실제 셀(Cell)의 데이터 입력과 Display 부분을 구현해 보기로 하자. 셀(Cell)의 데이터 입력은 특정 셀(Cell)을 선택하고 한 번 더 클릭을 하면 TextBox가 나타나서 사용자가 입력을 할 수 있도록 되어져 있다.

사용자가 TextBox에 데이터를 입력하면 해당 행(Row)의 선택 컬럼에 입력한 데이터를 저장하고, 저장된 데이터를 화면에 다시 보여주어서 사용자가 확인할 수 있도록 하는 것이다.

이 기능을 구현하기 위해 필요한 각 기능들을 소스를 보면서 확인하도록 하자.

 

하나의 행(Row)에서 원하는 컬럼의 값들을 가져오기 위한 Col Class를 살펴보자.

    /// 1개의 Row에 대한 Column들의 Text값을 가져오기 위한 클래스
    public class Col
    {
        #region 변수
        private List<Header> colHeaders;    //Column의 Header정보를 저장
        private DataRow row;    //Row의 Column정보를 저장하기 위한 DataRow
        private HorizontalAlignment alignment;  //Column의 Text 정렬 방법
        #endregion 변수

        #region Property
        /// Header에 정의된 Column 리스트 설정하거나 반환한다.
        public List<Header> ColHeaders 

        { 

            get { return this.colHeaders; } 

            set { this.colHeaders = value; }

        } 

        /// Column의 정렬방법
        public HorizontalAlignment Alignment
        {
            get { return this.alignment; }
        }

        /// Row의 Column정보를 저장하기 위한 DataRow
        public DataRow Row
        {
            get { return this.row; }
            set { this.row = value; }
        }      
        #endregion Property

        #region 생성자        
        /// 생성자 - 한개의 행에 대한 Column Header정보와 DataRow를 파라미터로 받음
        public Col(List<Header>  headers, DataRow row)
        {
            this.colHeaders = headers;
            this.row = row;
        }

        /// Column ID 값으로 DataRow내의 컬럼 값을 반환
        public string GetColText(string columnId)
        {
            string text = String.Empty;
            if (row[columnId] != null) text = row[columnId].ToString();

            return text;
        }

        /// Column Index에 해당하는 Column 값을 반환
        public string GetColText(int index)
        {
            string text = String.Empty;
            int colNo = 0;
            foreach (Header head in colHeaders)
            {
                if (head.Visible) //Column Header에서 해당 Column을 Visible true일 경우에만 값을 반환 - 화면에 Display하기 위함
                {
                    if (head.Index == index)
                    {
                        text = row[head.ColumnId].ToString();
                        alignment = head.TextAlign;
                    }
                    colNo++;
                }
            }

            return text;
        }
        #endregion 생성자
    }
}

 

처음 Header를 생성할 때 특정 Column을 화면에서 숨기고자 설정할 수도 있다. 이전의 소스 코드에서는 모든 컬럼(Column)의 Visible을 true로 설정하고 개발을 했었다. 하지만 경우에 따라서는 특정 컬럼(Column)이 숨겨져야 할 경우의 처리를 추가하도록 해야 한다.

WANIGrid의 Header영역을 그리는 DrawHeader함수에 컬럼(Column)의 Visible이 true인 경우에만 화면에 나타나도록 처리하는 부분을 추가하도록 한다.

DefaultHeaderGenerator.cs 파일을 열어서 DrawHeaders 메소드에 아래의 부분을 추가한다.

        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++)
            {                
                if (i == firstVisibleCol) columnStartX += leftHeaderWidth;  //첫 시작컬럼의 폭을 leftHeaderWidth 만큼 설정
                int headerWidth = this._headers[i].Width;   //i 번째 컬럼의 폭을 설정

                if (!this._headers[i].Visible) continue;  //기존 코드에 추가하는 부분

                //보여지는 컬럼의 폭이 컨트롤의 폭 보다 클경우
                if (columnStartX  + headerWidth > controlWidth)
                {
                    headerWidth = controlWidth - columnStartX - 3;
                    if (lastVisibleCol == _headers.Count - 1) IsLargeLastCol = true;
                } else
                {
                    IsLargeLastCol = false;
                }

              :

              :

            }

        }

 

향 후 각 컬럼(Column)에 대한 편집 여부도 설정할 수 있어야 하기에 Header.cs 파일에 boolean 변수와 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 여부
        private bool editable = true;   //컬럼 편집여부
        #endregion 변수

        :

        public bool Editable
        {
            get { return editable; }
            set { editable = value; }
        }

        :

    }

 

WANIGrid.cs 파일에서 추가 또는 변경되는 부분들을 하나씩 살펴보기로 하자.

마우스로 선택한 특정 셀(Cell)의 컬럼(Column) 중 액티브 컬럼을 설정하기 위한 변수를 선언한다. 사용자가 선택한 Active한 Column 값을 저장하기 위한 변수이다.

private int ActiveCell_ActvieCol = -1;

 

저장된 값을 사용자에게 Display할 때 보여지는 폰드 색상을 검은색으로 설정해서 저장한다. 화면에서 WANIGrid를 그리면서 데이터를 보여줄때 사용할 색상의 Brush이다.

private SolidBrush blackBrush = new SolidBrush(Color.Black);

 

WANIGrid 생성자에 TextBox인 editBox에 설정을 추가한다.

    public WANIGrid()
    {
        InitializeComponent();
        if (grid == null) grid = new WANI_Grid.Grid.Grid();
        rows = new RowCollection();
        hScrollBar.SmallChange = 1;
        rowHeight = Font.Height + 4;
        vScrollBar.SmallChange = rowHeight;
        editBox = new TextBox();
        editBox.BorderStyle = BorderStyle.None;
        editBox.BackColor = Color.White; //배경색을 흰색으로 설정
        editBox.Font = Font;
        editBox.Visible = false;
        Controls.Add(editBox);
        //마우스 우측 버튼 클릭 시 제공되는 ContextMenu 초기화
        InitializeContextMenu();
    }

 

그리고 editBox에 값이 변경되었을 때의 이벤트를 처리할 수 있도록 코드를 추가한다. editBox 값이 변경되면 변경된 값을 행(Row)의 DataRow 변수의 해당 컬럼(Column)에 값을 설정하도록 처리한다.

    public void EventHandlerInitialize()
    {
        //가로 스크롤바
        hScrollBar.Scroll += new ScrollEventHandler(HScrollBar_Scroll);
        //세로 스크롤바
        vScrollBar.Scroll += new ScrollEventHandler(VScrollBar_Scroll);
        //마우스 휠
        this.MouseWheel += new MouseEventHandler(Mouse_Wheel);
        this.editBox.TextChanged += EditBox_TextChanged; //editBox의 TextChanged 이벤트 추가
    }

 

    private void EditBox_TextChanged(object sender, EventArgs e)
    {
        DataRow row = rows[ActiveCell.Row].DataRow;
        Header header = grid.GridHeaderList.Where(x => x.Index == ActiveCell.Col).FirstOrDefault();
        if (header != null)
        {
            if (ActiveCell_ActvieCol > -1) row[header.ColumnId] = editBox.Text;
        }
    }

 

editBox에서 작업시 호출 되는 BeginEdit/EndEdit 메소드를 아래와 같이 수정하도록 한다.

사용자가 특정 셀(Cell)에 값을 입력하기 위한 BeginEdit 메소드가 호출 될 때 TextBox의 좌측과 폭의 값을 조정한다.

    private void BeginEdit()
    {
        if (readOnly) return;

        if (ActiveCell.Col != -1 && ActiveCell.Row != -1)
        {
            string tempStr = "";
            if (rows[ActiveCell.Row].DataRow[grid.GridHeaderList[ActiveCell.Col].ColumnId] != null)
            {
                tempStr = rows[ActiveCell.Row].DataRow[grid.GridHeaderList[ActiveCell.Col].ColumnId].ToString();
            }
            //TextBox에 입력된 값을 설정하고 TextBox 속성의 값을 설정한다.
            editBox.Text = tempStr;
            Rectangle r = GetSelectedCellRect(ActiveCell.Row, ActiveCell.Col);
            editBox.Left = r.Left + 3;
            editBox.Top = r.Top + 3;
            editBox.Height = r.Height;
            editBox.Width = r.Width - 3;
            editBox.Visible = true;
            editBox.Focus();         

            ActiveCell_ActvieCol = ActiveCell.Col;  //ActivieCell_ActiveCol 값을 설정      
        }
    }

 

EndEdit 메소드를 아래와 같이 변경한다.

    private void EndEdit()
    {
        if (readOnly) return;
        if (ActiveCell.Col != -1 && ActiveCell.Row != -1 && editBox.Visible)
        {
            ActiveCell_ActvieCol = -1;
        }
        editBox.Visible = false;
        editBox.Text = "";
        gridState = GridState.ACTIVE;
    }

 

WANIGrid Header 값을 설정할 때 특정 컬럼(Column)을 숨기고자 할 경우 Visible 값이 false인 경우의 컬럼(Column)은 화면에서 보여지지 않아야 한다. 특히 사용자가 WANIGrid 선택 시 마우스 X좌표 값으로 컬럼(Column) 값으로 변환하는 메소드인 GetColFromX에 Header 값을 체크해서 Visible값이 false인 경우에는 처리하지 않는 로직을 추가한다.

    private int GetColFromX(int X)
    {
        int col = 0;
        int tempWidth = leftHeaderWidth;

        if (X >= 0 && X <= leftHeaderWidth)
        {
            col = -1;
        }
        else
        {
            for (col = firstVisibleCol; col < lastVisibleCol; col++)
            {
                if (!grid.GridHeaderList[col].Visible) continue; //Header의 Visible 값이 false인 경우
                int width = grid.GridHeaderList[col].Width;
                if (X < width + tempWidth) break;
                tempWidth += width;
            }
        }
        if (col > grid.GridHeaderList.Count - 1) col = -1;
        return col;
    }

 

마우스 Click시 선택 셀(Cell)을 그리기 위한 Rectangle 값을 반환하는 GetSelectedCellRect 메소드에 Header의 Visible값이 false인 경우를 처리하는 로직을 추가한다.

    protected Rectangle GetSelectedCellRect(int row, int col)
    {
        if (row < firstVisibleRow || row > lastVisibleRow) return new Rectangle(0, 0, 0, 0);
        if (col < firstVisibleCol || col > lastVisibleCol) return new Rectangle(0, 0, 0, 0);

        :

        int left = leftHeaderWidth + 2;
        int width = 0;
        for (int i = firstVisibleCol; i <= lastVisibleCol; i++)
        {
            if (!grid.GridHeaderList[i].Visible) continue;
            :

        }

        return new Rectangle(left - 1, top + 1, width - 1, height - 1);

    }

 

WANIGrid의 최상단 Header영역을 클릭 했을 때 선택된 Column색상을 변환하는 부분에도 Header의 Visible값이 false인 경우 처리하지 않도록 하는 로직을 추가해야 한다.

DrawBackground메소드에서 아래의 파란색 부분을 추가하도록 한다.

    private void DrawBackground(Graphics g, Rectangle rc)
    {
        g.DrawRectangle(new Pen(SystemColors.Control, 2), 0, 0, rc.Width, rc.Height);
        rc.Inflate(-1, -1);
        g.FillRectangle(new SolidBrush(SystemColors.ControlLightLight), rc);

        //선택된 컬럼의 Background를 그린다.
        for (int i = 0; i < selectedCols.Count; i++)
        {
            int index = selectedCols[i];
            int left = leftHeaderWidth;

            for (int j = firstVisibleCol; j < lastVisibleCol && j < index; j++)
            {
                if (!grid.GridHeaderList[j].Visible) continue; //Header의 Visible 체크 
                left += grid.GridHeaderList[j].Width;
            }

            if (allRowsHeight > 0)
            {
                g.FillRectangle(selectedColor, left + 1, topHeaderHeight, grid.GridHeaderList[index].Width + 1, 

                                   allRowsHeight - (rowHeight * firstVisibleRow) + 1);
            }
        }

        :

        :

    }

 

 

 

 

 

 

 

 

반응형
반응형

Grid Header 영역 클릭 시 컬럼 선택을 표현하기 위해 배경 색상을 바꾸어 보았다. 마찬가지로 좌측 첫 열의 빈 공간을 클릭하면 행(Row)이 선택되도록 할 것이다.

기존의 컬럼 선택과 큰 차이가 없지만 행(Row) 선택을 표현하기 위한 코드를 같이 살펴보기로 하자.

[그림 12-1] 행 선택 영역

먼저 WANIGrid.cs 파일에 선택된 행(Row) 정보를 관리하기 위한 변수를 추가한다.

 

private List<int> selectedRows = new List<int>();   //선택된 행(Rows)들을 관리하기 위한 변수

 

마우스 다운 이벤트에 마우스 좌측 버튼이 눌러졌을 경우에 Top Header 영역이 아닌 상단 이미지와 같은 맨 좌측 영역에서 클릭 이벤트가 발생했을 경우, 선택된 행(Row)의 색상은 변경하도록 한다.

private void WANIGrid_MouseDown(object sender, MouseEventArgs e)
{
    //마우스 우측 버튼 클릭 시 Context 메뉴 제공
    if (e.Button == MouseButtons.Right)
    {
        //Grid Header 영역이 선택되어졌을 경우에는 메뉴 제공하지 않음.
        if (e.Y < grid.TopHeaderHeight) return;
        //마우스 우측 버튼이 클릭된 좌표 값을 얻어온다.
        Point p = new Point(e.X, e.Y);

        rightClickMenu.Show(this, p);
    }
    else if (e.Button == MouseButtons.Left)
    {
        //WANIGrid Top Header 영역을 마우스 좌측 버튼으로 클릭했을 때
        if (e.Y < topHeaderHeight)
        {
            MouseLeftButtonClickInTopHeadHeight(sender, e);
        }
        else //WANIGrid의 Top Header 영역을 제외한 영역에서 마우스 좌측 버튼을 클릭했을 때
        {
            MouseLeftButtonClickInContents(sender, e); --> 추가한 영역
        }
    }
}

 

마우스 좌측 버튼을 WANIGrid Control의 Header영역에서 클릭했을 때 호출되는 메소드인 MouseLeftButtonClickInToHeaderHeight에 시프트 키가 눌러졌을 때의 동작을 추가한 소스는 아래와 같다.

private void MouseLeftButtonClickInTopHeadHeight(object sender, MouseEventArgs e)
{
    selectedRows.Clear();

    //컬럼이 존재할 경우
    if (grid.GridHeaderList.Count > 0)
    {
        int col = GetColFromX(e.X);
        if (col < 0) return; //col값이 -1이면 처리하지 않음

        //Control Key를 누르지 않은 상태에서 컬럼을 선택했을 경우
        if (Control.ModifierKeys != Keys.Control && Control.ModifierKeys != Keys.Shift)
        {
            //선택된 컬럼일 경우
            if (selectedCols.Contains(col))
            {
                if (selectedCols.Count > 1) //선택된 컬럼이 두 개 이상일 경우
                {
                    selectedCols.Clear(); //여러 컬럼이 선택된 경우 기존의 선택된 컬럼 무효화
                    selectedCols.Add(col); //선택 컬럼 추가
                }
                else selectedCols.Remove(col);  //동일한 컬럼을 2번 선택하면 선택 표시 지움
            }
            else //선택된 컬럼이 없을 경우 기존 선택 컬럼을 모두 지우고 선택한 컬럼을 추가
            {
                selectedCols.Clear();
                selectedCols.Add(col);
            }
        }
        else
        {
            if (Control.ModifierKeys == Keys.Shift && selectedCols.Count > 0)
            {
                int index = selectedCols[0];
                int begin = Math.Min(col, index);
                int end = Math.Max(col, index);
                selectedCols.Clear();
                for (int i = begin; i <= end; i++)
                {
                    selectedCols.Add(i);
                }
            }
            else if (Control.ModifierKeys == Keys.Control)
            {
                if (selectedCols.Contains(col)) selectedCols.Remove(col);   //선택된 컬럼을 다시 선택할 경우 제거해서 컬럼 선택 무효화
                else selectedCols.Add(col); //선택된 컬럼을 추가
            }
        }
        Invalidate();
    }
    mousePoint.X = e.X;
    mousePoint.Y = e.Y;
}

 

마우스 좌측 버튼을 WANIGrid Control의 맨 왼쪽 부분에서 클릭했을 때 호출되는 메소드는 아래와 같다.

private void MouseLeftButtonClickInContents(object sender, MouseEventArgs e)
{
    selectedCols.Clear();
    int row = GetRowFromY(e.Y);
    if (row < 0) return;    //row값이 -1이면 처리하지 않음
    if (e.X < leftHeaderWidth)  //맨 좌측의 첫 컬럼을 선택했을 시 Row를 선택하도록 처리
    {
        if (Control.ModifierKeys != Keys.Control && Control.ModifierKeys != Keys.Shift)
        {
            if (selectedRows.Contains(row))
            {
                if (selectedRows.Count > 1) //선택된 행(Row)이 2개 이상일 경우
                {
                    selectedRows.Clear();   //여러 행(Row)가 선택된 경우 기존의 선택된 행(Row) 무효화
                    selectedRows.Add(row);  //선택 행(Row) 추가
                }
                else selectedRows.Remove(row);  //동일한 행(Row)를 2번 선택하면 선택 표시 지움
            }
            else //선택된 행(Row)가 없을 경우 기존 선택 행(Row)를 모두 지우고 선택한 행(Row)를 추가
            {
               selectedRows.Clear();
               selectedRows.Add(row);
            }
        }
        else
        {

            //Shift 키를 누르고 선택된 행이 1개 이상일 경우

            if (Control.ModifierKeys == Keys.Shift && selectedRows.Count > 0)

            {

                int index = selectedRows[0]; 
                int begin = Math.Min(row, index);  
                int end = Math.Max(row, index);
                selectedRows.Clear();
                for (int i = begin; i <= end; i++)
                {
                    selectedRows.Add(i);
                }

            }            
            else if (Control.ModifierKeys == Keys.Control) //컨트롤 키를 누른 상태일 경우

            {

                //선택된 행(Row)을 다시 선택할 경우 제거해서 행(Row) 선택 무효화

                if (selectedRows.Contains(row)) selectedRows.Remove(row);    
                else selectedRows.Add(row); //선택된 행(Row)를 추가

            }
        }
    }
    else
    {
        selectedRows.Clear();
    }

    Invalidate();
}

 

지금까지는 마우스의 좌측 버튼 클릭 시의 위치를 계산해서 선택된 행(Row)을 확인하고 선택 행(Row) 정보를 관리하는 selectedRows 변수에 값을 할당하거나 제거하는 로직을 작성했다.

컨트롤 키와 쉬프트 키가 눌러졌을 때, 행 선택 방법도 정의를 했다. 컨트롤 키를 누른 상태에서 행이나 컬럼을 선택했을 경우에는 여러 행과 컬럼이 선택되도록 했고, 시프트 키를 눌렀을 때에는 시작 행/컬럼 부터 다음 선택 행/컬럼 사이의 모든 행/컬럼이 선택되도록 했다.

실제 선택된 행(Row)의 바탕색을 변경하기 위한 데이터 작업은 완료했으나, 아직은 WANIGrid Control에서는 색상이 변경되는 모습은 볼 수 없다.

실제 선택된 행(Row)의 값이 변경된 후에 Invalidate() 메소드 호출이 되는 순간 WANIGrid Control의 Display 영역을 새롭게 그리기된다.

Control 영역을 다시 그릴 때 선택된 행(Row)의 배경 색이 나타날 수 있도록 해야 한다.

이렇게 하기 위해서는 WANIGrid Control의 배경을 담당하고 있는 DrawBackground 메소드에 선택된 행(Row)의 백그라운드를 그리는 로직을 추가한다.

private void DrawBackground(Graphics g, Rectangle rc) 

{

    .....

    //선택된 행(Row)의 Background를 그린다.
    for (int i = 0; i < selectedRows.Count; i++)
    {
        int index = selectedRows[i];                
        int top = topHeaderHeight;

        int width = 0;

        for (int k = firstVisibleCol; k <= lastVisibleCol; k++)
        {
            if (grid.GridHeaderList[k].Visible) width += grid.GridHeaderList[k].Width;
        }

        for (int j = firstVisibleRow; j < lastVisibleRow && j < index; j++)
        {
            top += rows[j].MaxLines * rowHeight;
        }

        //선택된 행(Row) 영역의 배경색을 표현한다.
        g.FillRectangle(selectedColor, leftHeaderWidth + 1, top + 1, width, rowHeight); 
    }

}

 

이렇게 함으로써 행(Row)를 선택하면 선택된 행(Row)의 배경색이 변경되어 선택된 행(Row)임을 사용자에게 표현해서 보여 줄 수 있게 되었다.

[그림 12-2] 한 개 행(Row) 선택
[그림 12-3] 여러 행(Row) 선택

아직도 가야 할 길이 멀기는 하지만 필요한 기능들을 하나 씩 만들어 가보기로 하자.

저자 또한 필요한 기능들을 직접 학습과 개발/테스트를 통해서 완성해 가고 있는 부분이기도 하다. 이러한 이유로 진도가 좀 더뎌지는 이유이기도 하다.

지금 까지의 소스는 아래의 파일을 참조하자.

WANI Grid_20190728.zip
0.35MB

반응형

+ Recent posts