Files
StellarX-kaifa/src/table.cpp
T
2025-11-30 19:05:58 +08:00

621 lines
17 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "Table.h"
// 绘制表格的当前页
// 使用双循环绘制行和列,考虑分页偏移
void Table::drawTable()
{
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
// 表体从“表头之下”开始
dX = x + border;
dY = y + border + lineHeights.at(0) + TABLE_HEADER_EXTRA; // 表头高度
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
size_t startRow = (currentPage - 1) * rowsPerPage;
size_t endRow = startRow + (size_t)rowsPerPage < data.size() ? startRow + (size_t)rowsPerPage : data.size();
for (size_t i = startRow; i < endRow; ++i)
{
for (size_t j = 0; j < data[i].size(); ++j)
{
uX = dX + colWidths.at(j) + TABLE_COL_GAP;
fillrectangle(dX, dY, uX, uY);
outtextxy(dX + TABLE_PAD_X, dY + TABLE_PAD_Y, LPCTSTR(data[i][j].c_str()));
dX += colWidths.at(j) + TABLE_COL_GAP;
}
dX = x + border;
dY = uY;
uY = dY + lineHeights.at(0) + TABLE_ROW_EXTRA;
}
}
void Table::drawHeader()
{
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
// 内容区原点 = x+border, y+border
dX = x + border;
dY = y + border;
uY = dY + lineHeights.at(0) + TABLE_HEADER_EXTRA;
for (size_t i = 0; i < headers.size(); i++)
{
uX = dX + colWidths.at(i) + TABLE_COL_GAP; // 注意这里是 +20,和表体一致
fillrectangle(dX, dY, uX, uY);
outtextxy(dX + TABLE_PAD_X, dY + TABLE_PAD_Y, LPCTSTR(headers[i].c_str()));
dX += colWidths.at(i) + TABLE_COL_GAP; // 列间距 20
}
}
// 遍历所有数据单元和表头,计算每列的最大宽度和每行的最大高度,
// 为后续绘制表格单元格提供尺寸依据。此计算在数据变更时自动触发。
void Table::initTextWaH()
{
// 和绘制一致的单元内边距
const int padX = TABLE_PAD_X; // 左右 padding
const int padY = TABLE_PAD_Y; // 上下 padding
const int colGap = TABLE_COL_GAP; // 列间距
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
// 统计每列最大文本宽 & 每列最大行高(包含数据 + 表头)
colWidths.assign(headers.size(), 0);
lineHeights.assign(headers.size(), 0);
// 先看数据
for (size_t i = 0; i < data.size(); ++i)
{
for (size_t j = 0; j < data[i].size(); ++j)
{
const int w = textwidth(LPCTSTR(data[i][j].c_str()));
const int h = textheight(LPCTSTR(data[i][j].c_str()));
if (w > colWidths[j]) colWidths[j] = w;
if (h > lineHeights[j]) lineHeights[j] = h;
}
}
// 再用表头更新(谁大取谁)
for (size_t j = 0; j < headers.size(); ++j)
{
const int w = textwidth(LPCTSTR(headers[j].c_str()));
const int h = textheight(LPCTSTR(headers[j].c_str()));
if (w > colWidths[j]) colWidths[j] = w;
if (h > lineHeights[j]) lineHeights[j] = h;
}
// 用“所有列的最大行高”作为一行的基准高度
int maxLineH = 0;
for (int h : lineHeights)
if (h > maxLineH)
maxLineH = h;
// 列宽包含左右 padding:在计算完最大文本宽度后,加上 2*padX 作为单元格内边距
for (size_t j = 0; j < colWidths.size(); ++j) {
colWidths[j] += 2 * padX;
}
// 表内容总宽 = Σ(列宽 + 列间距)
int contentW = 0;
for (size_t j = 0; j < colWidths.size(); ++j)
contentW += colWidths[j] + colGap;
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding
const int headerH = maxLineH + 2 * padY;
const int rowH = maxLineH + 2 * padY;
const int rowsH = rowH * rowsPerPage;
// 页脚:
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
const int btnTextH = textheight(LPCTSTR("上一页"));
const int btnPadV = TABLE_BTN_TEXT_PAD_V;
const int btnH = btnTextH + 2 * btnPadV;
const int footerPad = TABLE_FOOTER_PAD;
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
// 最终表宽/高:内容 + 对称边框
this->width = contentW + (border << 1);
this->height = headerH + rowsH + footerH + (border << 1);
// 记录原始宽高用于锚点布局的参考;此处仅在初始化单元尺寸时重置
this->localWidth = this->width;
this->localHeight = this->height;
}
void Table::initButton()
{
const int gap = TABLE_BTN_GAP;
const int padH = TABLE_BTN_PAD_H;
const int padV = TABLE_BTN_PAD_V; // 按钮垂直内边距
int pageW = textwidth(LPCTSTR(pageNumtext.c_str()));
int lblH = textheight(LPCTSTR(pageNumtext.c_str()));
// 统一按钮尺寸(用按钮文字自身宽高 + padding)
int prevW = textwidth(LPCTSTR(TABLE_STR_PREV)) + padH * 2;
int nextW = textwidth(LPCTSTR(TABLE_STR_NEXT)) + padH * 2;
int btnH = lblH + padV * 2;
// 基于“页码标签”的矩形来摆放:
// prev 在页码左侧 gap 处;next 在右侧 gap 处;Y 对齐 pY
int prevX = pX - gap - prevW;
int nextX = pX + pageW + gap;
int btnY = pY; // 和页码同一基线
if (!prevButton)
prevButton = new Button(prevX, btnY, prevW, btnH, TABLE_STR_PREV, RGB(0, 0, 0), RGB(255, 255, 255));
else
{
prevButton->setX(prevX);
prevButton->setY(btnY);
}
if (!nextButton)
nextButton = new Button(nextX, btnY, nextW, btnH, TABLE_STR_NEXT, RGB(0, 0, 0), RGB(255, 255, 255));
else
{
nextButton->setX(nextX);
nextButton->setY(btnY);
}
prevButton->textStyle = this->textStyle;
nextButton->textStyle = this->textStyle;
prevButton->setFillMode(tableFillMode);
nextButton->setFillMode(tableFillMode);
prevButton->setOnClickListener([this]()
{
if (currentPage > 1)
{
--currentPage;
dirty = true;
if (pageNum) pageNum->setDirty(true);
}
});
nextButton->setOnClickListener([this]()
{
if (currentPage < totalPages)
{
++currentPage;
dirty = true;
if (pageNum) pageNum->setDirty(true);
}
});
isNeedButtonAndPageNum = false;
}
void Table::initPageNum()
{
// 统一坐标系
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
const int baseH = lineHeights.empty() ? 0 : lineHeights.at(0);
const int headerH = baseH + TABLE_HEADER_EXTRA;
const int rowsH = baseH * rowsPerPage + rowsPerPage * TABLE_ROW_EXTRA;
// 内容宽度 = sum(colWidths + 20)initTextWaH() 已把 this->width += 2*border
// 因此 contentW = this->width - 2*border 更稳妥
const int contentW = this->width - (border << 1);
// 页脚顶部位置(表头 + 可视数据区 之后)
pY = y + border + headerH + rowsH + TABLE_FOOTER_BLANK; // +8 顶部留白
// 按理来说 x + (this->width - textW) / 2;就可以
// 但是在绘制时,发现控件偏右,因此减去40
int textW = textwidth(LPCTSTR(pageNumtext.c_str()));
pX = x + TABLE_PAGE_TEXT_OFFSET_X +(this->width - textW) / 2;
if (!pageNum)
pageNum = new Label(pX, pY, pageNumtext);
else
{
pageNum->setX(pX);
pageNum->setY(pY);
}
pageNum->textStyle = this->textStyle;
if (StellarX::FillMode::Null == tableFillMode)
pageNum->setTextdisap(true); // 透明文本
}
void Table::drawPageNum()
{
pageNumtext = "";
pageNumtext+= std::to_string(currentPage);
pageNumtext += "页/共";
pageNumtext += std::to_string(totalPages);
pageNumtext += "";
if (nullptr == pageNum || isNeedButtonAndPageNum)
initPageNum();
pageNum->setText(pageNumtext);
pageNum->textStyle = this->textStyle;
if (StellarX::FillMode::Null == tableFillMode)
pageNum->setTextdisap(true);
pageNum->draw();
}
void Table::drawButton()
{
if ((nullptr == prevButton || nullptr == nextButton)|| isNeedButtonAndPageNum)
initButton();
this->prevButton->textStyle = this->textStyle;
this->nextButton->textStyle = this->textStyle;
this->prevButton->setFillMode(tableFillMode);
this->nextButton->setFillMode(tableFillMode);
this->prevButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
this->nextButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
this->prevButton->setDirty(true);
this->nextButton->setDirty(true);
prevButton->draw();
nextButton->draw();
}
void Table::setX(int x)
{
this->x = x;
isNeedButtonAndPageNum = true;
dirty = true;
}
void Table::setY(int y)
{
this->y = y;
isNeedButtonAndPageNum = true;
dirty = true;
}
void Table::setWidth(int width)
{
// 调整列宽以匹配新的表格总宽度。不修改 localWidth,避免累计误差。
// 当 width 与当前 width 不同时,根据差值平均分配到各列,余数依次累加/扣减。
const int ncols = static_cast<int>(colWidths.size());
if (ncols <= 0) {
this->width = width;
isNeedButtonAndPageNum = true;
return;
}
int diff = width - this->width;
// 基础增量:整除部分
int baseChange = diff / ncols;
int remainder = diff % ncols;
for (int i = 0; i < ncols; ++i) {
int change = baseChange;
if (remainder > 0) {
change += 1;
remainder -= 1;
} else if (remainder < 0) {
change -= 1;
remainder += 1;
}
int newWidth = colWidths[i] + change;
// 限制最小宽度为 1,防止出现负值
if (newWidth < 1) newWidth = 1;
colWidths[i] = newWidth;
}
this->width = width;
// 需要重新布局页脚元素
isNeedButtonAndPageNum = true;
}
void Table::setHeight(int height)
{
//高度不变
}
Table::Table(int x, int y)
:Control(x, y, 0, 0)
{
this->id = "Table";
}
Table::~Table()
{
if (this->prevButton)
delete this->prevButton;
if (this->nextButton)
delete this->nextButton;
if (this->pageNum)
delete this->pageNum;
if (this->saveBkImage)
delete this->saveBkImage;
this->prevButton = nullptr;
this->nextButton = nullptr;
this->pageNum = nullptr;
this->saveBkImage = nullptr;
}
void Table::draw()
{
//在这里先初始化保证翻页按钮不为空
// 在一些容器中,Table不会被立即绘制可能导致事件事件传递时触发空指针警报
// 由于单元格初始化依赖字体数据所以先设置一次字体样式
// 先保存当前绘图状态
saveStyle();
// 设置表格样式
setfillcolor(tableBkClor);
setlinecolor(tableBorderClor);
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
settextcolor(textStyle.color);
setlinestyle((int)tableLineStyle, tableBorderWidth);
setfillstyle((int)tableFillMode);
// 是否需要计算单元格尺寸
if (isNeedCellSize)
{
initTextWaH();
isNeedCellSize = false;
}
restoreStyle();
if (this->dirty && this->show)
{
// 先保存当前绘图状态
saveStyle();
// 设置表格样式
setfillcolor(tableBkClor);
setlinecolor(tableBorderClor);
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
settextcolor(textStyle.color);
setlinestyle((int)tableLineStyle, tableBorderWidth);
setfillstyle((int)tableFillMode);
setbkmode(TRANSPARENT);
if (isNeedDrawHeaders)
{
// 重新设置表格样式
setfillcolor(tableBkClor);
setlinecolor(tableBorderClor);
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
settextcolor(textStyle.color);
setlinestyle((int)tableLineStyle, tableBorderWidth);
setfillstyle((int)tableFillMode);
setbkmode(TRANSPARENT);
}
// 在绘制前先恢复并更新背景快照:
// 如果已有快照且尺寸发生变化,先恢复旧快照以清除上一次绘制,然后丢弃旧快照再重新抓取新的区域。
if (hasSnap)
{
// 始终先恢复旧背景,清除上一帧内容
restBackground();
// 当尺寸变化或缓存图像无效时,需要重新截图
if (!saveBkImage || saveWidth != this->width || saveHeight != this->height)
{
discardBackground();
saveBackground(this->x, this->y, this->width, this->height);
}
}
else
{
// 首次绘制时无背景缓存,直接抓取
saveBackground(this->x, this->y, this->width, this->height);
}
// 恢复最新的背景,保证绘制区域干净
restBackground();
// 绘制表头
dX = x;
dY = y;
drawHeader();
this->isNeedDrawHeaders = false;
// 绘制当前页
drawTable();
// 绘制页码标签
drawPageNum();
// 绘制翻页按钮
if (this->isShowPageButton)
drawButton();
// 恢复绘图状态
restoreStyle();
dirty = false; // 标记不需要重绘
}
}
bool Table::handleEvent(const ExMessage& msg)
{
if(!show)return false;
bool consume = false;
if(!this->isShowPageButton)
return consume;
else
{
if(prevButton)consume = prevButton->handleEvent(msg);
if (nextButton&&!consume)
consume = nextButton->handleEvent(msg);
}
if (dirty)
requestRepaint(parent);
return consume;
}
void Table::setHeaders(std::initializer_list<std::string> headers)
{
this->headers.clear();
for (auto& lis : headers)
this->headers.push_back(lis);
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
isNeedDrawHeaders = true; // 标记需要重新绘制表头
dirty = true;
}
void Table::setData( std::vector<std::string> data)
{
if (data.size() < headers.size())
for (int i = 0; data.size() <= headers.size(); i++)
data.push_back("");
this->data.push_back(data);
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
if (totalPages < 1)
totalPages = 1;
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
dirty = true;
}
void Table::setData( std::initializer_list<std::vector<std::string>> data)
{
for (auto lis : data)
if (lis.size() < headers.size())
{
for (size_t i = lis.size(); i< headers.size(); i++)
lis.push_back("");
this->data.push_back(lis);
}
else
this->data.push_back(lis);
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
if (totalPages < 1)
totalPages = 1;
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
dirty = true;
}
void Table::setRowsPerPage(int rows)
{
this->rowsPerPage = rows;
totalPages = ((int)data.size() + rowsPerPage - 1) / rowsPerPage;
if (totalPages < 1)
totalPages = 1;
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
dirty = true;
}
void Table::showPageButton(bool isShow)
{
this->isShowPageButton = isShow;
this->dirty = true;
}
void Table::setTableBorder(COLORREF color)
{
this->tableBorderClor = color;
this->dirty = true;
}
void Table::setTableBk(COLORREF color)
{
this->tableBkClor = color;
this->dirty = true;
}
void Table::setTableFillMode(StellarX::FillMode mode)
{
if (StellarX::FillMode::Solid == mode || StellarX::FillMode::Null == mode)
this->tableFillMode = mode;
else
this->tableFillMode = StellarX::FillMode::Solid;
if (this->prevButton && this->nextButton && this->pageNum)
{
this->prevButton->textStyle = this->textStyle;
this->nextButton->textStyle = this->textStyle;
this->prevButton->setFillMode(tableFillMode);
this->nextButton->setFillMode(tableFillMode);
if (StellarX::FillMode::Null == tableFillMode)
pageNum->setTextdisap(true);
this->prevButton->setDirty(true);
this->nextButton->setDirty(true);
}
this->dirty = true;
}
void Table::setTableLineStyle(StellarX::LineStyle style)
{
this->tableLineStyle = style;
this->dirty = true;
}
void Table::setTableBorderWidth(int width)
{
this->tableBorderWidth = width;
this->dirty = true;
}
void Table::onWindowResize()
{
Control::onWindowResize(); // 先处理自己
if (this->prevButton && this->nextButton && this->pageNum)
{
prevButton->onWindowResize();
nextButton->onWindowResize();
pageNum->onWindowResize();
}
}
int Table::getCurrentPage() const
{
return this->currentPage;
}
int Table::getTotalPages() const
{
return this->totalPages;
}
int Table::getRowsPerPage() const
{
return this->rowsPerPage;
}
bool Table::getShowPageButton() const
{
return this->isShowPageButton;
}
COLORREF Table::getTableBorder() const
{
return this->tableBorderClor;
}
COLORREF Table::getTableBk() const
{
return this->tableBkClor;
}
StellarX::FillMode Table::getTableFillMode() const
{
return this->tableFillMode;
}
StellarX::LineStyle Table::getTableLineStyle() const
{
return this->tableLineStyle;
}
std::vector<std::string> Table::getHeaders() const
{
return this->headers;
}
std::vector<std::vector<std::string>> Table::getData() const
{
return this->data;
}
int Table::getTableBorderWidth() const
{
return this->tableBorderWidth;
}
int Table::getTableWidth() const
{
int temp = 0;
for (auto& w : colWidths)
temp += w;
return temp;
}
int Table::getTableHeight() const
{
return 0;
}