반응형

지금까지 화면에 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);
            }
        }

        :

        :

    }

 

 

 

 

 

 

 

 

반응형
반응형

WANIGrid에서 행(Row) 추가, 행(Row)과 컬럼(Column) 선택 등에 대한 처리를 했다. 이제 WANIGrid Control의 실제 보여지는 부분의 최종이라고 할 수 있는 Cell선택 및 데이터 입력 처리를 위한 선택 Cell의 TextBox 입력을 할 수 있도록 구현해 보고자 한다.

 

[그림 13-1] Cell 선택 시
[그림 13-2] Cell 선택 후 TextBox 제공

 

데이터를 입력하기 위한 Cell선택 시 선택 Cell임을 표현하고, 같은 Cell을 두 번 이상 클릭해서 데이터를 입력할 수 있는 TextBox를 제공하는 것이다.

이러한 기능을 구현하기 위해서 먼저 Cell 클래스를 보자.

Cell 클래스에는 행(Row)과 열(Column) 값을 관리하기 위한 클래스 이다.

public class Cell
{
    private int row;
    private int col;

    public int Row
    {
        get { return row; }
        set { row = value; }
    }

    public int Col
    {
        get { return col; }
        set { col = value; }
    }

    public Cell(int r, int c)
    {
        row = r;
        col = c;
    }

    public void Clear()
    {
        row = -1;
        col = -1;
    }
}

 

이렇게 새로 정의한 Cell Class와 Cell 선택 시 Cell이 선택되었음을 보여주고 데이터를 입력할 수 있는 TextBox를 제공하기 위한 변수를 아래와 같이 WANIGrid.cs에 변수 선언 부분에 선언 및 정의한다.

private Cell ActiveCell = new Cell(-1, -1);
private int ActiveCell_ActiveRow = 0;   //Active Cell의 Row
private bool readOnly = false; //ReadOnly 여부
private TextBox editBox = null;   //데이터 입력을 위한 TextBox 선언
private GridState gridState = GridState.NONE;   //Grid 상태를 저장하기 위한 열거형 변수

 

WANIGrid.cs 파일의 상단에 선택된 Cell의 상태를 나열한 열거형 변수를 아래와 같이 선언한다.

public enum GridState
{
    NONE,
    ACTIVE,
    EDIT
}

 

WANIGrid.cs 내의 생성자 WANIGrid에 TextBox를 생성하기 위한 구문을 추가한다. 아래의 소스 코드 중 editBox 속성 값을 할당하는 부분과 Control Collection에 Add하는 부분을 기존 소스에 추가한다.

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();    //TextBox 생성
    editBox.BorderStyle = BorderStyle.None;   //TextBox의 테두리 제거
    editBox.Font = Font;  //TextBox 폰트 선언
    editBox.Visible = false; 
    Controls.Add(editBox);
    //마우스 우측 버튼 클릭 시 제공되는 ContextMenu 초기화
    InitializeContextMenu();
}

 

선택한 Cell의 Rectangle 값을 반환하는 메소드를 추가한다. 여기서 구해진 Rectangle을 이용해서 WANIGrid Control 내의 선택 Cell의 표시 영역을 그릴 수 있게 된다.

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);

    //선택된 Cell의 높이를 구한다.
    int top = topHeaderHeight;
    int height = 0;
    for (int i = firstVisibleRow; i <= lastVisibleRow; i++)
    {
        height = rows[i].MaxLines * rowHeight;
        if (row == i) break;
        top += height;
    }

    //선택된 Cell의 폭을 구한다.
    int left = leftHeaderWidth + 2;
    int width = 0;
    for (int i = firstVisibleCol; i <= lastVisibleCol; i++)
    {
        //보여지는 컬럼의 폭이 컨트롤의 폭 보다 클경우
        if (left + grid.GridHeaderList[i].Width > this.Width)
        {
            width = this.Width - left - 1;
        }
        else
        {
            width = grid.GridHeaderList[i].Width;
        }

        if (col == i) break;
        left += width;
    }
    //선택된 Cell의 높이와 폭을 구해서 Rectangle을 만들어서 반환
    return new Rectangle(left - 1, top + 1, width - 1, height - 1);
}

 

이렇게 구해진 Rectangle을 이용해서 직접 WANIGrid Control 내에서 사각형을 그리는 메소드를 작성한다. Active Cell을 그리는 메소드인 DrawActiveCell를 아래와 같이 만든다.

private void DrawActiveCell(Graphics g)
{
    if (ActiveCell.Row != -1 && ActiveCell.Col != -1)
    {

        //선택된 Cell 영역을 그린다
        g.DrawRectangle(new Pen(Color.FromName("HotTrack"), 1), GetSelectedCellRect(ActiveCell.Row, ActiveCell.Col));
    }
}

 

DrawActiveCell 메소드를 OnPaint 영역에 추가하도록 한다. OnPaint 메소드 영역에 추가해야 WANIGrid Control 내에서 Invalidate()메소드가 호출 될 때 바로 그려져서 사용자에게 보여지게 된다.

private void WANIGrid_Paint(object sender, PaintEventArgs e)
{
    CalcVisibleRange();
    ReCalcScrollBars();
    DrawBackground(e.Graphics, rc);
    DrawHeaders(e.Graphics, rc);
    DrawContent(e.Graphics, rc, this.ClientRectangle.Width);
    DrawActiveCell(e.Graphics); //DrawActiveCell 메소드 호출 추가 
}

이렇게 해서 Cell 이 선택될 때 선택된 Cell 영역 내의 사각형을 그리는 부분을 완성했다.

이번에는 같은 Cell을 두 번 이상 선택했을 경우, 선택 Cell 영역에 데이터를 입력할 수 있는 TextBox를 제공하기 위한 메소드인 BeginEdit를 작성한다. BeginEdit 메소드는 같은 Cell 영역을 2번 선택했을 때 TextBox를 선택한 Cell 영역 내에서 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 + 5;
        editBox.Top = r.Top + 3;
        editBox.Height = r.Height;
        editBox.Width = r.Width - 7;
        editBox.Visible = true;
        editBox.Focus();                
    }
}

 

가로/세로 스크롤을 움직이거나 마우스 휠 값이 변경되었을 때, 제공된 TextBox를 제거하도록 하는 EndEdit 메소드를 살펴보자. 어떠한 이유로든 편집이 끝나면 EndEdit 메소드가 호출되어 editBox에 입력된 값을 실제 데이터 저장 공간에 반영하고 editBox의 Visible을 false로 해서 제거된 효과를 제공한다.

private void EndEdit()
{
    if (readOnly) return;
    if (ActiveCell.Col != -1 && ActiveCell.Row != -1 && editBox.Visible)
    {                

        //editBox에 입력 된 값을 실제 데이터 저장 공간에 반영
        rows[ActiveCell.Row].DataRow[grid.GridHeaderList[ActiveCell.Col].ColumnId] = editBox.Text;
    }

    editBox.Visible = false;
    editBox.Text = "";
    gridState = GridState.ACTIVE;
}

 

가로/세로 스크롤과 마우스 휠 이벤트 발생 시에 EndEdit 메소드를 호출해서 선택 Cell에 데이터를 입력하지 못하도록 처리해야 한다.

HScrollBar_Scroll, VScrollBar_Scroll, Mouse_Wheel 메소드의 첫 라인에 EndEdit(); 를 추가하도록 한다.

 

실제 Cell 선택을 체크할 수 있는 Mouse 좌측 버튼 Down 시에 발생하는 이벤트인 MouseLeftButtonClickInContents 메소드에 Cell 선택과 TextBox 처리 부분을 추가하기로 하자.

private void MouseLeftButtonClickInContents(object sender, MouseEventArgs e)
{
    selectedCols.Clear();            
    int row = GetRowFromY(e.Y);
    int col = GetColFromX(e.X);
    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
        {
            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)
            {
                if (selectedRows.Contains(row)) selectedRows.Remove(row);   //선택된 행(Row)을 다시 선택할 경우 제거해서 행(Row) 선택 무효화
                else selectedRows.Add(row); //선택된 행(Row)를 추가
            }
        }                
    }
    else //WANIGrid Control 내부의 Content 영역을 마우스 좌측 버튼으로 클릭했을 때
    {
        selectedRows.Clear();
        selectedCols.Clear();
        //선택된 row와 col이 기존의 선택되었던 ActiveCell의 row/col 값이 같은지 체크
        if (row == ActiveCell.Row && col == ActiveCell.Col)
        {
            Rectangle r = GetSelectedCellRect(ActiveCell.Row, ActiveCell.Col);
            int k = 0;                    
            for (int i = r.Top; i < r.Bottom && k < rows[row].MaxLines - 1; i *= rowHeight, k++)
            {
                if (e.Y >= i && e.Y <= i + rowHeight) break;
            }

            //현재의 Row가 ActiveCell_ActiveRow와 같지 않으면 EndEdit 메소드 호출
            if (ActiveCell_ActiveRow != k) EndEdit();
            ActiveCell_ActiveRow = k;
            BeginEdit();
        }
        else
        {
            ActiveCell.Row = row;
            ActiveCell.Col = col;
            EndEdit();
        }
    }
    Invalidate();
}

 

이렇게 해서 WANIGrid Control에서 Cell 선택 시의 사각형 영역과 데이터 입력을 위한 TextBox 제공 기능까지를 구현해 보았다.

지금까지의 소스는 아래를 참조하면 된다.

WANI Grid_20190730.zip
0.37MB

 

 

 

반응형
반응형

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

반응형
반응형

WANIGrid Control의 겉 모습을 어느 정도 구현을 했다. 행(Row) 추가 기능을 통해서 새로운 행들을 생성하고 가로/세로 스크롤바로 WANIGrid Control 내의 모습을 볼 수 있었다. 또한 마우스 휠을 이용해서 가로/세로 스크롤바를 움직인 것과 동일한 효과도 주었다.

 

여기에 추가로 Grid의 Top Header 영역을 클릭하면 해당 컬럼의 전체 로우를 선택했음을 표현할 수 있도록 색상을 변경하는 기능을 추가해 보기로 하자.

이번 시간에는 Grid의 Top Header 영역에서 마우스 좌측 버튼을 누르면 해당 위치의 컬럼을 선택한 것으로 표시하는 기능 개발하고자 한다.

 

WANIGrid.cs 파일을 열어서 선택한 컬럼이 한 개일 수도 있지만 2개 이상의 컬럼을 선택할 수도 있다. Control Key를 누르고 마우스 좌측 버튼을 눌렀을 경우에는 1개 이상의 컬럼을 선택할 수 있도록 처리할 것이다.

마우스 좌측 버튼만 눌렀을 때에는 한 개의 컬럼만 선택될 수 있도록 처리하도록 한다.

WANIGrid Header 영역의 맨 왼쪽 컬럼을 선택했을 경우는 아무런 처리가 되지 않도록 한다. 향 후에 이 영역을 선택했을 경우 모든 영역을 선택하도록 할 수도 있겠지만 지금은 아무런 동작을 하지 않도록 한다.

 

선택된 컬럼의 Index정보를 관리하기 위한 변수, 선택된 영역의 색상을 지정할 수 있도록 지원하기 위한 변수와 현재 마우스의 좌측 버튼 클릭 시 X, Y 좌표를 저장하기 위한 변수를 선언하도록 한다.

 

private List<int> selectedCols = new List<int>();   //선택된 컬럼(Columns)들을 관리하기 위한 변수

private Point mousePoint = new Point(0, 0); //Click한 마우스 좌표를 저장하기 위한 변수
private SolidBrush selectedColor = new SolidBrush(Color.LightCyan);

 

선택된 영역의 색상은 변경하기 위한 Property를 아래와 같이 설정한다. 디폴트 색상이 아닌 다른 색상을 지정하고자 할 경우 아래의 Property를 이용해서 색상을 변경할 수 있다.

 

public Color SelectedColor
{            
    set { selectedColor = new SolidBrush(value); }
}

 

실제 마우스 좌측 버튼이 클릭 되었을 때 마우스 X좌표의 위치로 컬럼의 Index정보를 반환하는 메소드는 아래와 같다.

 

private int GetColFromX(int X)
{
    int col = 0;
    int tempWidth = leftHeaderWidth; //맨 첫번째 비어 있는 컬럼의 폭

    if (X >= 0 && X <= leftHeaderWidth) //마우스 좌측 버튼 클릭 시 X좌표가 leftHeaderWidth 영역 내에 있을 경우에는 col변수에 -1값을 설정
    {
        col = -1;
    }
    else
    {

        //마우스 좌측 버튼 클릭 시 X좌표의 위치가 현재 보여지는 컬럼 내의 위치에 해당하는지 체크
        for (col = firstVisibleCol; col < lastVisibleCol; col++)
        {
            int width = grid.GridHeaderList[col].Width;
            if (X < width + tempWidth) break; //X좌표 위치와 컬럼 Index가 매핑 되었을 경우
            tempWidth += width;
        }
    }
    if (col > grid.GridHeaderList.Count - 1) col = -1; //매핑된 컬럼 Index값이 실제 컬럼 개수보다 많을 경우 -1을 반환
    return col;

}

 

마우스 좌측 버튼 클릭 시 X좌표에 해당하는 컬럼 Index를 가져오는 메소드까지 만들었다.

이제는 마우스 버튼이 눌러졌을 때 발생하는 이벤트에 마우스 좌측 버튼 클릭 부분을 추가하기로 하자.

MouseDown 이벤트 처리 부분을 살펴보자.

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); 
        }         
    }
}

 

실제 WANIGrid Top Header 영역이 클릭 되었을 때 호출되는 MouseLeftButtonClickInTopHeaderHeight 메소드는 아래와 같다.

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 (ModifierKeys != Keys.Control)
            {
                //선택된 컬럼일 경우
                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 (selectedCols.Contains(col)) selectedCols.Remove(col);   //선택된 컬럼을 다시 선택할 경우 제거해서 컬럼 선택 무효화
                    else selectedCols.Add(col); //선택된 컬럼을 추가
        }
        Invalidate();
    }

    //마우스 좌측 버튼 클릭 시 X/Y좌표를 저장
    mousePoint.X = e.X;
    mousePoint.Y = e.Y;
}

 

지금까지는 마우스 좌측 버튼 클릭 시 마우스 X좌표 위치에 대응하는 컬럼의 Inxex 값을 찾기 위한 로직을 만들었고, 실제 사용자에게 선택된 컬럼을 색상으로 보여주는 부분을 추가해야 실제 WANIGrid Control 실행 시에 사용자와 직접 상호작용하면서 선택된 컬럼 영역을 다이나믹하게 보여주게 된다.

 

기존의 DrawBackground 메소드에 선택된 컬럼의 배경 색상을 변경하는 기능을 아래와 같이 추가해야 한다.

 

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

        //WANIGrid Control 영역에서 보여지고 있는 컬럼 내에서 선택된 컬럼의 폭을 구한다.
        for (int j = firstVisibleCol; j < lastVisibleCol && j < index; j++)
        {
            left += grid.GridHeaderList[j].Width;
        }

        if (allRowsHeight > 0) //추가된 행(Row)가 있을 경우에만 처리
        {             
            g.FillRectangle(selectedColor, left + 1, topHeaderHeight, grid.GridHeaderList[index].Width + 1, 

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

 

이렇게 구현한 WANIGrid Control이 실행되었을 때 화면이다.

[그림 11-1] 1개의 컬럼을 선택했을 경우
[그림 11-2] 컨트롤 키를 누른 상태에서 여러 컬럼을 선택했을 경우

WANIGrid Control의 모습이 전보다는 좀 더 진화된 상태가 되었다.

다음에는 Row 선택 기능을 추가해 보기로 하자. 

 

지금까지의 소스는 아래를 참고하면 된다.

WANI Grid_20190725.zip
0.35MB

반응형
반응형

지금까지의 작업으로 Grid Control의 외적인 모습들을 개략적으로 만들어 왔다.

오늘은 Grid Control에 남아 있는 세로 스크롤바 이벤트 처리와 마우스 휠 이벤트 처리를 구현해서 Grid Control에서 정보를 Viewing하기 위한 일반적인 방법들을 구현하고자 한다.

 

저자가 작성한 소스를 본 사람들은 알겠지만 다소 엉성한 부분들이 꽤 있을 수 있다. 하나 하나의 기능을 만들어가면서 내용을 정리하고 있는 수준이다 보니 많은 부분이 깔끔하지 못할 수 있지만 나 처럼 누군가가 만들어 놓은 Grid Control을 사용만 해오다가 직접 내가 이러한 Control을 만들어 보면서 하나씩 배우고 익히는 부분이기에 더 그럴 수 있을 것이다. 

저자가 구현한 부분에 보다 좋은 아이디어나 방법이 있다면 댓글로 많은 조언과 도움을 주시길 희망하며...

 

[그림 10-1] 행 추가를 통해서 세로 스크롤바 및 마우스 휠 사용

세로 스크롤바의 경우에는 행(Row)의 맨 처음과 마지막 까지 스크롤바의 움직임에 따라 행(Row)을 이동하면서 행의 컬럼들을 보여주게 된다.

WANIGrid Control 내에서 세로스크롤바가 처음 나타났을 때의 행(Row) 개수를 파악하고 저장하기 위한 rowsCount 변수를 선언한다.

private int rowsCount = 0;  //Grid Control에서 보여지는 행의 갯수를 저장하기 위한 변수

 

WANIGrid Control의 사이즈가 변경될 때, Control 내의 표기할 수 있는 행(Row)의 개수가 변경될 수 있기에 rowsCount 변수를 초기화 하는 부분을 추가한다.

HScrollBar/VScrollBar를 초기화 하는 메소드에 rowsCount를 초기화한다.

public void InitializeScollBar()
{
    rowsCount = 0; //Control 크기가 변경되었을 경우 행(Row) 개수 초기화

    //가로 스크롤바 설정
    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;        
}

 

WANIGrid Control의 폭/높이를 기준으로 가로/세로 스크롤바를 보여줄지를 결정하는 메소드인 ReCalcScrollBar에서 rowsCount 변수에 Row개수를 설정하는 부분을 추가한다.

WANIGrid Control에서 세로 스크롤바가 나타나기 전의 Row수를 설정하는 부분을 추가해서 세로 스크롤바 또는 마우스 휠 이벤트 처리 시에 사용할 수 있도록 한다.

private void ReCalcScrollBars()
{
    if (hScrollBar == null || vScrollBar == null) return;
    //컬럼의 전체 폭
    if (grid != null && grid.GridHeaderList != null) 

        allColsWidth = grid.GridHeaderList[grid.GridHeaderList.Count() - 1].Left + grid.GridHeaderList[grid.GridHeaderList.Count() - 1].Width;

    //컬럼의 폭이 클라이언트 사이즈 폭 보다 클 경우 가로 스크롤바를 보여준다.
    if ((allColsWidth > 0) && (allColsWidth > ClientSize.Width - ysclWidth))
    {
        hScrollBar.Visible = true;
        hScrollBar.LargeChange = ((lastVisibleCol - firstVisibleCol) + 1) / 2 + 1;
        lastHScrollValue = ((lastVisibleCol - firstVisibleCol) + 1) / 2 + 1;
    }
    else
    {
        hScrollBar.Visible = false;
        grid.FirstVisibleCol = 0; //Control 크기가 바뀌면서 hScrollBar가 가려지면 Grid의 첫번째 컬럼 부터 그려지도록 처리
    }
    //로우의 높이가 클라이언트 사이즈 높이 보다 클 경우 세로 스크롤바를 보여준다.
    if (allRowsHeight > 0 && (allRowsHeight > Height - topHeaderHeight - xsclHeight))
    {
        vScrollBar.Visible = true;
        if (rowsCount == 0) rowsCount = (allRowsHeight / rowHeight) - 1; //WANIGrid Control에서 세로 스크롤바가 나타나기 전의 Row 개수 설정
        vScrollBar.Maximum = allRowsHeight;
        vScrollBar.LargeChange = rowHeight * 5;
        vScrollBar.SmallChange = rowHeight;
    }
    else
    {
        vScrollBar.Visible = false;
        grid.FirstVisibleRow = 0;
    }
}

 

세로 스크롤바를 움직였을 때 발생하는 이벤트

private void VScrollBar_Scroll(object sender, ScrollEventArgs e)
{
    firstVisibleRow = e.NewValue / rowHeight; //입력되는 세로 스크롤바의 값을 Row 위치로 변환

    if (firstVisibleRow > (allRowsHeight / rowHeight)) return;
    if (firstVisibleRow >= (allRowsHeight / rowHeight) - rowsCount)
    {
        firstVisibleRow = (allRowsHeight / rowHeight) - rowsCount;
        grid.FirstVisibleRow = firstVisibleRow;
        vScrollBar.Value = vScrollBar.Maximum;                
    }
    else
    {
        grid.FirstVisibleRow = firstVisibleRow;
        vScrollBar.Value = firstVisibleRow * rowHeight;
    }
                        
    CalcVisibleRange();
    ReCalcScrollBars();
    Invalidate();
}

 

마우스 휠 처리는 컨트롤 키를 누르고 휠을 움직였을 때와 그렇지 않고 휠만 움직였을 떄로 구분해서 처리한다.

컨트롤 키를 누른 상태에서 휠을 위/아래로 움직이면 가로 스크롤바를 움직인 효과가 나도록 했고, 마우스 휠만 위/아래로 움직였을 경우에는 세로 스크롤바를 움직인 효과가 나도록 처리했다.

마우스 휠의 경우 위로 굴리면 양수 값이 아래로 굴리면 음수 값이 나오게 된다. 

컨트롤 키를 누른 상태에서 마우스 휠을 위로 굴리면 가로 스크롤의 좌측으로, 아래로 굴리면 우측으로 움직이는 효과가 나도록 처리했다.

마우스 휠을 위로 굴리면 세로 스크롤의 위쪽으로, 아래로 굴리면 세로 스크롤의 아래쪽으로 움직인 효과가 나도록 처리했다.

private void Mouse_Wheel(object sender, MouseEventArgs e)
{
    //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) - rowsCount)
            {
                firstVisibleRow = (allRowsHeight / rowHeight) - rowsCount;
                grid.FirstVisibleRow = firstVisibleRow;
                vScrollBar.Value = vScrollBar.Maximum;
            }
            else
            {
                grid.FirstVisibleRow = firstVisibleRow;
                vScrollBar.Value = firstVisibleRow * rowHeight;
            }
        }
    }
    CalcVisibleRange();
    ReCalcScrollBars();
    Invalidate();
}

 

이렇게 해서 지금까지는 Grid Header를 만들고, Row를 추가하고 가로/세로 스크롤바 및 마우스 휠 이벤트 처리까지 구현을 해 보았다.

 

추가 한 행(Row)에 직접 데이터를 입력하고 보여주는 부분을 구현해 나가기에 앞서 WANIGrid Control 내에서 마우스로 클릭한 부분의 좌표를 Grid 내의 Cell 선택 영역으로 표시하기 위한 기능을 구현할 생각이다.

사용자가 WANIGrid Control 내의 Header 영역을 제외한 부분을 클릭하면 클릭 된 영역이 어느 행과 컬럼인지를 식별하기 위한 메소드를 만들어 볼 것이다.

부족하지만 Grid Control에 필요한 기능들을 고민과 학습을 통해서 하나씩 하나씩 천천히 구현해 나가볼 생각이다.

지금까지의 소스는 아래와 같다.

WANI Grid_20190721.zip
0.33MB

저자의 부족한 소스를 보고 조언 및 더 나은 방법이 있다면 기탄없이 댓글을 달아주시길 희망해 본다.

여러분들이 보내주는 의견들이 곧 WANIGrid Control의 기능을 보다 편리하고 고도화해 나갈 수 있는 방법이라 생각한다.

 

 

반응형
반응형

지금까지 Grid Control의 Header 부분과 가로 ScrollBar까지의 기능을 만들어 보았다.

추가한 Context Menu 중 "행 추가" 기능을 만들어서 Grid Control에서 행(Row)를 추가해 보기로 하자.

[그림 9-1] 행 추가

행(Row) 추가를 하게 되면 Grid Control의 마지막 행(Row) 뒤에 새로운 행(Row)를 추가하도록 한다.

한 개의 행에 대한 컬럼들은 Header 설정 시에 각 컬럼별로 폭(Width), 컬럼 ID, 정렬방법 등을 설정했기에 각 컬럼의 데이터를 저장하기 위해서 DataTable을 이용하기로 한다.

실제 사용 시에도 Database에서 가져온 데이터를 Grid Control에서 Display할 경우 Datatable 타입으로 할당해서 사용하는 것이 일반적이기에 Grid Control 내에서 추가되는 모든 행(Row)은 DataTable에 저장하기로 한다.

Header 설정 시 지정한 Column ID가 Datatable의 Column ID로 사용되어진다.

 

WANIGrid에서 행(Row) 추가 시에 생성되는 Row Class를 먼저 살펴보자.

1개의 행에 대해서 관리되어져야 할 정보는

  • 행(Row)의 Background Color - 행(Row) 선택 시 백그라운드 색상
  • 행(Row)의 Foreground Color 
  • 행(Row) 선택 여부
  • 행(Row) 데이터의 최대 라인수 - 기본 1라인
  • 행(Row)에 해당하는 컬럼 정보를 담고 있는 DataRow

한 개의 행(Row) 정보를 관리하기 위한 Row Class의 속성과 메소드를 살펴보자.

    public class Row : ICloneable
    {
        #region 변수
        private Color backColor;
        private Color foreColor;
        private bool selected;
        private int maxLines;
        private DataRow row;
        #endregion 변수

        #region 생성자
        public Row()
        {
            maxLines = 1; //maxLines는 기본 1라인
            selected = false;
            foreColor = Color.Black;
            backColor = Color.LightGray;
        }

        public Row(DataRow dRow)
        {
            maxLines = 1;
            selected = false;
            foreColor = Color.Black;
            backColor = Color.LightGray;
            row = dRow;
        }
        #endregion 생성자

        #region Properties
        public int MaxLines
        {
            get { return maxLines; }
            set
            {
                if (maxLines != value) maxLines = value;
            }
        }

        public Color BackColor
        {
            get { return backColor; }
            set { backColor = value; }
        }

        public Color ForeColor
        {
            get { return foreColor; }
            set { foreColor = value; }
        }

        public bool Selected
        {
            get { return selected; }
            set { selected = value; }
        }

        public DataRow DataRow
        {
            get { return row; }
            set { row = value; }
        }
        #endregion Properties

        #region Method
        public object Clone()
        {
            Row row = new Row();
            row.BackColor = backColor;
            row.ForeColor = foreColor;
            row.Selected = selected;
            return row;
        }
        #endregion Method
    }

 

행(Row) 추가 시 생성된 Row Class에 대한 관리가 필요하며, 이러한 관리를 위해서 RowCollection Class를 생성한다.

주요 기능으로는

  • Index 위치의 Row 정보를 설정하거나 가져온다
  • 특정 Row 정보가 존재하는 Index 반환
  • Row를 추가하고 Index를 반환
  • 여러 개의 Row를 추가
  • 특정 Row를 제거
  • 모든 Row를 Clear
  • Row의 Index 반환
  • 특정 Index 위치에 Row를 Insert

    public class RowCollection: CollectionBase
    {
        /// index 위치의 Row 정보를 설정하거나 가져온다.
        public Row this[int index]
        {
            get
            {
                if (index >= 0 && index < this.Count)
                    return List[index] as Row;
                else
                    return null;
            }
            set
            {
                List[index] = value;
            }
        }

        /// Row 정보가 존재하는 Index를 반환한다.
        public int this[Row item]
        {
            get { return List.IndexOf(item); }
        }

        /// Row를 추가하고 추가한 Row의 Index를 반환
        public virtual int Add(Row item)
        {
            int index = List.Add(item);
            return index;
        }

        /// 여러 개의 Row를 추가한다.
        public virtual void AddRange(Row[] items)
        {
            lock(List.SyncRoot)
            {
                for (int i = 0; i < items.Length; i++)
                {
                    List.Add(items[i]);
                }
            }
        }

        /// Row를 제거한다.
        public virtual void Remove(Row item)
        {
            List.Remove(item);
        }

        /// 모든 Row를 지운다.
        public new void Clear()
        {
            List.Clear();
        }

        /// Row의 Index를 반환  
        public virtual int IndexOf(Row item)
        {
            return List.IndexOf(item);
        }

        /// 특정 Index 위치에 Row를 Insert 한다.
        public virtual void Insert(int index, Row item)
        {
            List.Insert(index, item);
        }
    }

 

행(Row) 추가 시 다루어져야 할 정보에 대해서 알아 보았다. 행과 관련된 Row Class, RowCollection Class를 이용해서 추가 된 행(Row)을 WANIGrid Control에 나타내고(Display), 추가된 행(Row)의 컬럼 정보들을 관리하게 된다.

각 행(Row)에 대한 정보는 Row Class를 이용해서 관리하며, 행(Row) 전체에 대한 관리는 RowCollection Class를 이용해서 관리하게 될 것이다.

추가된 행(Row) 정보를 근간으로 사용자에게 시각적으로 행(Row)이 추가되었음을 보여줄 수 있도록 처리하기 위한 로직을 WINIGrid Control에 추가할 것이다.

 

WINIGrid Control에 추가되는 멤버 변수는

    private int firstVisibleRow = 0;    //화면상에서 처음 보여져야할 로우
    private int lastVisibleRow = 0;     //화면상에서 마지막에 보여질 로우

    private int allRowsHeight = 0;  //Grid Row의 전체 높이 

    private int rowHeight = 0;  //Row 높이
    private RowCollection rows;   //Row 정보를 관리하게 되는 Collection Class
    private DataTable dataSource;  //하나의 Row에 속하는 컬럼 정보를 저장하기 위한 DataTable 

 

dataSource 멤버 변수와 연계되는 프로퍼티 DataSource를 정의

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

WINIGrid Control의 Header Font와 Content Font를 정의하기 위한 프로퍼티 추가

    [Category("Font"), Description("The header font")]
    public Font HeaderFont
    {
        get { return headerFont; }
        set
        {
            if (headerFont != value)
            {
                headerFont = value;
                topHeaderHeight = headerFont.Height + 4;
            }
        }
    }
    [Category("Font"), Description("The Content font")]
    public new Font Font
    {
        get { return contentFont; }
        set
        {
            if (contentFont != value)
            {
                contentFont = value;
                rowHeight = Font.Height + 4;
            }
        }
    }

 

WINIGrid Control의 생성자에 RowCollection 멤버 변수 생성 및 HScrollBar/VScrollBar 설정을 추가한다.

public WANIGrid()

{

    .....

    rows = new RowCollection();  //RowCollection 생성
    hScrollBar.SmallChange = 1;   //HScrollBar의 SmallChange 값을 1로 설정
    rowHeight = Font.Height + 4;   //추가되는 Row의 높이 설정
    vScrollBar.SmallChange = rowHeight;  //VScrollBar의 SmallChange 값을 Row의 높이로 설정

    .....

}

 

Context Menu 초기화 메소드인 InitializeContextMenu()에 "행 추가" 선택 시 발생하는 이벤트 핸들러를 등록한다.

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[2].Click += new EventHandler(OnMenu_AppenRow_Click);
}

 

가로/세로 ScrollBar에 대한 Visible, Maximum/LargeChange/SmallChange 등을 설정하는 ReCalcScrollBars() 메소드에 Row의 수에 따라 세로 스크롤바를 보여주는 로직을 추가하도록 한다.

private void ReCalcScrollBars()
{

    .....

    //로우의 높이가 클라이언트 사이즈 높이 보다 클 경우 세로 스크롤바를 보여준다.
    if (allRowsHeight > 0 && (allRowsHeight > Height - topHeaderHeight - xsclHeight))
    {
        vScrollBar.Visible = true;
        vScrollBar.Maximum = allRowsHeight;
        vScrollBar.LargeChange = rowHeight * 5;
        vScrollBar.SmallChange = rowHeight;
    }
    else
    {
        vScrollBar.Visible = false;
        grid.LastVisibleRow = 0;
    }

    .....

}

 

화면에 보여져야 할 영역을 계산하는 함수인 CalcVisibleRange()에서 firstVisibleRow 값을 기준으로 lastVisibleRow를 찾아내는 로직을 추가한다.

private void CalcVisibleRange()
{

    ......

    //보여져야 할 Row 영역 계산
    int tempRow = 0;
    int rowIndex = 0;
    if (rows.Count > 0)
    {
        for (rowIndex = firstVisibleRow, tempRow = 0; rowIndex < rows.Count && 

             tempRow < Height - xsclHeight - topHeaderHeight; rowIndex++)
        {
            tempRow += rows[rowIndex].MaxLines * rowHeight;
        }
        lastVisibleRow = rowIndex;

        if (lastVisibleRow >= rows.Count) lastVisibleRow = rows.Count - 1;
    }
    else
    {
        lastVisibleRow = 0;
    }

    ......

}

 

행 추가 선택 시 행을 추가하기 위한 메소드

public void AppendRow()
{
    DataRow row = dataSource.NewRow(); //1개 행의 컬럼들의 정보를 담기 위한 Row생성
    Row r = new Row(row);  //DataRow 생성
    rows.Add(r); //DataRow 추가
    dataSource.Rows.Add(row); //DataTable에 1개의 행을 추가
    allRowsHeight += Font.Height + 4;  //행이 추가될때 추가된 전체 행 높이를 계산
    rowHeight = Font.Height + 4; //행 1개의 높이 설정
    OnRowsChanged(); //Row변경 시 처리하는 메소드 호출
}

 

public void OnRowsChanged()
{
    ReCalcScrollBars(); //가로세로 스크롤바 설정
    CalcVisibleRange();  //화면에 보여져야 할 영역 계산
    Invalidate(true);
}

 

Context Menu 중 "행 추가"를 선택했을 때 발생하는 Event 처리 메소드

protected void OnMenu_AppenRow_Click(object sender, EventArgs e)
{
    AppendRow();
}

 

WANIGrid의 내용을 그리기 위한 메소드를 작성한다.

/// 
/// Grid의 내용 그리기
/// 
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++)
            {
                if (j == firstVisibleCol)
                {
                    g.FillRectangle(brush, 1, columnStartY + 1, leftHeaderWidth, rowHeight);
                    g.DrawRectangle(pen, 1, columnStartY + 1, leftHeaderWidth, rowHeight);
                    columnStartX += leftHeaderWidth;     
                }

                //보여지는 컬럼의 폭이 컨트롤의 폭 보다 클경우
                if (columnStartX + grid.GridHeaderList[j].Width > controlWidth)
                {
                    columnWidth = controlWidth - columnStartX - 3;
                    g.DrawRectangle(pen, columnStartX + 1, columnStartY + 1, 

                                             columnWidth, rowHeight);                            
                }
                else
                {
                    columnWidth = grid.GridHeaderList[j].Width;
                    g.DrawRectangle(pen, columnStartX + 1, columnStartY + 1, 

                                            columnWidth, rowHeight);                            
                }
                columnStartX += columnWidth;                        
            }

            columnStartY += rowHeight;
        }
    }
    catch (Exception e)
    {
        MessageBox.Show(e.Message);
    }
}

 

WANIGrid Control의 Paint 메소드인 WANIGrid_Paint(object sender, PaintEventArgs e) 에 DrawContent 메소드를 추가한다.

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

 

Build 및 실행 후 "행 추가"를 선택하면 아래와 같이 행이 추가되는 모습을 직접 WANIGrid Control에서 볼 수 있게 된다.

행의 전체 높이가 WANIGrid Control의 높이 보다 적기에 세로 스크롤바는 현재 보이지 않는다.

[그림 10-2] 행 추가 화면

행을 계속 추가하면 행 전체의 높이가 WANIGrid Control의 높이보다 크게 되면 세로 스크롤바가 나타나게 된다.

[그림 10-3] 전체 행 높이가 크지면 나타나는 세로 스크롤바

WANIGrid Control에서 행을 추가 했을 경우, 사용자에게 추가된 행을 시각적으로 보여주는 방법과 세로 스크롤바를 제어하는 방법을 구현해 보았다.

다음 시간에는 행 추가 후 세로 스크롤바 제어에 대해서 살펴 보기로 하자.

지금 까지의 소스는 아래의 링크를 통해서 다운 받을 수 있다.

WANI Grid_20190709.zip
0.33MB

 

 

 

 

반응형
반응형

지금까지 WANIGrid에 Column Header를 설정하고 생성해서 Grid의 모습으로 보여지게끔 만들었다.

지금까지 만든 Grid 틀에 Row를 생성하기 위한 Interface인 Context Menu를 만들어 보기로 하자.

Control 내의 Mouse 우측 버튼 클릭 시 제공될 Context Menu를 만들기 전에 메뉴와 메시지 등에 대한 다국어 지원을 할 수 있도록 설정하는 방법을 설명하겠다.

현재 WANI Grid 프로젝트 내에 다국어처리를 위한 Resource파일을 관리하기 위한 폴더를 하나 추가한다.

폴더의 명칭은 간단하게 Resources로 한다.

 

WANIGrid Control의 속성 중 다국어 지원을 위해서 Language와 Localizable 속성에 아래와 같이 값을 설정한다.

  • Language - 영어(미국)
  • Localizable - True

[그림 8-1] WANIGrid Control 속성 변경

WANIGrid Control 내에서 마우스 우측 버튼 클릭 시 제공되어야 할 메뉴는 아래와 같이 한글과 영문으로 제공한다.

  • Row.Append - 행 추가 (Append row)
  • Row.After.Insert - 다음 행에 추가 (Insert after this row)
  • Row.Before.Insert - 이전 행에 추가 (Insert before this row)
  • Row.Delete - 행 삭제 (Delete row)

한글과 영문으로 제공하기 위해서 Resource파일들을 WANI Grid 프로젝트의 Resources폴더 아래에 만든다.

만들어진 Resources폴더 선택 후 우측 버튼을 눌러서 [추가] > [새항목]을 선택해서 리소스를 만든다.

[그림 8-2] 리소스 파일 생성

Default 언어를 영어(미국)으로 설정했고, 한글을 추가하는 것으로 했기에 리소스 파일을 LanguageResource.resx와 LanguageResource.ko-KR.resx으로 2개 생성한다.

[그림 8-3] 기본인 영어(미국) 리소스 파일 생성 및 작성
[그림 8-4] 한국어(대한민국) 리소스 파일 생성 및 작성

WINIGrid.cs 파일을 열어서 마우스 우측 버튼 클릭 시 제공되어야 할 ContextMenu를 담기위한 변수를 선언한다.


        private ContextMenu rightClickMenu = null;  //Mouse 우측 버튼 클릭 시 제공되는 메뉴

변수 선언 후 ContextMenu 항목을 생성하기 위한 초기화 메소드를 아래와 같이 생성한다.

        private void InitializeContextMenu()
        {
            //영문 메뉴가 정상적으로 나오는지 확인을 위해서는 아래 주석처리한 2라인을 주석해제해서 테스트 하면 된다.
            //System.Globalization.CultureInfo cultureInfo = new System.Globalization.CultureInfo("en-US"); // 한국어 : "ko-KR"
            //LanguageResource.Culture = cultureInfo;
            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);
        }

 

WANIGrid Control의 생성자에 위의 ContextMenu 초기화 메소드를 호출하도록 추가한다.

        public WANIGrid()
        {            
            InitializeComponent();
            if (grid == null) grid = new WANI_Grid.Grid.Grid();            
            hScrollBar.SmallChange = 1;
            //마우스 우측 버튼 클릭 시 제공되는 ContextMenu 초기화
            InitializeContextMenu();            
        }        

 

이렇게 컨트롤 내에서 마우스 우측 버튼 클릭 시 제공되는 Context Menu에 대한 설정은 마무리 되었다.

실제 마우스 우측 버튼 클릭 시 발생하는 이벤트를 추가해서 실제 메뉴가 정상적으로 나타나는지 확인하도록 하자.

WANIGrid Control에서 MouseDown 이벤트를 추가하고, Column Header 영역에서 우측 버튼이 클릭 되었을 때는 메뉴가 나타나지 않도록 처리하고 Column Header영역이 아닌 곳에서는 메뉴가 나타나도록 한다.

        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);
            }
        }

 

소스 추가 후 빌더해서 실행하면 아래와 같이 ContextMenu 가 정상적으로 제공됨을 알 수 있다.

 

[그림 8-5] ContextMenu 한글

ContextMenu가 영어(미국)으로 제공되는지 확인하기 위해서는 InitializeContextMenu 메소드 내의 주석처리 되어져 있는 부분의 주석을 해제하고 실행하면 된다.
            System.Globalization.CultureInfo cultureInfo = new System.Globalization.CultureInfo("en-US"); // 한국어 : "ko-KR"
            LanguageResource.Culture = cultureInfo;

 

[그림 8-6] ContextMenu 영어

지금까지 설명한 소스는 아래의 첨부 파일을 참조하자.

WANI Grid_20190622.zip
0.30MB

반응형
반응형

앞 시간에 Grid Header를 그렸고 이제는 가로 스크롤바에 대한 이벤트를 생성해보기로 하자.

Column이 많아지면 가로 스크롤바가 나타나고 스크롤 이벤트에 따라 컬럼들을 이동해서 보여주어야 한다.

이러한 작업을 하기 위해서 먼저 DefaultHeaderGenerator Class의 DrawHeaders 메소드를 살펴 보기로 하자.

이전 소스에서는 첫번째 컬럼부터 마지막 컬럼까지 Header를 보여주기 위한 목적으로 DrawHeaders 메소드를 간단하게 작성했었다.

이제는 화면에 보여져야 할 첫번째 컬럼과 마지막 컬럼을 기준으로 Grid Header정보를 그려야 한다.

이전 소스의 변경 부분은 아래와 같다.

[HeaderGenerator.cs]

Header의 첫번째 컬럼에서 마지막 컬럼까지의 폭이 Client 폭 보다 클 경우에 true, 그렇지 않을 경우에 false를 설정하기 위한 변수 선언

protected bool isLargeLastCol = false;    //첫번째 컬럼에서 마지막 컬럼 까지의 폭이 Client 폭 보다 클 경우 true

 

외부에서 get/set 할 수 있도록 Property 선언

public bool IsLargeLastCol
{
    get { return isLargeLastCol; }
    set { isLargeLastCol = value; }
}

 

[DefaultHeaderGenerator.cs]

public override void DrawHeaders(int firstVisibleCol, int lastVisibleCol, int controlWidth, Graphics graphics, Rectangle rect)
{
    SolidBrush brush = new SolidBrush(SystemColors.Control);
    Pen pen = new Pen(SystemColors.ControlLight);

    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 (columnStartX  + headerWidth > controlWidth)
        {
            headerWidth = controlWidth - columnStartX - 3;
            if (lastVisibleCol == _headers.Count - 1) IsLargeLastCol = true;
        } else
        {
            IsLargeLastCol = false;
        }

        //헤더영역의 사각형을 채우고 테두리를 그린다.
        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);

         columnStartX += headerWidth;
    }
}

 

[WANIGrid.cs]

가로 스크롤바를 움직였을 때 동작하기 위한 변수를 추가로 선언한다.

private int allColsWidth = 0;   //Grid 컬럼 전체의 폭
private int lastHScrollValue = 0; //가로 스크롤바를 최대값까지 갔을 경우 보여지는 컬럼 수를 2로 나눈 값 - 스크롤 시 이동 거리를 말함
private bool chkLast = false; //마지막 컬럼의 폭을 지정했던 폭 만큼 보여졌을 때 true

 

생성자에 hScrollBar의 최소 변경 값을 설정한다.

public WANIGrid()
{            
    InitializeComponent();
    if (grid == null) grid = new WANI_Grid.Grid.Grid();             
    hScrollBar.SmallChange = 1;
}

 

이벤트 핸들러 초기화

public void EventHandlerInitialize()
{
    hScrollBar.Scroll += new ScrollEventHandler(HScrollBar_Scroll);
}

 

가로 스크롤바를 스크롤 했을 때 발생하는 이벤트

        public void HScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            //가로 스크롤바를 움직여서 마지막 컬럼이 Client 영역에 나타났을 때
            if (e.NewValue >= (grid.GridHeaderList.Count - lastHScrollValue))
            {
                //마지막 컬럼의 전체가 Client 영역에 모두 나타나지 않았을 때
                if (grid.HeaderGen.IsLargeLastCol)
                {
                    //마지막 컬럼을 1칸 앞으로 땡긴다.
                    if (grid.LastVisibleCol >= grid.GridHeaderList.Count - 1)
                    {
                        if (!chkLast)
                        {
                            firstVisibleCol += 1;
                            grid.FirstVisibleCol = firstVisibleCol;
                            e.NewValue = firstVisibleCol;
                            chkLast = true;
                        }
                    }
                }
                else
                {
                    if (e.NewValue <= grid.GridHeaderList.Count - lastHScrollValue)
                    {
                        firstVisibleCol = e.NewValue;
                        grid.FirstVisibleCol = firstVisibleCol;
                        chkLast = false;
                    }
                }
            }
            else
            {
                if (e.NewValue < (grid.GridHeaderList.Count - lastHScrollValue))
                {
                    if (firstVisibleCol < grid.LastVisibleCol)
                    {
                        firstVisibleCol = e.NewValue;
                        grid.FirstVisibleCol = firstVisibleCol;
                        chkLast = false;
                    }
                }
            }
            CalcVisibleRange();
            ReCalcScrollBars();
            Invalidate();
        }

 

가로/세로 스크롤바를 재계산 하는 메소드. 세로 스크롤바 재계산 로직은 추후에 추가될 것이다.

        private void ReCalcScrollBars()
        {
            if (hScrollBar == null || vScrollBar == null) return;

            if (grid != null && grid.GridHeaderList != null) 

                allColsWidth = grid.GridHeaderList[grid.GridHeaderList.Count() - 1].Left + grid.GridHeaderList[grid.GridHeaderList.Count() - 1].Width;

 

            //컬럼의 폭이 클라이언트 사이즈 폭 보다 클 경우 가로 스크롤바를 보여준다.
            if ((allColsWidth > 0) && (allColsWidth > ClientSize.Width - ysclWidth))
            {
                hScrollBar.Visible = true;
                hScrollBar.LargeChange = ((lastVisibleCol - firstVisibleCol) + 1) / 2 + 1;
                lastHScrollValue = ((lastVisibleCol - firstVisibleCol) + 1) / 2 + 1;
            }
            else
            {
                hScrollBar.Visible = false;
                grid.FirstVisibleCol = 0; //Control 크기가 바뀌면서 hScrollBar가 가려지면 Grid의 첫번째 컬럼 부터 그려지도록 처리
            }
        }

 

WANIGrid Control Load 시에 스크롤바의 이벤트 핸들러를 초기화 한다.

        private void WANIGrid_Load(object sender, EventArgs e)
        {
            //더블버퍼링(Double Buffering) 처리
            this.SetStyle(ControlStyles.DoubleBuffer, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.UserPaint, true);

            rc = new Rectangle(0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height);
            EventHandlerInitialize(); //스크롤 이벤트 초기화
            InitializeScollBar();
            Invalidate();
        }

 

WANIGrid Control Resize 이벤트 시에 ReCalcScrollBars 추가 한다.

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

 

WANIGrid Control Paint 이벤트 시에 ReCalcScrollBars 추가 한다.
        private void WANIGrid_Paint(object sender, PaintEventArgs e)
        {
            CalcVisibleRange();
            ReCalcScrollBars();
            DrawBackground(e.Graphics, rc);
            DrawHeaders(e.Graphics, rc);
        }

 

WANIGrid Control SizeChanged 이벤트 시에 ReCalcScrollBars 추가 한다.
        private void WANIGrid_SizeChanged(object sender, EventArgs e)
        {
            InitializeScollBar();
            ReCalcScrollBars();
            Invalidate();
        }

 

WANIGrid Control ClientSizeChanged 이벤트 시에 ReCalcScrollBars 추가 한다.
        private void WANIGrid_ClientSizeChanged(object sender, EventArgs e)
        {
            rc = new Rectangle(0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height);
            InitializeScollBar();
            ReCalcScrollBars();
            Invalidate();
        }

 

이렇게 소스를 변경하고 실행하면 스크롤바를 움직일때 마다 컬럼이 바뀌어짐을 알 수 있다.

[그림 7-1] 실행 첫 화면
[그림 7-2] 스크롤 이벤트 발생 시 변경된 화면

이렇게 해서 WANIGrid Control의 가로 스크롤 이벤트 발생 시 Grid Header의 컬럼 정보를 볼 수 있도록 했다.

행이 추가 되기 전 전체 컬럼을 모두 살펴볼 수 있는 기능을 개발한 것이며, 오늘 까지 이렇게 만든 기능들은 추후 계속해서 변화 또는 개선이 되어질 것이다. 

그때 마다 변경된 사항들에 대해서 설명을 해 나갈 것이다.

지금까지의 소스는 아래의 첨부를 참조하기 바란다.

 

WANI Grid_20190620.zip
0.39MB

다음에는 행을 추가하기 위해 고려해야 할 사항들에 대해서 알아 보기로 하자.

 

 

반응형
반응형

Grid 전체 모습과 기능을 갖추기 위해서 Grid의 전체 기능을 총괄할 수 있는 Grid Class를 만들어 보기로 하는데 지금까지 Grid Header를 그리기 위한 작업들을 주욱 해 왔다. 따라서 Header 영역의 컬럼을 그릴 수 있는 기능을 구현해 보기로 하자.

 

Grid에 대한 모든 책임과 권한을 가지는 Class인 Grid Class를 생성해보기로 하겠다.

Grid의 전체 기능 중 Header 영역을 설정하고 그리는 기능을 추가하기 위해서 필요한 변수 부터 먼저 설정하기로 하자.

헤더를 생성하기 위한 HeaderGenerator 를 선언하고 Header의 높이와 Header 왼쪽의 빈 공간을 설정한다.

가로 스크롤바를 이용할 경우 현재 보여져야 할 화면 영역의 첫번째 Column과 마지막 Column 정보를 저장하기 위한 변수 선언, Header Title의 폰트 선언 및 설정을 한다.

private HeaderGenerator headerGen;
private int topHeaderHeight = 20;   //Grid의 Header 높이
private int leftHeaderWidth = 22; //Grid의 맨 왼쪽 빈Column Width
private int firstVisibleCol = 0;    //화면상에서 처음 보여져야할 컬럼
private int lastVisibleCol = 0;     //화면상에서 마지막에보여질 컬럼    
private Font headerFont = new Font("맑은 고딕", 9, FontStyle.Bold);
private SolidBrush blackBrush = new SolidBrush(Color.Black);

 

각 변수들을 Class 외부에서 get/set 할 수 있도록 Property 설정을 추가한다.

 

Header를 그리기 위한 DrawHeader 메소드를 만든다.

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

 

Header 영역을 그리기 위한 Grid Class의 정의는 이것으로 마무리하고, WANIGrid Control에 대해서 알아보자.

WANIGrid Control은 디자인 모드에서 개발자가 직접 가져다 사용할 수 있는 Control 이며, 실제 Grid의 제어 영역이기도 하다.

WANIGrid Control이 가장 Core이며 핵심이 되는 부분이다. 앞으로도 이 Control에 많은 기능들이 추가 될 것이다.

현재 WANIControl에서 Header영역을 그리기 위해 필요한 주요 기능들을 나열해 보자.

  • GridType에 따른 Grid 정보에 대한 Get/Set Method - 나타내고자 하는 Grid Type 설정
  • Grid의 왼쪽 빈 공간에 대한 폭 설정 - Grid 첫번째 컬럼의 폭을 기본으로 22로 설정을 했으나, 언제든 목적에 따라 폭을 변경할 수 있도록 하기 위해서 임.
  • 가로/세로 스크롤바 위치 선정을 위한 초기화 - Control이 생성 또는 변경 되는 사이즈에 따라 가로/세로 스크롤바의 위치를 초기화
  • Grid Header를 생성하기 위한 SetHeader Method - WANIControl 외부에서 Header 정보를 만들어서 설정할 수 있도록 지원
  • 현재 보여지는 Grid 영역의 계산을 담당하는 Method - 현재 보여지는 첫 컬럼/로우, 마지막 컬럼/로우를 계산
  • WANIGrid Control의 Backgroud를 그리는 DrawBackground Method - WANIGrid Control의 기본 Background 화면을 생성
  • Grid Header를 그리기 위한 DrawHeaders Method - GridType에 맞는 Grid 모습을 그린다
  • Control Event 처리 - Load/Resize/Paint/SizeChanged/ClientSizeChanged - WANIGrid Control의 변경 Event에 따른 Grid 모습을 다시 그릴 수 있도록 처리하기 위해서 이벤트 처리가 필요

생각보다 많은 기능들을 처리해야 하는 부분이라 현재는 소스가 이렇게 구성되지만 향 후에 Refactoring을 통한 많은 개선과 변화가 있을 수 있는 부분이기도 하다.

WANIGrid Control의 소스에 대한 대략적인 내용들을 살펴 보았다. 상기의 내용을 기반으로 소스를 읽어보면 한결 이해하기가 쉬울 것이다.

지금은 단순히 지정된 Grid Header와 제목만 보여주는 기능만을 가지고 있지만 앞으로 계속 진행하면서 주요 기능들을 하나씩 추가해 나갈 것이다.

좋은 아이디어나 개선사항이 있으면 언제든 주저하지 말고 댓글을 남겨주시길 바라며....

[그림 6-1] WANIGrid Control 실행 모습

간략하게 설명한 내용의 대한 소스코드는 아래의 파일을 참조하자.

WANI Grid_20190616.zip
0.28MB

 

 

 

반응형

+ Recent posts