반응형

그 동안 찾아온 게으름이란 녀석 때문에 한 동안 WANI Grid Control의 소스를 꽤나 멀리했다.

근자에 바빴던 필자의 본업으로 인해 정신적 시간적 여유가 없어서 소스 보면서 어느 부분을 좀 더 개선하면 보다 쓸만한 녀석으로 바꿀 수 있을지에 대한 생각을 해보지 못했다.

2019년 한 해를 보내며 지금 작성한 이글이 2019년 마지막 글이 될지도 모르겠다.

 

오늘 추가 및 개선해 볼 기능은 특정 Column선택 시 표시되는 부분과 행(Row) 숨기기 및 숨기기 해제 기능을 추가해 볼 것이다.

 

기존의 특정 컬럼 선택 시 표현 되는 부분은 아래의 이미지와 같다.

[그림 25-1] 기존 컬럼 선택 시 표현 방법

선택된 컬럼의 행(Row)를 표기 하기 위해 LightCyan으로 표시하고 Dot선으로 Retangle을 그려서 표시를 해주고 있지만 눈으로 식별되는 부분이 약한 듯 해서 선택된 Rectangle 부분의 색상을 바탕색과 동일한 색상으로 표기를 하고자 한다.

개선한 컬럼 선택 부분은 아래의 이미지를 참고하자.

[그림 25-2] 개선된 컬럼 선택 표현 방법

사용자 좀 더 정확하게 선택한 컬럼을 식별하기가 쉽도록 개선되었다.

이 부분을 보완하기 위해서는 WANIGrid.Event.cs 파일을 열어서 Paint 영역에서 호출되는 메소드의 위치와 DrawActiveCell 메소드의 내용을 보완하면 된다.

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

 

기존의 마지막에 위치해 있던 DrawActiveCell 메소드를 WANI Grid 내의 내용을 그리는 메소드인 DrawContent 메소드 앞에 위치를 시키고 DrawActiveCell 메소드에 선택한 컬럼의 Rectangle 부분에 FillRectangle 처리를 해주면 된다.

WANIGrid.Method.cs 파일을 열어서 DrawActiveCell 메소드의 FillRectange 부분을 추가해 주면 된다.

    private void DrawActiveCell(Graphics g)
    {
        if (ActiveCell.Row != -1 && ActiveCell.Col != -1)
        {
            if (ActiveCell.Col > grid.GridHeaderList.Count) return;
            //Calendar 날짜 컬럼은 Active Cell이 되지 않도록 처리
            if (grid.GridHeaderList[ActiveCell.Col].IsDate) return;
 

             //파선 패턴 설정
            float[] dashValues = { 1, 1, 1, 1 };
            Pen grayPen = new Pen(Color.Gray, 1);
            grayPen.DashPattern = dashValues;
            g.FillRectangle(new SolidBrush(SystemColors.ControlLightLight), GetSelectedCellRect(ActiveCell.Row, ActiveCell.Col));
            g.DrawRectangle(grayPen, GetSelectedCellRect(ActiveCell.Row, ActiveCell.Col));
        }
    }

 

행(Row) 숨기기 기능을 추가하기 위해서는 한개의 행(Row) 정보를 담당하는 Row 클래스에 행 숨기기 정보를 관리하기 위한 변수를 추가해야 한다.

Row.cs 파일을 열어서 bool 타입의 hidden 변수와 Hidden Property를 선언 및 정의하기로 한다.

    private bool hidden = false;

    public bool Hidden
    {
        get { return hidden; }
        set { hidden = value; }
    }

행을 숨기고자 할 경우 Row 클래스의 hidden 변수에 true를 설정하고, 숨기기 취소를 할 경우 false를 설정하면 된다.

 

Context Menu에 선택한 행들을 모두 숨기기 처리하기 위한 메뉴와 숨기기 처리된 모든 행을 숨기기 취소하는 메뉴를 추가하도록 한다.

 

기존의 행을 그리던 모든 메소드에 행(Row)을 관리하던 컬렉션인 rows 변수의 해당 행의 row의 hidden 변수가 true일 경우 해당 행을 그리지 않고 Skip할 수 있도록 if (rows[i].hidden) continue;  부분을 모두 추가해 주면 된다.

다국어 처리를 위해 Resources폴더 내의 LanguageResource.ko-KR.resx, LanguageResource.resx 파일을 열어서 메뉴 명칭을 추가한다.

Row.Hidden : 행 숨기기/Row Hide, Row.HiddenCancel : 행 숨기기 취소/Cancel Row Hide

    private void InitializeContextMenu()
    {
        rightClickMenu = new ContextMenu();
        rightClickMenu.MenuItems.Add(LanguageResource.Row_Before_Insert);
        rightClickMenu.MenuItems.Add(LanguageResource.Row_After_Insert);
        rightClickMenu.MenuItems.Add(LanguageResource.Row_Append);
        rightClickMenu.MenuItems.Add(LanguageResource.Row_Delete);
        rightClickMenu.MenuItems.Add(LanguageResource.Row_Hidden);
        rightClickMenu.MenuItems.Add(LanguageResource.Row_HiddenCancel);

        rightClickMenu.MenuItems[0].Click += new EventHandler(OnMenu_BeforeInsertRow_Click);
        rightClickMenu.MenuItems[1].Click += new EventHandler(OnMenu_AfterInsertRow_Click);
        rightClickMenu.MenuItems[2].Click += new EventHandler(OnMenu_AppenRow_Click);
        rightClickMenu.MenuItems[3].Click += new EventHandler(OnMenu_DeleteRow_Click);
        rightClickMenu.MenuItems[4].Click += new EventHandler(OnMenu_RowHidden_Click);
        rightClickMenu.MenuItems[5].Click += new EventHandler(OnMenu_RowHiddenCancel_Click);
    }

 

WANIGrid.Event.cs 파일 내에 행 숨기기/숨기기 취소 이벤트 처리를 해 둔 부분과 마우스 좌측 버튼 클릭시 호출되는 MouseLeftButtonClickContents 메소드의 추가된 행(Row)의 hidden 변수가 true일 때 처리를 위해서 추가한

if (rows[row].Hidden) continue;  부분을 참고하기 바랍니다.

 

WANIGrid.Mehtod.cs 파일을 열어서 행(Row)의 hidden 변수가 true일 경우 화면에서 그려지지 않도록 추가한 부분 역시

if (rows[row].Hidden) continue; 이 부분이 추가되어져 있다.

소스 내의 rows[row].Hidden 이 true일 경우 화면 상에 표시 되지 않도록 처리한 부분들이다.

이 부분들을 살펴 보면 될 것이다.

추가로 선택한 행들을 숨기기 처리하거나 숨겨진 모든 행(Row)들을 숨기기 취소하는 메소드인 HideAndHideCancelRow 를 살펴보기 바란다.

 

특별히 어려운 내용이 없는 관계로 변경 및 보완/추가된 영역을 알려 주었으니, 첨부한 소스 코드들을 참고해서 변경된 내용들을 참고하기 바란다.

 

WANI Grid_20191225.zip
0.74MB

반응형
반응형

Calendar 컬럼(Column)을 표현하기 위해 사전 준비 작업과 YearMonthWeekNoDayHeader, YearMonthWeekNoDayHeaderGenerator, HeaderBuilder를 살펴 보았고, 실제 WANIGrid Control 영역을 살펴보자.

 

새로운 변수를 추가하기로 하자.

    private int currentCol = 0; //현재 첫페이지를 저장하기 위한 변수

    private SolidBrush holidayColorBrush = new SolidBrush(Color.LightSkyBlue); //휴일색상

    private bool fixedColEditable = true;   //고정 컬럼 수정 여부

 

속성(Property)설정을 아래와 같이 한다.

    /// <summary>
    /// 휴일 색상으로 채워주는 Brush 값을 가져오거나 설정한다.
    /// </summary>
    public SolidBrush HolidayColorBrush
    {
        get { return holidayColorBrush;}
        set { holidayColorBrush = value; }
    }

 

    /// <summary>
    /// 고정 컬럼 수정여부
    /// </summary>
    public bool FixedColEditable
    {
        get { return fixedColEditable; }
        set { fixedColEditable = value; }
    }

 

WANIGrid 영역의 Event 처리 부분인 WANIGrid.Event.cs 파일을 추가로 보완하기로 하자.

Calendar 컬럼 영역의 헤드를 마우스 클릭 했을 때 특정 컬럼이 선택되지 않도록 처리하기 위해서 마우스 좌측 버튼이 헤더 영역에서 클릭 이벤트가 있는지 체크해야 한다.

이러한 이벤트를 잡기 위해서 MouseLeftButtonClickInTopHeadHeight 메소드를 찾아서 아래의 파란색 부분을 추가한다.

 

    /// <summary>
    /// WANIGrid의 Header 영역에서 마우스 좌측 버튼 클릭 시
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    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이면 처리하지 않음                

            //Calendar 날짜 컬럼은 선택되지 않도록 처리
            if (grid.GridHeaderList[col].IsDate) return;

            //Control Key를 누르지 않은 상태에서 컬럼을 선택했을 경우
            if (Control.ModifierKeys != Keys.Control && Control.ModifierKeys != Keys.Shift)
            {

            :

            :

            }

         :

        }

      :

    }

  

Calendar 컬럼의 헤더 영역을 마우스 이동 시 컬럼 폭 조정이 되지 않도록 처리해야 한다.

WANIGrid_MouseMove 이벤트의 폭을 변경하는 영역에 해당 컬럼이 Calendar정보를 가진 컬럼인지 체크하는 부분을 추가해야 한다. 아래의 파란색 부분을 참조하자.

    /// <summary>
    /// Mouse Move Event
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    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;
        }         

          :
        if (grid.GridHeaderList.Count > 0 && e.Y < topHeaderHeight)
        {  
            :    
            //Header의 컬럼과 컬럼 간의 경계선 상에 마우스 포인트가 위치했을 경우
            if (!grid.GridHeaderList[i].IsDate && (e.X > colLine - 2 && e.X < colLine + 2))
            {
                Cursor = Cursors.VSplit;
                resizeCol = i;
                if (resizeCol >= grid.GridHeaderList.Count) resizeCol = grid.GridHeaderList.Count - 1;
                break;
            }
            :
        } else
        {
                        :
                        //Header의 컬럼과 컬럼 간의 경계선 상에 마우스 포인트가 위치했을 경우
                        if (!grid.GridHeaderList[i].IsDate && (e.X > colLine - 2 && e.X < colLine + 2))
                        {
                            Cursor = Cursors.VSplit;
                            resizeCol = i;
                            if (resizeCol >= grid.GridHeaderList.Count) resizeCol = grid.GridHeaderList.Count - 1;
                            break;
                        }
                    :
                        //Header의 컬럼과 컬럼 간의 경계선 상에 마우스 포인트가 위치했을 경우
                        if (!grid.GridHeaderList[i].IsDate && (e.X > colLine - 2 && e.X < colLine + 2))
                        {
                            Cursor = Cursors.VSplit;
                            resizeCol = i;
                            if (resizeCol >= grid.GridHeaderList.Count) resizeCol = grid.GridHeaderList.Count - 1;
                            break;
          :
        }
    }

 

컬럼의 데이터 수정 시 RowCollection 타입의 rows변수의 속성인 DataRow에  값이 null일 경우에 Event 처리가 되지 않도록 한다. 아래의 파란색 부분을 추가한다.

    /// <summary>
    /// TextBox인 editBox의 TextChanged Event
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void EditBox_TextChanged(object sender, EventArgs e)
    {
        :

        if (rows[ActiveCell.Row].DataRow == null) return;
        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;
        }
    }

 

WANIGrid Control상에서 Control키를 누르고 + 또는 - 키를 눌렀을 때 Calendar 컬럼의 Width를 1씩 줄여서 Display하는 KeyDown 이벤트를 추가한다.

    /// <summary>
    /// Control Key + : 폭 넓게
    /// Control Key - : 폭 좁게
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void WANIGrid_KeyDown(object sender, KeyEventArgs e)
    {
        if (this.grid.GridDisplayType != GridType.YearMonthWeekNoDayType) return;            

        if (e.Control && e.KeyCode == Keys.Add)
        {
            foreach(YearMonthWeekNoDayHeader header in this.grid.GridHeaderList)
            {
                if (header.IsDate) header.Width += 1;
            }
        } else if (e.Control && e.KeyCode == Keys.Subtract)
        {
            foreach (YearMonthWeekNoDayHeader header in this.grid.GridHeaderList)
            {
                if (header.IsDate) header.Width -= 1;
            }
        }
        Invalidate();
    }

 

WANIGrid.Method.cs파일의 추가/보완할 부분들을 살펴보자

먼저 Grid 컬럼의 Header를 생성해서 추가하는 SetHeader 메소드를 살펴보자.

추가/변경 되는 부분은 아래의 파란색 부분을 참조하자.

    /// <summary>
    /// Grid 컬럼의 Header를 생성해서 추가한다.
    /// </summary>
    /// <param name="headerBuilder"></param>
    public void SetHeader(HeaderBuilder headerBuilder)
    {
        if (grid == null) grid = new WANI_Grid.Grid.Grid();
        if (headerBuilder != null)
        {
            grid.HeaderGen = headerBuilder.HeaderGen;
            topHeaderHeight = headerBuilder.HeaderGen.TopHeaderHeight;
            grid.DicWeekDay = headerBuilder.DicWeekDay;
            grid.DicMonthDay = headerBuilder.DicMonthDay;
        }

        grid.GridDisplayType = headerBuilder.GridDisplayType;
        grid.TopHeaderHeight = headerBuilder.HeaderGen.TopHeaderHeight;
        InitializeScollBar();

        //dataSource가 null일 경우 Header정보를 근간으로 DataTable 생성
        if (dataSource != null)
        {
            dataSource.Clear();
            dataSource = null;
            rows.Clear();
            rowHeight = 0;
            rowsCount = 0;
            selectedCols.Clear();
            selectedRows.Clear();
            allRowsHeight = 0;                
            vScrollBar.Maximum = allRowsHeight;
            firstVisibleCol = 0;
            lastVisibleCol = 0;
            firstVisibleRow = 0;
            lastVisibleRow = 0;
        }

        if (dataSource == null)
        {
            dataSource = new DataTable();
            foreach (Header hd in grid.HeaderGen.GetHeaders())
            {
                dataSource.Columns.Add(new DataColumn(hd.ColumnId, typeof(string)));
            }               
        }                                    
    }

 

Control의 Backgroud 그리는 메소드인 DrawBackground 메소드의 추가 된 부분은 아래와 같다.

    /// <summary>
    /// Control의 Backgroud 그리기
    /// </summary>
    /// <param name="g"></param>
    /// <param name="rc"></param>
    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);

        int lastFixedCol = GetLastFixedCol();
        if (lastFixedCol == -1)
        {
            DrawDefaultWANIGridControl(g, rc);
            return;
        }
        //고정 컬럼으로 지정이 가능한 영역은 Calendar 컬럼이 아닌 경우에만 가능

        //Grid의 GridHeaderList 정보를 가져와서 YearMonthHeader Type의 변환 후 GetDateTime이 DateTime.MinValue 보다 작은 경우는

        //Calendar 컬럼이 아님.
        int enableFixCol = 0;
        for (int i = 0; i <= lastFixedCol; i++)
        {
            YearMonthWeekNoDayHeader yearMonthHeader = grid.GridHeaderList[i] as YearMonthWeekNoDayHeader;
            if (yearMonthHeader != null && yearMonthHeader.GetDateTime <= DateTime.MinValue) enableFixCol = i;
        }
        if (lastFixedCol > enableFixCol) lastFixedCol = enableFixCol;

        //선택된 컬럼의 Background를 그린다.
        SelectedColChangeBackground(g, lastFixedCol);

        //선택된 행(Row)의 Background를 그린다.
        SelectedRowChangeBackground(g, lastFixedCol);

        //휴일 색상을 그린다
        FillHolidayBackgroundColor(g);
    }

 

휴일 컬럼의 배경 색상을 칠하는 메소드인 FillHolidayBackgroundColor 메소드를 추가하도록 한다.

    private void FillHolidayBackgroundColor(Graphics g)
    {
        if (grid.GridDisplayType != GridType.YearMonthWeekNoDayType) return;
        //고정 컬럼이 없을 경우
        if (colFixed == 0)
        {
            int startX = leftHeaderWidth;
            for (int i = firstVisibleCol; i <= lastVisibleCol; i++)
            {
                YearMonthWeekNoDayHeader yearMonthHeader = grid.GridHeaderList[i] as YearMonthWeekNoDayHeader;                    
                if (yearMonthHeader.IsHoliday)
                {                        
                    g.FillRectangle(holidayColorBrush, startX + 1, topHeaderHeight + 1, yearMonthHeader.Width, 

                                         allRowsHeight - (rowHeight * firstVisibleRow) + 1);                        
                }
                startX += yearMonthHeader.Width;
            }
        } else
        {
            int startX = leftHeaderWidth + GetFixedColWidth();                
            for (int i = firstVisibleCol + colFixed; i <= lastVisibleCol && i < grid.GridHeaderList.Count; i++)
            {                    
                YearMonthWeekNoDayHeader yearMonthHeader = grid.GridHeaderList[i] as YearMonthWeekNoDayHeader;                    
                if (yearMonthHeader.IsHoliday)
                {                        
                    g.FillRectangle(holidayColorBrush, startX + 1, topHeaderHeight + 1, yearMonthHeader.Width, 

                                         allRowsHeight - (rowHeight * firstVisibleRow) + 1);                        
                }
                startX += grid.GridHeaderList[i].Width;
            }
        }
    }

 

Header영역을 그리는 DrawHeaders메소드에서 Parameter가 하나 더 추가 되었다. 추가된 파라미터는 고정 컬럼의 수정여부에 따라 고정 컬럼의 값을 변경 또는 변경할 수 없도록 지정하는 부분이다.

    /// <summary>
    /// Grid의 Header 그리기
    /// </summary>
    /// <param name="g"></param>
    /// <param name="rc"></param>

    /// <param name="colFixed"></param> 

    /// <param name="fixedColEditable"></param>
    private void DrawHeaders(Graphics g, Rectangle rc, int colFixed, bool fixedColEditable)
    {
        if (grid != null)
        {
            grid.DrawHeader(g, rc, rc.Width, colFixed, fixedColEditable);                
        }
    }

 

grid.GridHeaderList의 IsDate가 true인 경우에 선택된 Active Cell의 경우 Active Cell 표시가 되지 않도록 한다.

    /// <summary>
    /// Active Cell을 표기한다.
    /// </summary>
    /// <param name="g"></param>
    private void DrawActiveCell(Graphics g)
    {
        if (ActiveCell.Row != -1 && ActiveCell.Col != -1)
        {
            //Calendar 날짜 컬럼은 Active Cell이 되지 않도록 처리
            if (grid.GridHeaderList[ActiveCell.Col].IsDate) return;

            //파선 패턴 설정
            float[] dashValues = { 1, 1, 1, 1 };
            Pen grayPen = new Pen(Color.Gray, 1);
            grayPen.DashPattern = dashValues;
            g.DrawRectangle(grayPen, GetSelectedCellRect(ActiveCell.Row, ActiveCell.Col));
        }
    }

 

지금까지의 작업으로 완성된 WANIGrid Test 화면은 아래와 같다.

아래의 테스트 화면에 대한 사용 설명은 다음 시간에 간단히 정리하도록 하겠다.

[그림 24-1] WANIGrid Test 화면

 

그외에도 가로/세로 스크롤바를 재계산하는 ReCalcScrollBars메소드의 오류 영역을 보완했고, 자잘한 버그들을 찾아서 조치했다.

이 부분은 직접 소스를 참조하도록 하자. 

 

WANI Grid_20191009.zip
0.55MB

 

 

 

 

 

반응형
반응형

기본적인 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 솔루션에 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

반응형

+ Recent posts