반응형

앞에서 WANIGrid Control 내의 마우스 우측 버튼 클릭 시 나타나게 되는 Context 메뉴 부분을 마무리 하도록 하자.

먼저 WANIGrid 내의 마우스 우측 버튼 클릭 시 Context Menu를 제공할지 말지를 설정할 수 있도록 바꾸면 좋을 듯 싶다. 마우스 우측 버튼 클릭 시 Context Menu가 나타나지 않도록 설정을 부여해야만 할 경우도 있을 수 있기 때문이다. 예를 들어 데이터를 WANIGrid Control에 조회만 할 수 있도록 하고, 새로운 행(Row)을 추가/삭제할 수 없도록 해야 할 경우도 있기 때문이다.

이러한 설정을 할 수 있도록 WANIGrid.cs파일의 변수 설정 부분에 아래의 코드를 추가하도록 한다.

ContextMenu를 보여줄지 말지를 설정할 수 있는 변수인 bool타입의 contextMenuShow

    private bool isShowContextMenu = true;    //ContextMenu 제공 여부

 

Property 설정 부분에 아래의 Code를 추가한다. 이렇게 WANIGrid Control 내에서 Mouse 우측 버튼 클릭 시 Context Menu 제공 여부를 설정할 수 있는 변수와 Property를 작성했다.

    ///<summary> 
    /// 마우스 우측 버튼 클릭 시 ContextMenu 제공 여부
    ///</summary> 
    public bool IsShowContextMenu
    {
        get { return isShowContextMenu; }
        set { isShowContextMenu = value; }
    }

 

Mouse Down 이벤트 메소드에서 Context Menu를 보여주는 로직을 처리하다 보니 소스 라인이 너무 길어지면서 가독성이 떨어지는 부분이 있어서 Context Menu를 보여주는 부분을 별도의 메소드로 분리를 하자. 

선택된 행(Row)의 수가 없거나 1개일 경우, 1개 이상일 경우에 따라 사용할 수 있는 Contex Menu의 활성화/비활성화 부분을 추가한다.

  • 선택된 행(Row)의 갯수가 0보다 작거나 같은 경우에는 "행 추가" 메뉴만 활성화
  • 선택된 행(Row)의 갯수가 1개일 경우에는 모든 메뉴를 활성화
  • 선택된 행(Row)의 갯수가 2개 이상일 경우 "행 추가", "행 삭제" 메뉴만 활성화 

    /// <summary> 
    /// Context Menu를 보여준다.
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// </summary> 
    private void ShowContextMenu(object sender, MouseEventArgs e)
    {
        if (!isShowContextMenu) return;
        //마우스 우측 버튼이 클릭된 좌표 값을 얻어온다.
        Point p = new Point(e.X, e.Y);
            
        if (selectedRows.Count <= 0)
        {
            rightClickMenu.MenuItems[0].Enabled = false;
            rightClickMenu.MenuItems[1].Enabled = false;
            rightClickMenu.MenuItems[2].Enabled = true;
            rightClickMenu.MenuItems[3].Enabled = false;
        }
        else if (selectedRows.Count == 1)
        {
            rightClickMenu.MenuItems[0].Enabled = true;
            rightClickMenu.MenuItems[1].Enabled = true;
            rightClickMenu.MenuItems[2].Enabled = true;
            rightClickMenu.MenuItems[3].Enabled = true;
        }
        else if (selectedRows.Count > 1)
        {
            rightClickMenu.MenuItems[0].Enabled = false;
            rightClickMenu.MenuItems[1].Enabled = false;
            rightClickMenu.MenuItems[2].Enabled = true;
            rightClickMenu.MenuItems[3].Enabled = true;
        }

        rightClickMenu.Show(this, p);
    }

 

[그림 17-1] 행(Row)이 없을 때의 Context Menu
[그림 17-2] 1개의 행(Row)를 선택했을 때 Context Menu
[그림 17-3] 2개 이상의 행(Row)를 선택했을 때 Context Menu

 

Mouse Down 이벤트 발생 시 호출되는 WANIGrid_MouseDown 이벤트를 아래와 같이 변경한다.

    /// <summary> 
    /// Mouse Down Event
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// </summary> 
    private void WANIGrid_MouseDown(object sender, MouseEventArgs e)
    {
        //마우스 우측 버튼 클릭 시 Context 메뉴 제공
        if (e.Button == MouseButtons.Right)
        {
            //WANIGrid Header 영역이 선택되어졌을 경우에는 메뉴 제공하지 않음.
            if (e.Y < grid.TopHeaderHeight) return;

            ShowContextMenu(sender, e);                
        }
        else if (e.Button == MouseButtons.Left)
        {

          :

          :

        }

    }

 

앞서 작성했던 Context Menu에 실제 클릭 이벤트 부분을 추가해 보도록 하자.

기존에 Context 메뉴는 만들었으나, 실제 동작하는 메뉴는 행 추가 메뉴 뿐이었다.

나머지 3개 메뉴에 대한 동작들도 같이 완성시켜 보도록 하겠다.

이전 행에 추가(OnMenu_BeforeInsertRow_Click), 다음 행에 추가(OnMenu_AfterInsertRow_Click), 행 삭제(OnMenu_DeleteRow_Click) 메뉴에 대한 이벤트 등록을 추가했다.

    /// <summary> 
    /// 마우스 우측 버튼 클릭시 제공 되는 메뉴 초기화
    /// </summary> 
    private void InitializeContextMenu()
    {
        //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);

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

 

먼저 행 추가 메뉴를 클릭했을 때 호출되는 AppendRow() 메소드를 아래와 같이 보완한다.

    /// <summary> 
    /// Row 추가
    /// </summary> 
    public void AppendRow()
    {
        DataRow row = dataSource.NewRow();
        Row r = new Row(row);
        rows.Add(r);
        dataSource.Rows.Add(row);            
        rowHeight = Font.Height + 4;
        allRowsHeight += rowHeight;            
        OnRowsChanged();
    }

 

Context Menu 중 행 삭제를 선택했을 때 호출되는 이벤트를 살펴보자.

선택한 행의 갯수 만큼 Looping 돌면서 DeleteRow(int delRow) 메소드 호출과 함께 삭제된 행의 Index정보를 재설정하는 RebuildSelectedRowsIndex 메소드를 호출한다.

선택된 행을 모두 삭제하고 나면 selectedRows 리스트를 Clear한다.

    /// <summary>
    /// 선택한 행(Row) 삭제
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// </summary>
    protected void OnMenu_DeleteRow_Click(object sender, EventArgs e)
    {
        if (selectedRows.Count > 0)
        {
            for (int i = 0; i < selectedRows.Count; i++)
            {
                int crntRow = selectedRows[i];
                DeleteRow(crntRow);
                RebuildSelectedRowsIndex(crntRow);
            }
            selectedRows.Clear();
        }
    }

 

행 삭제 메뉴 클릭 시 선택된 행(Row) 번호를 입력받아 동작하게 되는 DeleteRow(int delRow)는 아래와 같다.

    /// <summary>
    /// Row 삭제
    /// <param name="delRow"></param>
    /// </summary>
    public void DeleteRow(int delRow)
    {
        if (delRow < rows.Count && delRow >= 0)
        {
            DataRow row = rows[delRow].DataRow; //전체 Row중에 선택 Row를 가져온다.
            row.Delete(); //해당 Row 정보를 delete한다.                
            rows.RemoveAt(delRow);  //전체 Row중에 선택된 Row를 제거한다.               
            allRowsHeight -= rowHeight; //전제 Row의 높이에서 선택된 Row의 높이를 뺀다
            OnRowsChanged();
        }
    }

 

행 삭제 후 삭제해야 할 행 목록에서 행의 Index 정보를 재설정하는 메소드인 RebuildSelectedRowsIndex를 살펴보자.

이 메소드가 처리하는 것은 선택된 행(Row)의 목록에서 현재 선택된 행 이후의 행(Row)의 Index값을 1씩 빼서 재설정하는 것이다.

    /// <summary>
    /// 1개의 Row 삭제 후 선택한 행들의 Index 정보를 갱신한다.
    /// <param name="index"></param>
    /// </summary>
    private void RebuildSelectedRowsIndex(int index)
    {
        for (int i = index; i < selectedRows.Count; i++)
        {
            int reIndex = selectedRows[i] - 1; 
            selectedRows[i] = reIndex;
        }
    }

 

이전 행에 추가 메뉴 선택 시 수행되는 이벤트인 OnMenu_BeforeInsertRow_Click 이벤트는 사용자가 마우스 좌측 버튼을 클릭해서 Activie된 셀(Cell)에서 행(Row) 정보를 가져와서 해당 행 이전의 위치에 행(Row)을 추가 한다.

이전 행의 추가는 현재 행(Row)에 행(Row)을 추가하면 이전 행 추가가 된다.

    /// <summary>
    /// 선택 Row 앞에 행(Row)을 추가
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// </summary>
    protected void OnMenu_BeforeInsertRow_Click(object sender, EventArgs e)
    {
        int crntRow = ActiveCell.Row;
        BeforeInsert(crntRow);
    }

 

    /// <summary>
    /// 이전 행에 Row 추가
    /// <param name="crntRow"></param>
    /// </summary>
    public void BeforeInsert(int crntRow)
    {
        DataRow row = dataSource.NewRow(); //DataTable인 dataSource에 새로운 행의 DataRow 생성
        Row r = new Row(row); //새로운 Row를 생성
            
        if (crntRow < 0) crntRow = 0; //crntRow의 값이 0보다 작으면 0으로 설정
        rows.Insert(crntRow, r);   //전제 Row를 관리하는 List인 rows에 생성된 Row를 추가 
        dataSource.Rows.InsertAt(row, crntRow); //생성된 DataRow를 DataTable에 원하는 위치에 Insert
        allRowsHeight += Font.Height + 4;   //전체 Row의 높이 값을 저장하는 allRowsHeight에 추가되는 Row의 높이 값을 더한다.
        rowHeight = Font.Height + 4; //1개 Row의 높이 값을 rowHeight 변수에 저장
        OnRowsChanged();
    }

 

다음 행에 추가 메뉴 선택 시 수행되는 이벤트인 OnMenu_AfterInsertRow_Click 이벤트는 사용자가 마우스 좌측 버튼을 클릭해서 Active된 셀(Cell)에서 행(Row) 정보를 가져와서 해당 행 다음의 위치에 행(Row)을 추가한다.

    /// <summary>
    /// 선택 Row 뒤에 행(Row)을 추가
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// </summary>
    protected void OnMenu_AfterInsertRow_Click(object sender, EventArgs e)
    {
        int crntRow = ActiveCell.Row;
        AfterInsert(crntRow);
    }

 

    /// <summary>
    /// 다음 행에 Row 추가
    /// <param name="crntRow"></param>
    /// </summary>
    public void AfterInsert(int crntRow)
    {            
        if (crntRow + 1 == rows.Count) //현재 행(Row)가 마지막 행(Row)일 경우
        {
            AppendRow();
        }
        else
        {
            DataRow row = dataSource.NewRow();  //DataTable인 dataSource에 새로운 행의 DataRow 생성
            Row r = new Row(row);    //새로운 Row를 생성
            rows.Insert(crntRow + 1, r);    //핸재 행의 다음행에 추가
            dataSource.Rows.InsertAt(row, crntRow + 1);     //생성된 DataRow를 DataTable의 현재행 다음에 Insert
            allRowsHeight += Font.Height + 4;   //전체 Row의 높이 값을 저장하는 allRowsHeight에 추가되는 Row의 높이 값을 더한다.
            rowHeight = Font.Height + 4;    //1개 Row의 높이 값을 rowHeight 변수에 저장
            OnRowsChanged();
        }
    }

 

이렇게 해서 이전에 생성했던 Context Menu의 모든 기능을 구현했다. 구현한 메뉴를 테스트 하다보면 오류나 보완해야 할 부분들이 발생하게 된다.

이러한 작업들을 추가한 부분들도 같이 살펴 보기로 하자.

 

선택한 셀(Cell)에서 값이 변경되었을 때 호출되는 이벤트 부분에 전체 행(Row) 목록 변수인 rows의 Count가 0일 경우 값을 보여주기 위한 로직을 타지 않도록 처리해야 한다.

    /// <summary>
    /// TextBox인 editBox의 TextChanged Event
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// </summary>
    private void EditBox_TextChanged(object sender, EventArgs e)
    {
        if (rows.Count < 1)
        {
            editBox.Visible = false;
            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;
        }
    }

 

마우스 좌측 버튼을 눌러서 특정 셀(Cell)을 선택했을 때 파란색 테두리로 보여주던 부분을 회색의 점선으로 테두리를 그리도록 변경했다. 생뚱맞은 파란색 테두리 보다는 회선 점선이 일반적이다는 의견을 반영한 부분이다.

    /// <summary>
    /// Active Cell을 표기한다.
    /// <param name="g"></param>
    /// </summary>
    private void DrawActiveCell(Graphics g)
    {
        if (ActiveCell.Row != -1 && ActiveCell.Col != -1)
        {
            //파선 패턴 설정
            float[] dashValues = { 1, 1, 1, 1 };
            Pen grayPen = new Pen(Color.Gray, 1);
            grayPen.DashPattern = dashValues;                
            g.DrawRectangle(grayPen, GetSelectedCellRect(ActiveCell.Row, ActiveCell.Col));                                
        }
    }

 

[그림 17-4] 선택된 셀(Cell)의 테두리 라인 변경

 

선택한 셀(Cell)의 영역을 반환하는 메소드인 GetSelectedCellRect 메소드에서 전체 행(Row) 목록을 관리하는 rows의 Count가 0일 경우에 EndEdit메소드를 호출하고 하위 로직을 타지 않도록 보완했다.

    /// <summary>
    /// 선택한 Cell의 영역을 반환
    /// <param name="row"></param>
    /// <param name="col"></param>
    /// <returns></returns>
    /// </summary>
    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++)
        {
            if (rows.Count == 0)
            {
                EndEdit();
                continue;
            }
            height = rows[i].MaxLines * rowHeight;
            if (row == i) break;
            top += height;
        }

       :

       :

    }

 

기존 코드에서 여러 번 수행되어야 하는 영역을 새로운 메소드로 분리해서 호출해서 사용하도록 변경해보자.

마우스 좌측 버튼 클릭 시 행(Row) 선택 영역의 색을 변경하는 부분의 소스를 SelectedRowsChangeColor라는 변수로 분리해 내었다.

    private void SelectedRowsChangeColor(int 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)를 추가
            }
        }
        //Row를 선택하면 EditBox를 감춘다.
        editBox.Visible = false;
    }

 

이렇게 분리된 메소드 SelectedRowsChangeColor를 호출하는 메소드인 MouseLeftButtonClickInContents 이다.

이 부분에 있는 선택된 영역의 색상 변경을 하기 위해 처리하는 영역 중 중복된 부분을 SelectedRowsChangeColor라는 메소드로 뽑아 낸 것이다.

이렇게 함으로써 이 메소드의 소스 길이도 상당히 짧아지는 효과를 볼 수 있게 되었다.

    /// <summary>
    /// WANIGrid Control의 맨 왼쪽 컬럼에서 마우스 좌측 버튼을 눌렀을 경우
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// </summary>
    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를 선택하도록 처리
        {
            SelectedRowsChangeColor(row);

        }

        else //WANIGrid Control 내부의 Content 영역을 마우스 좌측 버튼으로 클릭했을 때

       {

            selectedRows.Clear();
            selectedCols.Clear();

            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;
                }
                if (ActiveCell_ActiveRow != k) EndEdit();
                ActiveCell_ActiveRow = k;
                BeginEdit();    
            }
            else
            {
                ActiveCell.Row = row;
                ActiveCell.Col = col;
                EndEdit();
                SelectedRowsChangeColor(ActiveCell.Row);
            }

       }

    }   

 

다음 강좌에서는 점점 크기가 커지고 있는 WANIGrid.cs 파일을 보다 효율적으로 관리하기 위한 방법에 대해서 공유하고자 한다.

기능이 추가되면서 점점 소스코드가 길어지는 문제가 발생하고 있는데...

이 부분에 대해서 보다 효과적으로 대처하기 위한 아이디어와 방법을 찾아보기로 하자.

 

지금까지의 소스는 아래를 참조하자.

WANI Grid_20190825.zip
0.39MB

 

반응형

+ Recent posts