需求
1.可以自定义行数和列数。
2.可以自定义窗体占用的行数和列数。
3.可以保存用户自定义的布局。
思路
通过继承QWidget,在paintEvent事件中,根据行列数绘制 网格 单元格(cell)及区域(LOCATION)。
在mousePressEvent中获取鼠标起始点,通过计算获取起始点所在的单元格。
在mouseMoveEvent中实时获取鼠标点,通过计算获取当前点所在的单元格(cell),根据起始点所在单元格的行列数和当前点所在的单元格行列数,计算出所有跨越包含的单元格(cell)(就像是绘制矩形,有一个起始点和终点就可以得出矩形的位置和长宽。)并添加到列表中。如果其中包含的单元格在已经选定的区域(LOCATION:包含大于等于1个单元格)中,就清空当前的单元格列表。绘制效果。
在mouseRealeaseEvent中获取鼠标点,通过计算获取鼠标释放时鼠标点所在的单元格。如果此单元格在其他已经存在的区域中,则没有意义,不进行保存区域操作;如果单元格不在已经存在的区域中,且该区域内的所有单元格都不在其他已经存在的区域内,就保存当前的区域,将此区域加入区域列表中。绘制效果。
LOCATION中包含起始行和起始列,以及跨越的行数,跨域的列数。
在类中有row和column用于记录当前的总行数和总列数。并可以通过函数修改这两个值,通过更新重新绘制网格。
效果
代码
class LayoutBox:public QWidget{
Q_OBJECT
public:
struct LOCATION{
LOCATION(int _row,int _column,int _rowSpan,int _columnSpan){
row=_row;column=_column;rowSpan=_rowSpan;columnSpan=_columnSpan;
}
int row=-1;
int column=-1;
int rowSpan=-1;
int columnSpan=-1;
int appID=-1;
};
enum MODE{
THREE_ONE_THREE, //3+1+3
THREE_ONE_THREE_ONE, //3+1+3+1
TWO_ONE_TWO, //2+1+2
TWO_ONE_TWO_ONE, //2+1+2+1
THREE_ONE, //3+1
};
public:
explicit LayoutBox(QWidget* parent=nullptr);
~LayoutBox();
void clear();
void setRow(int row);
void setColumn(int column);
void setDefault();
void setMode(MODE mode);
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event)override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
private:
bool isInLoationList(LOCATION &cell);
LOCATION getCell(QPoint pos);
QList<QRectF> getCellRects(int startRow,int startColumn,int endRow,int endColumn);
QRectF getCellRect(LOCATION &cell);
QRectF getLocationRect(LOCATION &location);
int getLocation(LOCATION &cell);
void setLocationAppID(int index,int appID);
signals:
void locationClickedSignal(int);
private:
int _row=6; //行数
int _column=8; //列数
double _cellWidth=0; //单元格宽度
double _cellHeight=0; //单元格高度
int _midLine=1; //分界线宽度
QList<LOCATION> _locationList; //位置列表
LOCATION _startCell=LOCATION(-1,-1,1,1);
bool _isStart=false; //是否开始
QList<QRectF> _currentRects; //当前选中的布局
};
LayoutBox::LayoutBox(QWidget* parent):QWidget(parent){}
LayoutBox::~LayoutBox(){}
void LayoutBox::clear(){
_locationList.clear();
_currentRects.clear();
_isStart=false;
update();
}
void LayoutBox::setRow(int row){
if(row<=0){
_row=1;
}else if(row>50){
_row=50;
}else{
_row=row;
}
_cellWidth=(width()-_midLine*(_column+1))/(double)_column;
_cellHeight=(height()-_midLine*(_row+1))/(double)_row;
clear();
}
void LayoutBox::setColumn(int column){
if(column<=0){
_column=1;
}else if(column>50){
_column=50;
}else{
_column=column;
}
_cellWidth=(width()-_midLine*(_column+1))/(double)_column;
_cellHeight=(height()-_midLine*(_row+1))/(double)_row;
clear();
}
void LayoutBox::setDefault(){
_column=8;
_row=6;
_cellWidth=(width()-_midLine*(_column+1))/(double)_column;
_cellHeight=(height()-_midLine*(_row+1))/(double)_row;
clear();
}
void LayoutBox::setMode(MODE mode){
setDefault();
if(mode==THREE_ONE_THREE){
_locationList<<LOCATION(0,0,2,2)<<LOCATION(2,0,2,2)<<LOCATION(4,0,2,2)<<LOCATION(0,2,6,4)<<LOCATION(0,6,2,2)<<LOCATION(2,6,2,2)<<LOCATION(4,6,2,2);
}else if(mode==THREE_ONE_THREE_ONE){
_locationList<<LOCATION(0,0,2,2)<<LOCATION(2,0,2,2)<<LOCATION(4,0,2,2)<<LOCATION(0,2,5,4)<<LOCATION(5,2,1,4)<<LOCATION(0,6,2,2)<<LOCATION(2,6,2,2)<<LOCATION(4,6,2,2);
}else if(mode==TWO_ONE_TWO){
_locationList<<LOCATION(0,0,3,2)<<LOCATION(3,0,3,2)<<LOCATION(0,2,6,4)<<LOCATION(0,6,3,2)<<LOCATION(3,6,3,2);
}else if(mode==TWO_ONE_TWO_ONE){
_locationList<<LOCATION(0,0,3,2)<<LOCATION(3,0,3,2)<<LOCATION(0,2,4,4)<<LOCATION(4,2,2,4)<<LOCATION(0,6,3,2)<<LOCATION(3,6,3,2);
}else if(mode==THREE_ONE){
_locationList<<LOCATION(0,0,2,2)<<LOCATION(2,0,2,2)<<LOCATION(4,0,2,2)<<LOCATION(0,2,6,6);
}
update();
}
void LayoutBox::resizeEvent(QResizeEvent *event){
Q_UNUSED(event)
_cellWidth=(width()-_midLine*(_column+1))/(double)_column;
_cellHeight=(height()-_midLine*(_row+1))/(double)_row;
}
void LayoutBox::paintEvent(QPaintEvent *event){
Q_UNUSED(event)
QPainter painter(this);
painter.save();
painter.setRenderHints(QPainter::Antialiasing,true);
painter.setPen(Qt::NoPen);
painter.setBrush(PAINT::yellow);
for(int i=0;i<_column+1;i++){
QRectF rect(i*(_midLine+_cellWidth),0,_midLine,height());
painter.drawRect(rect);
}
for(int i=0;i<_row+1;i++){
QRectF rect(0,i*(_midLine+_cellHeight),width(),_midLine);
painter.drawRect(rect);
}
painter.setBrush(PAINT::blue20);
for(int i=0;i<_currentRects.length();i++){
QRectF rect=_currentRects.at(i);
painter.drawRect(rect);
}
painter.setBrush(PAINT::deepblue);
for(int i=0;i<_locationList.size();i++){
LOCATION location=_locationList.at(i);
QRectF rect=getLocationRect(location);
painter.drawRect(rect);
if(location.appID>=0){
painter.setPen(Qt::white);
painter.drawText(rect,Qt::AlignCenter,QString("AppID:%1").arg(location.appID));
painter.setPen(Qt::NoPen);
}
}
painter.restore();
}
void LayoutBox::mouseDoubleClickEvent(QMouseEvent *event){
LOCATION cell=getCell(event->pos());
if(cell.row>=0&&cell.column>=0&&isInLoationList(cell)){
int locationIndex=getLocation(cell);
emit locationClickedSignal(locationIndex);
}
}
void LayoutBox::mousePressEvent(QMouseEvent *event){
LOCATION cell=getCell(event->pos());
if(cell.row>=0&&cell.column>=0){
if(!isInLoationList(cell)){
_startCell=cell;
_isStart=true;
}
}else{
_isStart=false;
}
}
void LayoutBox::mouseMoveEvent(QMouseEvent *event){
if(_isStart){
LOCATION cell=getCell(event->pos());
if(cell.row>=0&&cell.column>=0&&!isInLoationList(cell)){
_currentRects=getCellRects(_startCell.row,_startCell.column,cell.row,cell.column);
update();
}else{
_currentRects.clear();
update();
}
}else{
_currentRects.clear();
}
}
void LayoutBox::mouseReleaseEvent(QMouseEvent *event){
if(_isStart){
LOCATION cell=getCell(event->pos());
if(cell.row>=0&&cell.column>=0&&_currentRects.length()!=0){
int sRow=qMin(_startCell.row,cell.row);
int eRow=qMax(_startCell.row,cell.row);
int sColumn=qMin(_startCell.column,cell.column);
int eColumn=qMax(_startCell.column,cell.column);
LOCATION location(sRow,sColumn,eRow-sRow+1,eColumn-sColumn+1);
_locationList.append(location);
update();
}
}
_isStart=false;
}
bool LayoutBox::isInLoationList(LayoutBox::LOCATION &cell){
for(int i=0;i<_locationList.length();i++){
LOCATION location=_locationList.at(i);
if(location.row<=cell.row&&cell.row<=location.row+location.rowSpan-1&&location.column<=cell.column&&cell.column<=location.column+location.columnSpan-1){
return true;
}
}
return false;
}
LayoutBox::LOCATION LayoutBox::getCell(QPoint pos){
if(pos.x()>0&&pos.x()<width()&&pos.y()>0&&pos.y()<height()){
int column=qFloor(pos.x()/(_cellWidth+_midLine));
int row=qFloor(pos.y()/(_cellHeight+_midLine));
return LOCATION(row,column,1,1);
}else{
return LOCATION(-1,-1,1,1);
}
}
QList<QRectF> LayoutBox::getCellRects(int startRow,int startColumn,int endRow,int endColumn){
QList<QRectF> rects;
int sRow=qMin(startRow,endRow);
int eRow=qMax(startRow,endRow);
int sColumn=qMin(startColumn,endColumn);
int eColumn=qMax(startColumn,endColumn);
for(int row=sRow;row<eRow+1;row++){
for(int column=sColumn;column<eColumn+1;column++){
LOCATION cell(row,column,1,1);
if(isInLoationList(cell)){
rects.clear();
return rects;
}else{
QRectF rect=getCellRect(cell);
rects.append(rect);
}
}
}
return rects;
}
QRectF LayoutBox::getCellRect(LayoutBox::LOCATION &cell){
return QRectF(_midLine+cell.column*(_cellWidth+_midLine),_midLine+cell.row*(_cellHeight+_midLine),_cellWidth,_cellHeight);
}
QRectF LayoutBox::getLocationRect(LayoutBox::LOCATION &location){
double sx=_midLine+location.column*(_midLine+_cellWidth);
double sy=_midLine+location.row*(_midLine+_cellHeight);
double w=(_midLine+_cellWidth)*location.columnSpan-_midLine;
double h=(_midLine+_cellHeight)*location.rowSpan-_midLine;
return QRectF(sx,sy,w,h);
}
int LayoutBox::getLocation(LOCATION &cell){
for(int i=0;i<_locationList.length();i++){
LOCATION location=_locationList.at(i);
if(location.row<=cell.row&&cell.row<=location.row+location.rowSpan-1&&location.column<=cell.column&&cell.column<=location.column+location.columnSpan-1){
return i;
}
}
return -1;
}
void LayoutBox::setLocationAppID(int index, int appID){
_locationList[index].appID=appID;
}
后语
本示例代码没有实现保存和读取,可以自行定义配置文件保存这些布局数据,在需要时加载配置数据得到效果。