DataGridView of multidimensional header

Keywords: C#

background

For the DataGridView control originally provided by. NET, there is no pressure to make a table in the following form.

But if you change the form, it becomes the following form

 

The traditional DataGridView can't do it. If you expand it, it's OK. Many netizens have also extended the DataGridView control, but some can only make two-dimensional headers. Or use a third-party control. I have also used the BoundGridView of devaexpress before. However, in the absence of available third-party controls, it is a little troublesome to achieve the following effects.

 

It has to be extended by itself, but in the end, a report control of control library, Telerik's Reporting, was used. However, I have extended the DataGridView to make the above report.

 

prepare

I learned the code of some netizens. It turns out that the multidimensional header is redrawn by GDI + on the header of DataGirdView.

The methods used include

Graphics.FillRectangle / / fill a rectangle

Graphics.DrawLine / / draw a line

Graphics.DrawString   // Write string

 

In addition, in order to facilitate the organization of headers, I also defined a header data structure HeaderItem and HeaderCollection as the data entity of each header cell and the collection of the whole header.

HeaderItem is defined as follows

public class HeaderItem
    {
        private int _startX;//Starting abscissa
        private int _startY;//Start ordinate
        private int _endX; //Termination abscissa
        private int _endY; //End ordinate
        private bool _baseHeader; //Basic header

        public HeaderItem(int startX, int endX, int startY, int endY, string content)
        {
            this._endX = endX;
            this._endY = endY;
            this._startX = startX;
            this._startY = startY;
            this.Content = content;
        }

        public HeaderItem(int x, int y, string content):this(x,x,y,y,content)
        {

        }

        public HeaderItem()
        {

        }

        public static HeaderItem CreateBaseHeader(int x,int y,string content)
        {
            HeaderItem header = new HeaderItem();
            header._endX= header._startX = x;
            header._endY= header._startY = y;
            header._baseHeader = true;
            header.Content = content;
            return header;
        }

        public int StartX
        {
            get { return _startX; }
            set
            {
                if (value > _endX)
                {
                    _startX = _endX;
                    return;
                }
                if (value < 0) _startX = 0;
                else _startX = value;
            }
        }

        public int StartY
        {
            get { return _startY; }
            set
            {
                if (_baseHeader)
                {
                    _startY = 0;
                    return;
                }
                if (value > _endY)
                {
                    _startY = _endY;
                    return;
                }
                if (value < 0) _startY = 0;
                else _startY = value;
            }
        }

        public int EndX
        {
            get { return _endX; }
            set
            {
                if (_baseHeader)
                {
                    _endX = _startX;
                    return;
                }
                if (value < _startX)
                {
                    _endX = _startX;
                    return;
                }
                _endX = value;
            }
        }

        public int EndY
        {
            get { return _endY; }
            set
            {
                if (value < _startY)
                {
                    _endY = _startY;
                    return;
                }
                _endY = value;
            }
        }

        public bool IsBaseHeader
        {get{ return _baseHeader;} }

        public string Content { get; set; }
    }

The design idea is to use the mathematical rectangular coordinate system to locate and delimit the size of each header cell. Different from the coordinate positioning displayed by the computer, the origin here is placed in the lower left corner as mathematically. The positive direction of X axis is horizontal to the right and the positive direction of Y axis is vertical to the up. As shown in the figure below

 

The reason for special processing of the original column header in GridView is that both the start and end coordinates can be set here, while the start ordinate (StartY) of the original column header can only be 0, and the end abscissa (EndX) must be equal to the start abscissa (StartY).

 

In addition, the HeaderCollection of the collection of all column header cells is defined as follows

public class HeaderCollection
    {
        private List<HeaderItem> _headerList;
        private bool _iniLock;

        public DataGridViewColumnCollection BindCollection{get;set;}

        public HeaderCollection(DataGridViewColumnCollection cols)
        {
            _headerList = new List<HeaderItem>();
            BindCollection=cols;
            _iniLock = false;
        }

        public int GetHeaderLevels()
        {
            int max = 0;
            foreach (HeaderItem item in _headerList)
                if (item.EndY > max)
                    max = item.EndY;

            return max;
        }

        public List<HeaderItem> GetBaseHeaders()
        {
            List<HeaderItem> list = new List<HeaderItem>();
            foreach (HeaderItem item in _headerList)
                if (item.IsBaseHeader) list.Add(item);
            return list;
        }

        public HeaderItem GetHeaderByLocation(int x, int y) //First X Coordinate traversal, and then Y Coordinate traversal. Find an instance of the header cell containing the input coordinates
        {
            if (!_iniLock) InitHeader();
            HeaderItem result=null;
            List<HeaderItem> temp = new List<HeaderItem>();
            foreach (HeaderItem item in _headerList)
                if (item.StartX <= x && item.EndX >= x)
                    temp.Add(item);
            foreach (HeaderItem item in temp)
                if (item.StartY <= y && item.EndY >= y)
                    result = item;

            return result;
        }

        public IEnumerator GetHeaderEnumer()
        {
            return _headerList.GetEnumerator();
        }

        public void AddHeader(HeaderItem header)
        {
            this._headerList.Add(header);
        }

        public void AddHeader(int startX, int endX, int startY, int endY, string content)
        {
            this._headerList.Add(new HeaderItem(startX,endX,startY,endY,content));
        }

        public void AddHeader(int x, int y, string content)
        {
            this._headerList.Add(new HeaderItem(x, y, content));
        }

        public void RemoveHeader(HeaderItem header)
        {
            this._headerList.Remove(header);
        }

        public void RemoveHeader(int x, int y)
        {
           HeaderItem header= GetHeaderByLocation(x, y);
           if (header != null) RemoveHeader(header);
        }

        private void InitHeader()
        {
            _iniLock = true;
            for (int i = 0; i < this.BindCollection.Count; i++)
                if(this.GetHeaderByLocation(i,0)==null)
                this._headerList.Add(HeaderItem.CreateBaseHeader(i,0 , this.BindCollection[i].HeaderText));
            _iniLock = false;
        }
    }

The Add method and Remove method are defined following the Collection of. NET Frameword. In addition, the GetHeaderByLocation method is described. This method can obtain the HeaderItem of that coordinate through the given coordinate. This coordinate is the case where the entire header merged cells are ignored, for example

 

In the above figure, if you enter 0, 0 returns a gray area, and if you enter 2, 1 or 3, 2 or 5, 1 returns an orange area.

 

Extended control

To really extend the control, the most important thing is to override the OnCellPainting method, which is actually bound to the event triggered when the table cell is redrawn. You can know which cell is currently redrawn through the ColumnIndex and RowIndex properties of the DataGridViewCellPaintingEventArgs parameter, Therefore, the header cell information to be drawn is obtained through the HeaderCollection for redrawing, and the redrawn cells will be marked to prevent repeated drawing.

protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
        {
            if (e.ColumnIndex == -1 || e.RowIndex != -1)
            {
                base.OnCellPainting(e);
                return;
            }
            int lev=this.Headers.GetHeaderLevels();
            this.ColumnHeadersHeight = (lev + 1) * _baseColumnHeadHeight;
            for (int i = 0; i <= lev; i++) //After reaching a column, traverse each row to find the header that has not been drawn for drawing
            {
                HeaderItem tempHeader= this.Headers.GetHeaderByLocation(e.ColumnIndex, i);
                if (tempHeader==null|| i != tempHeader.EndY || e.ColumnIndex != tempHeader.StartX) continue;
                DrawHeader(tempHeader, e);
            }
            e.Handled = true;
        }

In the above code, first judge whether the current cell to be redrawn is the header part. If not, call the original OnCellPainting method. e.Handled=true; More critical, with this code, redrawing can take effect.

The process of drawing cells is encapsulated in the method DrawHeader

private void DrawHeader(HeaderItem item,DataGridViewCellPaintingEventArgs e)
        {
            if (this.ColumnHeadersHeightSizeMode != DataGridViewColumnHeadersHeightSizeMode.DisableResizing)
                this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
            int lev=this.Headers.GetHeaderLevels();  //Get the total number of rows in the whole header
            lev=(lev-item.EndY)*_baseColumnHeadHeight;   //Reset the row height of the header

            SolidBrush backgroundBrush = new SolidBrush(e.CellStyle.BackColor);
            SolidBrush lineBrush = new SolidBrush(this.GridColor);
            Pen linePen = new Pen(lineBrush);
            StringFormat foramt = new StringFormat();
            foramt.Alignment = StringAlignment.Center;
            foramt.LineAlignment = StringAlignment.Center;

            Rectangle headRec = new Rectangle(e.CellBounds.Left, lev, ComputeWidth(item.StartX, item.EndX)-1, ComputeHeight(item.StartY, item.EndY)-1);
            e.Graphics.FillRectangle(backgroundBrush, headRec);  //Fill rectangle
            e.Graphics.DrawLine(linePen, headRec.Left, headRec.Bottom, headRec.Right, headRec.Bottom); //Draw the bottom line of the cell
            e.Graphics.DrawLine(linePen, headRec.Right, headRec.Top, headRec.Right, headRec.Bottom);  //Draw the right line of the cell
            e.Graphics.DrawString(item.Content, this.ColumnHeadersDefaultCellStyle.Font, Brushes.Black,headRec, foramt);  //Fill in header title
        }

When filling a rectangle, remember to subtract a pixel from the constant sum width of the rectangle, so that it will not overlap with the adjacent rectangle, resulting in the edge line of the rectangle not being displayed. In addition, the ColumnHeadersHeightSizeMode property should be set here. If it is not set to DisableResizing, the height of the header cannot be changed. In this way, even if two-dimensional, three-dimensional and n-dimensional are set, it is only one-dimensional in the end.

 

Some auxiliary methods used here are as follows: calculate the height and width through coordinates.

private int ComputeWidth(int startX, int endX)
        {
            int width = 0;
            for (int i = startX; i <= endX; i++)
                width+= this.Columns[i].Width;
            return width;
        }

        private int ComputeHeight(int startY, int endY)
        {
            return _baseColumnHeadHeight * (endY - startY+1);
        }

For an example code to be used, the bound fields should be set for each column of DataGridView in advance, otherwise the automatically added columns will not work.

HeaderItem item= this.boundGridView1.Headers.GetHeaderByLocation(0, 0);  //Get include coordinates(0,0)Cell for
            item.EndY = 2;
            item = this.boundGridView1.Headers.GetHeaderByLocation(9,0 );
            item.EndY = 2;
            item = this.boundGridView1.Headers.GetHeaderByLocation(10, 0);
            item.EndY = 2;
            item = this.boundGridView1.Headers.GetHeaderByLocation(11, 0);
            item.EndY = 2;

            this.boundGridView1.Headers.AddHeader(1, 2, 1, 1, "language"); //Add header and start coordinate(1,1) ,Termination coordinates(2,1) content"language"
            this.boundGridView1.Headers.AddHeader(3, 4, 1, 1, "mathematics");  //Add header and start coordinate(3,1) ,Termination coordinates(4,1) content"mathematics"
            this.boundGridView1.Headers.AddHeader(5, 6, 1, 1, "English");  //Add header and start coordinate(5,1) ,Termination coordinates(6,1) content"English"
            this.boundGridView1.Headers.AddHeader(7, 8, 1, 1, "X section");  //Add header and start coordinate(7,1) ,Termination coordinates(8,1) content"X section"
            this.boundGridView1.Headers.AddHeader(1, 8, 2, 2, "achievement");  //Add header and start coordinate(1,2) ,Termination coordinates(8,2) content"achievement"

The renderings are shown below

 

Generally speaking, I feel a little fussy, but I can't think of any better way. If you think there is anything bad in the above, you are welcome to clap bricks; If you find anything wrong in the above, please criticize and correct it; If you feel good, please support it. thank you! Finally, the source code of the whole control is attached

 

     public class BoundGridView : DataGridView
     {
         private int _baseColumnHeadHeight;

         public BoundGridView():base()
         {
             this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
             _baseColumnHeadHeight = this.ColumnHeadersHeight;
             this.Headers = new HeaderCollection(this.Columns);
         }

         public HeaderCollection Headers{ get;private set; }

         protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
         {
             if (e.ColumnIndex == -1 || e.RowIndex != -1)
             {
                 base.OnCellPainting(e);
                 return;
             }
             int lev=this.Headers.GetHeaderLevels();
             this.ColumnHeadersHeight = (lev + 1) * _baseColumnHeadHeight;
             for (int i = 0; i <= lev; i++)
             {
                 HeaderItem tempHeader= this.Headers.GetHeaderByLocation(e.ColumnIndex, i);
                 if (tempHeader==null|| i != tempHeader.EndY || e.ColumnIndex != tempHeader.StartX) continue;
                 DrawHeader(tempHeader, e);
             }
             e.Handled = true;
         }

         private int ComputeWidth(int startX, int endX)
         {
             int width = 0;
             for (int i = startX; i <= endX; i++)
                 width+= this.Columns[i].Width;
             return width;
         }

         private int ComputeHeight(int startY, int endY)
         {
             return _baseColumnHeadHeight * (endY - startY+1);
         }

         private void DrawHeader(HeaderItem item,DataGridViewCellPaintingEventArgs e)
         {
             if (this.ColumnHeadersHeightSizeMode != DataGridViewColumnHeadersHeightSizeMode.DisableResizing)
                 this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
             int lev=this.Headers.GetHeaderLevels();
             lev=(lev-item.EndY)*_baseColumnHeadHeight;

             SolidBrush backgroundBrush = new SolidBrush(e.CellStyle.BackColor);
             SolidBrush lineBrush = new SolidBrush(this.GridColor);
             Pen linePen = new Pen(lineBrush);
             StringFormat foramt = new StringFormat();
             foramt.Alignment = StringAlignment.Center;
             foramt.LineAlignment = StringAlignment.Center;

             Rectangle headRec = new Rectangle(e.CellBounds.Left, lev, ComputeWidth(item.StartX, item.EndX)-1, ComputeHeight(item.StartY, item.EndY)-1);
             e.Graphics.FillRectangle(backgroundBrush, headRec);
             e.Graphics.DrawLine(linePen, headRec.Left, headRec.Bottom, headRec.Right, headRec.Bottom);
             e.Graphics.DrawLine(linePen, headRec.Right, headRec.Top, headRec.Right, headRec.Bottom);
             e.Graphics.DrawString(item.Content, this.ColumnHeadersDefaultCellStyle.Font, Brushes.Black,headRec, foramt);
         }
     }

     public class HeaderItem
     {
         private int _startX;
         private int _startY;
         private int _endX;
         private int _endY;
         private bool _baseHeader;

         public HeaderItem(int startX, int endX, int startY, int endY, string content)
         {
             this._endX = endX;
             this._endY = endY;
             this._startX = startX;
             this._startY = startY;
             this.Content = content;
         }

         public HeaderItem(int x, int y, string content):this(x,x,y,y,content)
         { 

         }

         public HeaderItem()
         { 

         }

         public static HeaderItem CreateBaseHeader(int x,int y,string content)
         {
             HeaderItem header = new HeaderItem();
             header._endX= header._startX = x;
             header._endY= header._startY = y;
             header._baseHeader = true;
             header.Content = content;
             return header;
         }

         public int StartX
         {
             get { return _startX; }
             set 
             {
                 if (value > _endX)
                 {
                     _startX = _endX;
                     return;
                 }
                 if (value < 0) _startX = 0;
                 else _startX = value;
             }
         }

         public int StartY
         {
             get { return _startY; }
             set
             {
                 if (_baseHeader)
                 {
                     _startY = 0;
                     return;
                 }
                 if (value > _endY)
                 {
                     _startY = _endY;
                     return;
                 }
                 if (value < 0) _startY = 0;
                 else _startY = value;
             }
         }

         public int EndX
         {
             get { return _endX; }
             set 
             {
                 if (_baseHeader)
                 {
                     _endX = _startX;
                     return;
                 }
                 if (value < _startX)
                 {
                     _endX = _startX;
                     return;
                 }
                 _endX = value; 
             }
         }

         public int EndY
         {
             get { return _endY; }
             set 
             {
                 if (value < _startY)
                 {
                     _endY = _startY;
                     return;
                 }
                 _endY = value; 
             }
         }

         public bool IsBaseHeader
         {get{ return _baseHeader;} }

         public string Content { get; set; }
     }

     public class HeaderCollection
     {
         private List<HeaderItem> _headerList;
         private bool _iniLock;

         public DataGridViewColumnCollection BindCollection{get;set;}

         public HeaderCollection(DataGridViewColumnCollection cols)
         {
             _headerList = new List<HeaderItem>();
             BindCollection=cols;
             _iniLock = false;
         }

         public int GetHeaderLevels()
         {
             int max = 0;
             foreach (HeaderItem item in _headerList)
                 if (item.EndY > max)
                     max = item.EndY;

             return max;
         }

         public List<HeaderItem> GetBaseHeaders()
         {
             List<HeaderItem> list = new List<HeaderItem>();
             foreach (HeaderItem item in _headerList)
                 if (item.IsBaseHeader) list.Add(item);
             return list;
         }

         public HeaderItem GetHeaderByLocation(int x, int y)
         {
             if (!_iniLock) InitHeader();
             HeaderItem result=null;
             List<HeaderItem> temp = new List<HeaderItem>();
             foreach (HeaderItem item in _headerList)
                 if (item.StartX <= x && item.EndX >= x)
                     temp.Add(item);
             foreach (HeaderItem item in temp)
                 if (item.StartY <= y && item.EndY >= y)
                     result = item;

             return result;
         }

         public IEnumerator GetHeaderEnumer()
         {
             return _headerList.GetEnumerator();
         }

         public void AddHeader(HeaderItem header)
         {
             this._headerList.Add(header);
         }

         public void AddHeader(int startX, int endX, int startY, int endY, string content)
         {
             this._headerList.Add(new HeaderItem(startX,endX,startY,endY,content));
         }

         public void AddHeader(int x, int y, string content)
         {
             this._headerList.Add(new HeaderItem(x, y, content));
         }

         public void RemoveHeader(HeaderItem header)
         {
             this._headerList.Remove(header);
         }

         public void RemoveHeader(int x, int y)
         {
            HeaderItem header= GetHeaderByLocation(x, y);
            if (header != null) RemoveHeader(header);
         }

         private void InitHeader()
         {
             _iniLock = true;
             for (int i = 0; i < this.BindCollection.Count; i++)
                 if(this.GetHeaderByLocation(i,0)==null)
                 this._headerList.Add(HeaderItem.CreateBaseHeader(i,0 , this.BindCollection[i].HeaderText));
             _iniLock = false;
         }
     }

 

 

Turn:   https://www.cnblogs.com/HopeGi/archive/2013/04/03/2982837.html

https://www.cnblogs.com/jiasonglin/archive/2013/03/28/2986040.html

 

Posted by gsaldutti on Thu, 25 Nov 2021 19:07:51 -0800