1712 lines
46 KiB
C++
1712 lines
46 KiB
C++
#include "TableView.h"
|
||
|
||
namespace ezui {
|
||
|
||
TableView::TableView(Object* parentObject) : Control(parentObject) {
|
||
Init();
|
||
}
|
||
|
||
TableView::~TableView() {
|
||
EndEdit(false);
|
||
// 注意:m_editBox、m_editCombo、m_editCheck 作为子控件,由父控件自动管理
|
||
// 不需要手动 delete,否则会导致双重释放
|
||
m_editBox = nullptr;
|
||
m_editCombo = nullptr;
|
||
m_editCheck = nullptr;
|
||
}
|
||
|
||
void TableView::Init() {
|
||
// 初始化垂直滚动条
|
||
m_vScrollBar.SetWidth(10);
|
||
m_vScrollBar.Parent = this;
|
||
m_vScrollBar.OffsetCallback = [this](int offset) {
|
||
this->OffsetY(offset);
|
||
};
|
||
|
||
// 初始化水平滚动条
|
||
m_hScrollBar.SetHeight(10);
|
||
m_hScrollBar.Parent = this;
|
||
m_hScrollBar.OffsetCallback = [this](int offset) {
|
||
this->OffsetX(offset);
|
||
};
|
||
|
||
// 创建编辑控件(初始隐藏)
|
||
m_editBox = new TextBox();
|
||
m_editBox->SetVisible(false);
|
||
m_editBox->SetMultiLine(true);
|
||
// 为编辑框设置默认字体样式
|
||
m_editBox->Style.FontSize = m_cellFontSize;
|
||
m_editBox->Style.FontFamily = L"Microsoft YaHei";
|
||
// 添加到子控件列表,这样才能正确处理事件和绘制
|
||
this->Add(m_editBox);
|
||
m_editBox->TextChanged = [this](const UIString& text) {
|
||
if (m_editing && m_editRow >= 0 && m_editCol >= 0) {
|
||
// 实时更新数据
|
||
if (m_editRow < (int)m_data.size() && m_editCol < (int)m_data[m_editRow].size()) {
|
||
m_data[m_editRow][m_editCol].Text = text;
|
||
UpdateRowHeight(m_editRow);
|
||
if (CellValueChanged) {
|
||
CellValueChanged(m_editRow, m_editCol, text);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
int TableView::GetContentWidth() const {
|
||
int width = m_firstColumnWidth;
|
||
for (const auto& col : m_columns) {
|
||
width += col.Width;
|
||
}
|
||
return width;
|
||
}
|
||
|
||
int TableView::GetContentHeight() const {
|
||
int height = m_headerHeight;
|
||
for (int h : m_rowHeights) {
|
||
height += h;
|
||
}
|
||
return height;
|
||
}
|
||
|
||
int TableView::GetColumnX(int colIndex) const {
|
||
int x = m_firstColumnWidth - m_scrollOffsetX;
|
||
for (int i = 0; i < colIndex && i < (int)m_columns.size(); ++i) {
|
||
x += m_columns[i].Width;
|
||
}
|
||
return x;
|
||
}
|
||
|
||
int TableView::GetRowY(int rowIndex) const {
|
||
int y = m_headerHeight - m_scrollOffsetY;
|
||
for (int i = 0; i < rowIndex && i < (int)m_rowHeights.size(); ++i) {
|
||
y += m_rowHeights[i];
|
||
}
|
||
return y;
|
||
}
|
||
|
||
bool TableView::HitTestCell(const Point& pt, int* outRow, int* outCol) const {
|
||
// 检查是否在表头区域
|
||
if (pt.Y < m_headerHeight) {
|
||
*outRow = -1;
|
||
// 检查是否在第一列
|
||
if (pt.X < m_firstColumnWidth) {
|
||
*outCol = -1;
|
||
return true;
|
||
}
|
||
// 检查是否在数据列
|
||
int x = m_firstColumnWidth;
|
||
for (int i = 0; i < (int)m_columns.size(); ++i) {
|
||
if (pt.X >= x - m_scrollOffsetX && pt.X < x - m_scrollOffsetX + m_columns[i].Width) {
|
||
*outCol = i;
|
||
return true;
|
||
}
|
||
x += m_columns[i].Width;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 检查行
|
||
int y = m_headerHeight;
|
||
for (int row = 0; row < (int)m_rowHeights.size(); ++row) {
|
||
int rowY = y - m_scrollOffsetY;
|
||
if (pt.Y >= rowY && pt.Y < rowY + m_rowHeights[row]) {
|
||
*outRow = row;
|
||
|
||
// 检查列
|
||
if (pt.X < m_firstColumnWidth) {
|
||
*outCol = -1; // 第一列
|
||
return true;
|
||
}
|
||
|
||
int x = m_firstColumnWidth;
|
||
for (int col = 0; col < (int)m_columns.size(); ++col) {
|
||
int colX = x - m_scrollOffsetX;
|
||
if (pt.X >= colX && pt.X < colX + m_columns[col].Width) {
|
||
*outCol = col;
|
||
return true;
|
||
}
|
||
x += m_columns[col].Width;
|
||
}
|
||
return false;
|
||
}
|
||
y += m_rowHeights[row];
|
||
}
|
||
|
||
*outRow = -1;
|
||
*outCol = -1;
|
||
return false;
|
||
}
|
||
|
||
int TableView::HitTestColumnBorder(const Point& pt) const {
|
||
const int borderHitWidth = 5; // 边界检测宽度
|
||
|
||
// 只在表头区域检测
|
||
if (pt.Y >= m_headerHeight) {
|
||
return -2; // 返回-2表示不在表头区域,不应该显示拖动光标
|
||
}
|
||
|
||
// 检测第一列右边界
|
||
int x = m_firstColumnWidth;
|
||
if (std::abs(pt.X - x) <= borderHitWidth) {
|
||
return -1; // 返回-1表示第一列边界(特殊处理)
|
||
}
|
||
|
||
// 检测数据列边界
|
||
for (int i = 0; i < (int)m_columns.size(); ++i) {
|
||
x += m_columns[i].Width;
|
||
int borderX = x - m_scrollOffsetX;
|
||
if (std::abs(pt.X - borderX) <= borderHitWidth) {
|
||
return i;
|
||
}
|
||
}
|
||
|
||
return -2; // 未命中任何边界
|
||
}
|
||
|
||
bool TableView::HitTestVScrollBar(const Point& pt) {
|
||
if (!m_vScrollBar.IsVisible()) {
|
||
return false;
|
||
}
|
||
Rect scrollRect = m_vScrollBar.GetRect();
|
||
return scrollRect.Contains(pt);
|
||
}
|
||
|
||
bool TableView::HitTestHScrollBar(const Point& pt) {
|
||
if (!m_hScrollBar.IsVisible()) {
|
||
return false;
|
||
}
|
||
Rect scrollRect = m_hScrollBar.GetRect();
|
||
return scrollRect.Contains(pt);
|
||
}
|
||
|
||
void TableView::DrawHeader(PaintEventArgs& args) {
|
||
auto& g = args.Graphics;
|
||
|
||
// 绘制表头背景
|
||
Rect headerRect(0, 0, Width(), m_headerHeight);
|
||
g.SetColor(m_headerBackColor);
|
||
g.FillRectangle(RectF(headerRect));
|
||
|
||
// 绘制第一列表头
|
||
Rect firstColRect(0, 0, m_firstColumnWidth, m_headerHeight);
|
||
g.SetColor(m_headerBackColor);
|
||
g.FillRectangle(RectF(firstColRect));
|
||
|
||
// 如果第一列是 CheckBox 类型,绘制全选复选框
|
||
if (m_firstColumnType == FirstColumnType::CheckBox) {
|
||
int checkSize = (std::min)(m_headerHeight - 8, 16);
|
||
int checkX = (m_firstColumnWidth - checkSize) / 2;
|
||
int checkY = (m_headerHeight - checkSize) / 2;
|
||
Rect checkRect(checkX, checkY, checkSize, checkSize);
|
||
|
||
// 绘制复选框边框
|
||
g.SetColor(m_cellBorderColor);
|
||
g.DrawRectangle(RectF(checkRect));
|
||
|
||
// 如果全选,绘制勾选标记
|
||
if (m_headerSelectAll) {
|
||
g.SetColor(m_headerForeColor);
|
||
// 简单的勾选线
|
||
PointF p1(checkX + 3, checkY + checkSize / 2);
|
||
PointF p2(checkX + checkSize / 3, checkY + checkSize - 4);
|
||
PointF p3(checkX + checkSize - 3, checkY + 4);
|
||
g.DrawLine(p1, p2);
|
||
g.DrawLine(p2, p3);
|
||
}
|
||
} else if (m_firstColumnType == FirstColumnType::Index) {
|
||
// 绘制 "#" 或序号标题
|
||
Font font(m_cellFontFamily, m_cellFontSize);
|
||
g.SetFont(font);
|
||
g.SetColor(m_headerForeColor);
|
||
TextLayout layout(L"#", font, SizeF(m_firstColumnWidth, m_headerHeight), TextAlign::MiddleCenter);
|
||
g.DrawTextLayout(layout, PointF(0, 0));
|
||
}
|
||
|
||
// 绘制第一列边框
|
||
if (m_cellBorderStyle != StrokeStyle::None) {
|
||
g.SetColor(m_cellBorderColor);
|
||
g.DrawRectangle(RectF(firstColRect));
|
||
}
|
||
|
||
// 绘制数据列表头
|
||
int x = m_firstColumnWidth - m_scrollOffsetX;
|
||
for (int i = 0; i < (int)m_columns.size(); ++i) {
|
||
const auto& col = m_columns[i];
|
||
|
||
// 裁剪到可见区域
|
||
if (x + col.Width <= m_firstColumnWidth || x >= Width()) {
|
||
x += col.Width;
|
||
continue;
|
||
}
|
||
|
||
Rect colRect(x, 0, col.Width, m_headerHeight);
|
||
|
||
// 绘制表头文字
|
||
Font font(m_cellFontFamily, m_cellFontSize);
|
||
g.SetFont(font);
|
||
g.SetColor(m_headerForeColor);
|
||
|
||
// 添加排序指示器
|
||
std::wstring headerText = col.HeaderText.unicode();
|
||
if (col.CurrentSort == SortOrder::Ascending) {
|
||
headerText = headerText + L" ▲";
|
||
} else if (col.CurrentSort == SortOrder::Descending) {
|
||
headerText = headerText + L" ▼";
|
||
}
|
||
|
||
TextLayout layout(headerText, font, SizeF(col.Width - 4, m_headerHeight), TextAlign::MiddleCenter);
|
||
g.DrawTextLayout(layout, PointF(x + 2, 0));
|
||
|
||
// 绘制边框
|
||
if (m_cellBorderStyle != StrokeStyle::None) {
|
||
g.SetColor(m_cellBorderColor);
|
||
g.DrawRectangle(RectF(colRect));
|
||
}
|
||
|
||
x += col.Width;
|
||
}
|
||
}
|
||
|
||
void TableView::DrawCells(PaintEventArgs& args) {
|
||
int startY = m_headerHeight;
|
||
|
||
for (int row = 0; row < (int)m_data.size(); ++row) {
|
||
int rowY = startY + GetRowY(row) - m_headerHeight;
|
||
int rowHeight = m_rowHeights[row];
|
||
|
||
// 跳过不可见行
|
||
if (rowY + rowHeight <= m_headerHeight || rowY >= Height()) {
|
||
continue;
|
||
}
|
||
|
||
// 绘制第一列
|
||
Rect firstColRect(0, rowY, m_firstColumnWidth, rowHeight);
|
||
DrawFirstColumn(args, row, firstColRect);
|
||
|
||
// 绘制数据列
|
||
int x = m_firstColumnWidth - m_scrollOffsetX;
|
||
for (int col = 0; col < (int)m_columns.size(); ++col) {
|
||
int colWidth = m_columns[col].Width;
|
||
|
||
// 跳过不可见列
|
||
if (x + colWidth <= m_firstColumnWidth || x >= Width()) {
|
||
x += colWidth;
|
||
continue;
|
||
}
|
||
|
||
Rect cellRect(x, rowY, colWidth, rowHeight);
|
||
DrawCell(args, row, col, cellRect);
|
||
x += colWidth;
|
||
}
|
||
}
|
||
}
|
||
|
||
void TableView::DrawFirstColumn(PaintEventArgs& args, int row, const Rect& cellRect) {
|
||
auto& g = args.Graphics;
|
||
|
||
// 获取行选中状态背景色
|
||
Color backColor = m_cellBackColor;
|
||
if (m_firstColumnType == FirstColumnType::CheckBox && row < (int)m_rowChecked.size() && m_rowChecked[row]) {
|
||
backColor = SelectedRowBackColor;
|
||
}
|
||
|
||
// 绘制背景
|
||
g.SetColor(backColor);
|
||
g.FillRectangle(RectF(cellRect));
|
||
|
||
// 根据第一列类型绘制内容
|
||
if (m_firstColumnType == FirstColumnType::Index) {
|
||
// 绘制序号
|
||
Font font(m_cellFontFamily, m_cellFontSize);
|
||
g.SetFont(font);
|
||
g.SetColor(m_cellForeColor);
|
||
std::wstring indexText = std::to_wstring(row + 1); // 从1开始
|
||
TextLayout layout(indexText, font, SizeF(m_firstColumnWidth - 4, cellRect.Height), TextAlign::MiddleCenter);
|
||
g.DrawTextLayout(layout, PointF(cellRect.X + 2, cellRect.Y));
|
||
} else if (m_firstColumnType == FirstColumnType::CheckBox) {
|
||
// 绘制复选框
|
||
int checkSize = (std::min)(cellRect.Height - 8, 16);
|
||
int checkX = cellRect.X + (m_firstColumnWidth - checkSize) / 2;
|
||
int checkY = cellRect.Y + (cellRect.Height - checkSize) / 2;
|
||
Rect checkRect(checkX, checkY, checkSize, checkSize);
|
||
|
||
g.SetColor(m_cellBorderColor);
|
||
g.DrawRectangle(RectF(checkRect));
|
||
|
||
if (row < (int)m_rowChecked.size() && m_rowChecked[row]) {
|
||
g.SetColor(m_cellForeColor);
|
||
PointF p1(checkX + 3, checkY + checkSize / 2);
|
||
PointF p2(checkX + checkSize / 3, checkY + checkSize - 4);
|
||
PointF p3(checkX + checkSize - 3, checkY + 4);
|
||
g.DrawLine(p1, p2);
|
||
g.DrawLine(p2, p3);
|
||
}
|
||
}
|
||
// TextBox 类型第一列留空
|
||
|
||
// 绘制边框
|
||
if (m_cellBorderStyle != StrokeStyle::None) {
|
||
g.SetColor(m_cellBorderColor);
|
||
g.DrawRectangle(RectF(cellRect));
|
||
}
|
||
}
|
||
|
||
void TableView::DrawCell(PaintEventArgs& args, int row, int col, const Rect& cellRect) {
|
||
auto& g = args.Graphics;
|
||
|
||
if (row >= (int)m_data.size() || col >= (int)m_data[row].size()) {
|
||
return;
|
||
}
|
||
|
||
const auto& cellData = m_data[row][col];
|
||
const auto& colInfo = m_columns[col];
|
||
|
||
// 确定背景色
|
||
Color backColor = m_cellBackColor;
|
||
|
||
// 如果第一列是 CheckBox 且选中,使用选中行背景色(优先级低)
|
||
if (m_firstColumnType == FirstColumnType::CheckBox && row < (int)m_rowChecked.size() && m_rowChecked[row]) {
|
||
backColor = SelectedRowBackColor;
|
||
}
|
||
|
||
// 如果单元格有独立背景色,使用独立设置(优先级高)
|
||
if (cellData.Style.HasBackColor) {
|
||
backColor = cellData.Style.BackColor;
|
||
}
|
||
|
||
// 如果正在编辑此单元格,跳过内容绘制(只绘制背景),让 TextBox 绘制内容
|
||
bool isEditing = m_editing && m_editRow == row && m_editCol == col;
|
||
|
||
// 绘制背景(编辑时也需要绘制,但使用白色以匹配 TextBox)
|
||
if (isEditing) {
|
||
g.SetColor(Color::White);
|
||
} else {
|
||
g.SetColor(backColor);
|
||
}
|
||
g.FillRectangle(RectF(cellRect));
|
||
|
||
// 如果正在编辑此单元格,跳过内容绘制
|
||
if (isEditing) {
|
||
return;
|
||
}
|
||
|
||
// 确定前景色
|
||
Color foreColor = m_cellForeColor;
|
||
if (cellData.Style.HasForeColor) {
|
||
foreColor = cellData.Style.ForeColor;
|
||
}
|
||
|
||
// 根据单元格类型绘制内容
|
||
Font font(m_cellFontFamily, m_cellFontSize);
|
||
g.SetFont(font);
|
||
g.SetColor(foreColor);
|
||
|
||
switch (colInfo.Type) {
|
||
case CellType::TextBox:
|
||
case CellType::ReadOnly: {
|
||
// 绘制文本
|
||
TextLayout layout(cellData.Text.unicode(), font,
|
||
SizeF(cellRect.Width - 4, cellRect.Height - 2), TextAlign::MiddleLeft);
|
||
g.DrawTextLayout(layout, PointF(cellRect.X + 2, cellRect.Y + 1));
|
||
break;
|
||
}
|
||
case CellType::CheckBox: {
|
||
// 绘制复选框
|
||
int checkSize = (std::min)(cellRect.Height - 8, 16);
|
||
int checkX = cellRect.X + (cellRect.Width - checkSize) / 2;
|
||
int checkY = cellRect.Y + (cellRect.Height - checkSize) / 2;
|
||
Rect checkRect(checkX, checkY, checkSize, checkSize);
|
||
|
||
g.SetColor(m_cellBorderColor);
|
||
g.DrawRectangle(RectF(checkRect));
|
||
|
||
if (cellData.Checked) {
|
||
g.SetColor(foreColor);
|
||
PointF p1(checkX + 3, checkY + checkSize / 2);
|
||
PointF p2(checkX + checkSize / 3, checkY + checkSize - 4);
|
||
PointF p3(checkX + checkSize - 3, checkY + 4);
|
||
g.DrawLine(p1, p2);
|
||
g.DrawLine(p2, p3);
|
||
}
|
||
break;
|
||
}
|
||
case CellType::ComboBox: {
|
||
// 绘制下拉框文本
|
||
UIString displayText;
|
||
if (cellData.ComboIndex >= 0 && cellData.ComboIndex < (int)colInfo.ComboItems.size()) {
|
||
displayText = colInfo.ComboItems[cellData.ComboIndex];
|
||
}
|
||
TextLayout layout(displayText.unicode(), font,
|
||
SizeF(cellRect.Width - 20, cellRect.Height - 2), TextAlign::MiddleLeft);
|
||
g.DrawTextLayout(layout, PointF(cellRect.X + 2, cellRect.Y + 1));
|
||
|
||
// 绘制下拉箭头
|
||
int arrowSize = 8;
|
||
int arrowX = cellRect.GetRight() - arrowSize - 4;
|
||
int arrowY = cellRect.Y + (cellRect.Height - arrowSize / 2) / 2;
|
||
PointF ap1(arrowX, arrowY);
|
||
PointF ap2(arrowX + arrowSize, arrowY);
|
||
PointF ap3(arrowX + arrowSize / 2, arrowY + arrowSize / 2);
|
||
g.SetColor(foreColor);
|
||
g.DrawLine(ap1, ap3);
|
||
g.DrawLine(ap2, ap3);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 绘制边框
|
||
StrokeStyle borderStyle = m_cellBorderStyle;
|
||
Color borderColor = m_cellBorderColor;
|
||
|
||
if (cellData.Style.HasBorderStyle) {
|
||
borderStyle = cellData.Style.BorderStyle;
|
||
}
|
||
if (cellData.Style.HasBorderColor) {
|
||
borderColor = cellData.Style.BorderColor;
|
||
}
|
||
|
||
if (borderStyle != StrokeStyle::None) {
|
||
g.SetColor(borderColor);
|
||
// TODO: 支持虚线样式
|
||
g.DrawRectangle(RectF(cellRect));
|
||
}
|
||
}
|
||
|
||
void TableView::BeginEdit(int row, int col) {
|
||
if (row < 0 || col < 0 || row >= (int)m_data.size() || col >= (int)m_columns.size()) {
|
||
return;
|
||
}
|
||
|
||
const auto& colInfo = m_columns[col];
|
||
|
||
// ReadOnly 类型不能编辑
|
||
if (colInfo.Type == CellType::ReadOnly) {
|
||
return;
|
||
}
|
||
|
||
EndEdit(true); // 结束之前的编辑
|
||
|
||
m_editing = true;
|
||
m_editRow = row;
|
||
m_editCol = col;
|
||
|
||
// 计算单元格位置
|
||
int x = GetColumnX(col);
|
||
int y = GetRowY(row);
|
||
int width = colInfo.Width;
|
||
int height = m_rowHeights[row];
|
||
|
||
Rect editRect(x, y, width, height);
|
||
|
||
switch (colInfo.Type) {
|
||
case CellType::TextBox: {
|
||
// 确保 TextBox 字体与单元格字体一致
|
||
m_editBox->Style.FontSize = m_cellFontSize;
|
||
m_editBox->Style.FontFamily = m_cellFontFamily;
|
||
m_editBox->SetRect(editRect);
|
||
m_editBox->SetText(m_data[row][col].Text);
|
||
m_editBox->SetVisible(true);
|
||
// 确保 TextBox 获得正确的窗口句柄(从 TableView 继承)
|
||
m_editBox->SetHwnd(this->Hwnd());
|
||
// 触发焦点事件(这会启动光标闪烁计时器)
|
||
m_editBox->SendEvent(FocusEventArgs(m_editBox));
|
||
Invalidate();
|
||
break;
|
||
}
|
||
case CellType::CheckBox: {
|
||
// 直接切换状态
|
||
m_data[row][col].Checked = !m_data[row][col].Checked;
|
||
m_editing = false;
|
||
m_editRow = -1;
|
||
m_editCol = -1;
|
||
if (CellValueChanged) {
|
||
CellValueChanged(row, col, m_data[row][col].Checked ? L"true" : L"false");
|
||
}
|
||
Invalidate();
|
||
break;
|
||
}
|
||
case CellType::ComboBox: {
|
||
// TODO: 显示下拉列表
|
||
// 暂时简化处理,循环选择
|
||
int nextIndex = (m_data[row][col].ComboIndex + 1) % (std::max)(1, (int)colInfo.ComboItems.size());
|
||
m_data[row][col].ComboIndex = nextIndex;
|
||
m_editing = false;
|
||
m_editRow = -1;
|
||
m_editCol = -1;
|
||
|
||
UIString newValue;
|
||
if (nextIndex >= 0 && nextIndex < (int)colInfo.ComboItems.size()) {
|
||
newValue = colInfo.ComboItems[nextIndex];
|
||
}
|
||
if (CellValueChanged) {
|
||
CellValueChanged(row, col, newValue);
|
||
}
|
||
Invalidate();
|
||
break;
|
||
}
|
||
default:
|
||
m_editing = false;
|
||
m_editRow = -1;
|
||
m_editCol = -1;
|
||
break;
|
||
}
|
||
}
|
||
|
||
void TableView::EndEdit(bool save) {
|
||
if (!m_editing) {
|
||
return;
|
||
}
|
||
|
||
if (save && m_editRow >= 0 && m_editCol >= 0) {
|
||
if (m_editRow < (int)m_data.size() && m_editCol < (int)m_data[m_editRow].size()) {
|
||
const auto& colInfo = m_columns[m_editCol];
|
||
if (colInfo.Type == CellType::TextBox) {
|
||
m_data[m_editRow][m_editCol].Text = m_editBox->GetText();
|
||
UpdateRowHeight(m_editRow);
|
||
}
|
||
}
|
||
}
|
||
|
||
m_editBox->SetVisible(false);
|
||
m_editing = false;
|
||
m_editRow = -1;
|
||
m_editCol = -1;
|
||
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::UpdateRowHeight(int row) {
|
||
if (row < 0 || row >= (int)m_data.size()) {
|
||
return;
|
||
}
|
||
|
||
int maxHeight = m_defaultRowHeight;
|
||
|
||
// 遍历该行所有单元格,计算最大需要的高度
|
||
for (int col = 0; col < (int)m_data[row].size() && col < (int)m_columns.size(); ++col) {
|
||
const auto& cellData = m_data[row][col];
|
||
const auto& colInfo = m_columns[col];
|
||
|
||
if (colInfo.Type == CellType::TextBox || colInfo.Type == CellType::ReadOnly) {
|
||
int lines = CalculateTextLines(cellData.Text, colInfo.Width - 4);
|
||
int neededHeight = lines * (m_cellFontSize + 4) + 8;
|
||
if (neededHeight > maxHeight) {
|
||
maxHeight = neededHeight;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (row < (int)m_rowHeights.size()) {
|
||
m_rowHeights[row] = maxHeight;
|
||
}
|
||
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
|
||
int TableView::CalculateTextLines(const UIString& text, int width) const {
|
||
if (text.empty() || width <= 0) {
|
||
return 1;
|
||
}
|
||
|
||
Font font(m_cellFontFamily, m_cellFontSize);
|
||
TextLayout layout(text.unicode(), font, SizeF(width, EZUI_FLOAT_MAX));
|
||
Size box = layout.GetFontBox();
|
||
|
||
int fontHeight = m_cellFontSize + 4;
|
||
int lines = (box.Height + fontHeight - 1) / fontHeight;
|
||
return (std::max)(1, lines);
|
||
}
|
||
|
||
void TableView::RefreshScrollBars() {
|
||
int contentWidth = GetContentWidth();
|
||
int contentHeight = GetContentHeight();
|
||
|
||
// 可视区域大小(初步计算,不考虑滚动条)
|
||
int viewWidth = Width();
|
||
int viewHeight = Height() - m_headerHeight;
|
||
|
||
// 判断是否需要滚动条
|
||
bool needVScroll = (contentHeight - m_headerHeight) > viewHeight;
|
||
bool needHScroll = contentWidth > viewWidth;
|
||
|
||
// 如果需要垂直滚动条,水平可视区域要减去滚动条宽度
|
||
if (needVScroll) {
|
||
viewWidth -= m_vScrollBar.Width();
|
||
// 重新判断是否需要水平滚动条
|
||
needHScroll = contentWidth > viewWidth;
|
||
}
|
||
|
||
// 如果需要水平滚动条,垂直可视区域要减去滚动条高度
|
||
if (needHScroll) {
|
||
viewHeight -= m_hScrollBar.Height();
|
||
// 重新判断是否需要垂直滚动条
|
||
if (!needVScroll) {
|
||
needVScroll = (contentHeight - m_headerHeight) > viewHeight;
|
||
if (needVScroll) {
|
||
viewWidth -= m_vScrollBar.Width();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设置垂直滚动条
|
||
if (needVScroll) {
|
||
m_vScrollBar.SetVisible(true);
|
||
int vScrollHeight = Height() - m_headerHeight - (needHScroll ? m_hScrollBar.Height() : 0);
|
||
m_vScrollBar.SetRect(Rect(Width() - m_vScrollBar.Width(), m_headerHeight,
|
||
m_vScrollBar.Width(), vScrollHeight));
|
||
} else {
|
||
m_vScrollBar.SetVisible(false);
|
||
m_scrollOffsetY = 0;
|
||
}
|
||
|
||
// 设置水平滚动条
|
||
if (needHScroll) {
|
||
m_hScrollBar.SetVisible(true);
|
||
int hScrollWidth = Width() - m_firstColumnWidth - (needVScroll ? m_vScrollBar.Width() : 0);
|
||
m_hScrollBar.SetRect(Rect(m_firstColumnWidth, Height() - m_hScrollBar.Height(),
|
||
hScrollWidth, m_hScrollBar.Height()));
|
||
} else {
|
||
m_hScrollBar.SetVisible(false);
|
||
m_scrollOffsetX = 0;
|
||
}
|
||
|
||
m_vScrollBar.RefreshScroll();
|
||
m_hScrollBar.RefreshScroll();
|
||
}
|
||
|
||
void TableView::OffsetX(int offset) {
|
||
m_scrollOffsetX = -offset;
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::OffsetY(int offset) {
|
||
m_scrollOffsetY = -offset;
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::DoSort(int colIndex) {
|
||
if (colIndex < 0 || colIndex >= (int)m_columns.size()) {
|
||
return;
|
||
}
|
||
|
||
// 更新排序状态
|
||
SortOrder newOrder = SortOrder::Ascending;
|
||
if (m_columns[colIndex].CurrentSort == SortOrder::Ascending) {
|
||
newOrder = SortOrder::Descending;
|
||
} else if (m_columns[colIndex].CurrentSort == SortOrder::Descending) {
|
||
newOrder = SortOrder::None;
|
||
}
|
||
|
||
// 重置其他列的排序状态
|
||
for (auto& col : m_columns) {
|
||
col.CurrentSort = SortOrder::None;
|
||
}
|
||
m_columns[colIndex].CurrentSort = newOrder;
|
||
|
||
if (newOrder == SortOrder::None) {
|
||
Invalidate();
|
||
return;
|
||
}
|
||
|
||
// 创建索引数组进行排序
|
||
std::vector<int> indices(m_data.size());
|
||
for (int i = 0; i < (int)indices.size(); ++i) {
|
||
indices[i] = i;
|
||
}
|
||
|
||
// 排序
|
||
std::sort(indices.begin(), indices.end(), [this, colIndex, newOrder](int a, int b) {
|
||
UIString textA, textB;
|
||
if (colIndex < (int)m_data[a].size()) {
|
||
textA = m_data[a][colIndex].Text;
|
||
}
|
||
if (colIndex < (int)m_data[b].size()) {
|
||
textB = m_data[b][colIndex].Text;
|
||
}
|
||
|
||
if (newOrder == SortOrder::Ascending) {
|
||
return textA < textB;
|
||
} else {
|
||
return textA > textB;
|
||
}
|
||
});
|
||
|
||
// 重新排列数据
|
||
std::vector<std::vector<CellData>> newData(m_data.size());
|
||
std::vector<int> newRowHeights(m_rowHeights.size());
|
||
std::vector<bool> newRowChecked(m_rowChecked.size());
|
||
|
||
for (int i = 0; i < (int)indices.size(); ++i) {
|
||
int oldIndex = indices[i];
|
||
newData[i] = std::move(m_data[oldIndex]);
|
||
if (oldIndex < (int)m_rowHeights.size()) {
|
||
newRowHeights[i] = m_rowHeights[oldIndex];
|
||
}
|
||
if (oldIndex < (int)m_rowChecked.size()) {
|
||
newRowChecked[i] = m_rowChecked[oldIndex];
|
||
}
|
||
}
|
||
|
||
m_data = std::move(newData);
|
||
m_rowHeights = std::move(newRowHeights);
|
||
m_rowChecked = std::move(newRowChecked);
|
||
|
||
Invalidate();
|
||
}
|
||
|
||
// ============ 重写的虚函数 ============
|
||
|
||
void TableView::OnPaint(PaintEventArgs& args) {
|
||
__super::OnPaint(args);
|
||
|
||
// 绘制单元格(先绘制,这样表头会覆盖在上面)
|
||
DrawCells(args);
|
||
|
||
// 绘制表头
|
||
DrawHeader(args);
|
||
|
||
// 绘制滚动条
|
||
if (m_vScrollBar.IsVisible()) {
|
||
m_vScrollBar.SendEvent(args);
|
||
}
|
||
if (m_hScrollBar.IsVisible()) {
|
||
m_hScrollBar.SendEvent(args);
|
||
}
|
||
|
||
// 绘制编辑控件(放在最后确保在最上层)
|
||
if (m_editBox->IsVisible()) {
|
||
m_editBox->SendEvent(args);
|
||
}
|
||
}
|
||
|
||
void TableView::OnChildPaint(PaintEventArgs& args) {
|
||
// 重写子控件绘制,跳过 m_editBox(因为我们在 OnPaint 中单独处理)
|
||
// 其他子控件正常绘制
|
||
ViewControls.clear();
|
||
Rect rect(0, 0, Width(), Height());
|
||
for (auto& it : GetControls()) {
|
||
if (it == m_editBox) {
|
||
continue; // 跳过编辑框,由 OnPaint 单独处理
|
||
}
|
||
if (rect.IntersectsWith(it->GetRect())) {
|
||
ViewControls.push_back(it);
|
||
}
|
||
it->SendEvent(args);
|
||
}
|
||
}
|
||
|
||
void TableView::OnLayout() {
|
||
// 设置滚动条位置
|
||
m_vScrollBar.SetRect(Rect(Width() - m_vScrollBar.Width(), m_headerHeight,
|
||
m_vScrollBar.Width(), Height() - m_headerHeight - (m_hScrollBar.IsVisible() ? m_hScrollBar.Height() : 0)));
|
||
|
||
m_hScrollBar.SetRect(Rect(m_firstColumnWidth, Height() - m_hScrollBar.Height(),
|
||
Width() - m_firstColumnWidth - (m_vScrollBar.IsVisible() ? m_vScrollBar.Width() : 0), m_hScrollBar.Height()));
|
||
|
||
RefreshScrollBars();
|
||
__super::OnLayout();
|
||
}
|
||
|
||
void TableView::OnMouseMove(const MouseEventArgs& args) {
|
||
__super::OnMouseMove(args);
|
||
|
||
// 转发事件给编辑框(用于文本选择)
|
||
if (m_editBox->IsVisible()) {
|
||
Rect editRect = m_editBox->GetRect();
|
||
m_editBox->SendEvent(MouseEventArgs(Event::OnMouseMove,
|
||
Point(args.Location.X - editRect.X, args.Location.Y - editRect.Y),
|
||
args.Button, args.ZDelta));
|
||
}
|
||
|
||
// 转发事件给滚动条
|
||
if (m_vScrollBar.IsVisible()) {
|
||
m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseMove,
|
||
Point(args.Location.X - m_vScrollBar.X(), args.Location.Y - m_vScrollBar.Y()),
|
||
args.Button, args.ZDelta));
|
||
}
|
||
if (m_hScrollBar.IsVisible()) {
|
||
m_hScrollBar.SendEvent(MouseEventArgs(Event::OnMouseMove,
|
||
Point(args.Location.X - m_hScrollBar.X(), args.Location.Y - m_hScrollBar.Y()),
|
||
args.Button, args.ZDelta));
|
||
}
|
||
|
||
// 列宽拖动
|
||
if (m_draggingColumn) {
|
||
int delta = args.Location.X - m_dragStartX;
|
||
int newWidth = (std::max)(20, m_dragStartWidth + delta);
|
||
|
||
if (m_dragColumnIndex == -1) {
|
||
// 拖动第一列
|
||
m_firstColumnWidth = newWidth;
|
||
} else if (m_dragColumnIndex >= 0 && m_dragColumnIndex < (int)m_columns.size()) {
|
||
m_columns[m_dragColumnIndex].Width = newWidth;
|
||
}
|
||
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
return;
|
||
}
|
||
|
||
// 检测鼠标是否在列边界上
|
||
int borderCol = HitTestColumnBorder(args.Location);
|
||
if (borderCol >= -1) { // -1 表示第一列边界
|
||
// 设置调整大小的光标
|
||
Style.Cursor = LoadCursor(Cursor::SIZEWE);
|
||
} else {
|
||
Style.Cursor = LoadCursor(Cursor::ARROW);
|
||
}
|
||
|
||
// 更新悬停状态
|
||
int row, col;
|
||
if (HitTestCell(args.Location, &row, &col)) {
|
||
if (m_hoverRow != row || m_hoverCol != col) {
|
||
m_hoverRow = row;
|
||
m_hoverCol = col;
|
||
Invalidate();
|
||
}
|
||
}
|
||
}
|
||
|
||
void TableView::OnMouseDown(const MouseEventArgs& args) {
|
||
__super::OnMouseDown(args);
|
||
|
||
if (args.Button != MouseButton::Left) {
|
||
return;
|
||
}
|
||
|
||
// 检查是否点击在编辑框上
|
||
if (m_editBox->IsVisible()) {
|
||
Rect editRect = m_editBox->GetRect();
|
||
if (editRect.Contains(args.Location)) {
|
||
m_editBox->SendEvent(MouseEventArgs(Event::OnMouseDown,
|
||
Point(args.Location.X - editRect.X, args.Location.Y - editRect.Y),
|
||
args.Button, args.ZDelta));
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 检查是否点击在滚动条上
|
||
if (HitTestVScrollBar(args.Location)) {
|
||
m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseDown,
|
||
Point(args.Location.X - m_vScrollBar.X(), args.Location.Y - m_vScrollBar.Y()),
|
||
args.Button, args.ZDelta));
|
||
return;
|
||
}
|
||
if (HitTestHScrollBar(args.Location)) {
|
||
m_hScrollBar.SendEvent(MouseEventArgs(Event::OnMouseDown,
|
||
Point(args.Location.X - m_hScrollBar.X(), args.Location.Y - m_hScrollBar.Y()),
|
||
args.Button, args.ZDelta));
|
||
return;
|
||
}
|
||
|
||
// 检测是否点击在列边界上
|
||
int borderCol = HitTestColumnBorder(args.Location);
|
||
if (borderCol >= -1) {
|
||
m_draggingColumn = true;
|
||
m_dragColumnIndex = borderCol;
|
||
m_dragStartX = args.Location.X;
|
||
if (borderCol == -1) {
|
||
m_dragStartWidth = m_firstColumnWidth;
|
||
} else {
|
||
m_dragStartWidth = m_columns[borderCol].Width;
|
||
}
|
||
return;
|
||
}
|
||
|
||
int row, col;
|
||
if (!HitTestCell(args.Location, &row, &col)) {
|
||
EndEdit(true);
|
||
return;
|
||
}
|
||
|
||
// 点击表头
|
||
if (row == -1) {
|
||
if (col == -1) {
|
||
// 点击第一列表头(全选)
|
||
if (m_firstColumnType == FirstColumnType::CheckBox) {
|
||
m_headerSelectAll = !m_headerSelectAll;
|
||
for (int i = 0; i < (int)m_rowChecked.size(); ++i) {
|
||
m_rowChecked[i] = m_headerSelectAll;
|
||
}
|
||
Invalidate();
|
||
}
|
||
} else {
|
||
// 点击数据列表头(排序)
|
||
DoSort(col);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 点击第一列
|
||
if (col == -1) {
|
||
if (m_firstColumnType == FirstColumnType::CheckBox && row < (int)m_rowChecked.size()) {
|
||
m_rowChecked[row] = !m_rowChecked[row];
|
||
|
||
// 更新全选状态
|
||
bool allChecked = true;
|
||
for (bool checked : m_rowChecked) {
|
||
if (!checked) {
|
||
allChecked = false;
|
||
break;
|
||
}
|
||
}
|
||
m_headerSelectAll = allChecked;
|
||
|
||
Invalidate();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 点击数据单元格
|
||
// 获取单元格类型
|
||
CellType cellType = CellType::TextBox;
|
||
if (col >= 0 && col < (int)m_columns.size()) {
|
||
cellType = m_columns[col].Type;
|
||
}
|
||
|
||
// CheckBox 和 ComboBox 单击就触发
|
||
if (cellType == CellType::CheckBox || cellType == CellType::ComboBox) {
|
||
BeginEdit(row, col);
|
||
m_lastClickTime = 0;
|
||
m_lastClickRow = -1;
|
||
m_lastClickCol = -1;
|
||
return;
|
||
}
|
||
|
||
// TextBox 类型需要双击才进入编辑
|
||
ULONGLONG currentTime = ::GetTickCount64();
|
||
if (currentTime - m_lastClickTime < 300 &&
|
||
m_lastClickRow == row && m_lastClickCol == col) {
|
||
// 双击,开始编辑
|
||
BeginEdit(row, col);
|
||
m_lastClickTime = 0; // 重置,避免三击触发
|
||
m_lastClickRow = -1;
|
||
m_lastClickCol = -1;
|
||
} else {
|
||
// 单击,记录时间和位置
|
||
m_lastClickTime = currentTime;
|
||
m_lastClickRow = row;
|
||
m_lastClickCol = col;
|
||
EndEdit(true);
|
||
}
|
||
}
|
||
|
||
void TableView::OnMouseUp(const MouseEventArgs& args) {
|
||
__super::OnMouseUp(args);
|
||
m_draggingColumn = false;
|
||
|
||
// 转发事件给编辑框
|
||
if (m_editBox->IsVisible()) {
|
||
Rect editRect = m_editBox->GetRect();
|
||
m_editBox->SendEvent(MouseEventArgs(Event::OnMouseUp,
|
||
Point(args.Location.X - editRect.X, args.Location.Y - editRect.Y),
|
||
args.Button, args.ZDelta));
|
||
}
|
||
|
||
// 转发事件给滚动条
|
||
if (m_vScrollBar.IsVisible()) {
|
||
m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseUp,
|
||
Point(args.Location.X - m_vScrollBar.X(), args.Location.Y - m_vScrollBar.Y()),
|
||
args.Button, args.ZDelta));
|
||
}
|
||
if (m_hScrollBar.IsVisible()) {
|
||
m_hScrollBar.SendEvent(MouseEventArgs(Event::OnMouseUp,
|
||
Point(args.Location.X - m_hScrollBar.X(), args.Location.Y - m_hScrollBar.Y()),
|
||
args.Button, args.ZDelta));
|
||
}
|
||
}
|
||
|
||
void TableView::OnMouseDoubleClick(const MouseEventArgs& args) {
|
||
__super::OnMouseDoubleClick(args);
|
||
|
||
int row, col;
|
||
if (HitTestCell(args.Location, &row, &col) && row >= 0 && col >= 0) {
|
||
BeginEdit(row, col);
|
||
}
|
||
}
|
||
|
||
void TableView::OnMouseWheel(const MouseEventArgs& args) {
|
||
__super::OnMouseWheel(args);
|
||
|
||
if (m_vScrollBar.IsVisible()) {
|
||
m_vScrollBar.SendEvent(MouseEventArgs(Event::OnMouseWheel, args.Location, args.Button, args.ZDelta));
|
||
}
|
||
}
|
||
|
||
void TableView::OnMouseLeave(const MouseEventArgs& args) {
|
||
__super::OnMouseLeave(args);
|
||
m_hoverRow = -1;
|
||
m_hoverCol = -1;
|
||
Style.Cursor = LoadCursor(Cursor::ARROW);
|
||
}
|
||
|
||
void TableView::OnSize(const SizeEventArgs& args) {
|
||
__super::OnSize(args);
|
||
RefreshScrollBars();
|
||
}
|
||
|
||
void TableView::OnKeyDown(const KeyboardEventArgs& args) {
|
||
__super::OnKeyDown(args);
|
||
|
||
// 如果编辑框可见,将键盘事件转发给编辑框
|
||
if (m_editBox->IsVisible()) {
|
||
m_editBox->SendEvent(args);
|
||
}
|
||
|
||
if (args.wParam == VK_ESCAPE) {
|
||
EndEdit(false);
|
||
} else if (args.wParam == VK_RETURN && !m_editing) {
|
||
// 如果不是多行编辑,回车可以结束编辑
|
||
}
|
||
}
|
||
|
||
void TableView::OnKeyChar(const KeyboardEventArgs& args) {
|
||
__super::OnKeyChar(args);
|
||
|
||
// 如果编辑框可见,将字符输入事件转发给编辑框
|
||
if (m_editBox->IsVisible()) {
|
||
m_editBox->SendEvent(args);
|
||
}
|
||
}
|
||
|
||
// ============ 公共接口实现 ============
|
||
|
||
void TableView::SetHeaders(const std::vector<UIString>& headers) {
|
||
m_columns.clear();
|
||
for (const auto& header : headers) {
|
||
ColumnInfo col;
|
||
col.HeaderText = header;
|
||
col.Width = 100;
|
||
m_columns.push_back(col);
|
||
}
|
||
|
||
// 更新现有数据的列数
|
||
for (auto& row : m_data) {
|
||
row.resize(m_columns.size());
|
||
}
|
||
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
|
||
int TableView::GetColumnCount() const {
|
||
return (int)m_columns.size();
|
||
}
|
||
|
||
void TableView::SetHeaderHeight(int height) {
|
||
m_headerHeight = (std::max)(20, height);
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
|
||
int TableView::GetHeaderHeight() const {
|
||
return m_headerHeight;
|
||
}
|
||
|
||
void TableView::SetFirstColumnType(FirstColumnType type) {
|
||
m_firstColumnType = type;
|
||
|
||
// 如果切换到 CheckBox 类型,初始化选中状态
|
||
if (type == FirstColumnType::CheckBox) {
|
||
m_rowChecked.resize(m_data.size(), false);
|
||
}
|
||
|
||
Invalidate();
|
||
}
|
||
|
||
FirstColumnType TableView::GetFirstColumnType() const {
|
||
return m_firstColumnType;
|
||
}
|
||
|
||
void TableView::SetFirstColumnWidth(int width) {
|
||
m_firstColumnWidth = (std::max)(20, width);
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
|
||
int TableView::GetFirstColumnWidth() const {
|
||
return m_firstColumnWidth;
|
||
}
|
||
|
||
void TableView::SetColumnWidth(int colIndex, int width) {
|
||
if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
|
||
m_columns[colIndex].Width = (std::max)(20, width);
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
int TableView::GetColumnWidth(int colIndex) const {
|
||
if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
|
||
return m_columns[colIndex].Width;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
const std::vector<int> TableView::GetColumnWidths() const {
|
||
std::vector<int> widths;
|
||
for (const auto& col : m_columns) {
|
||
widths.push_back(col.Width);
|
||
}
|
||
return widths;
|
||
}
|
||
|
||
void TableView::SetColumnType(int colIndex, CellType type) {
|
||
if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
|
||
m_columns[colIndex].Type = type;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
CellType TableView::GetColumnType(int colIndex) const {
|
||
if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
|
||
return m_columns[colIndex].Type;
|
||
}
|
||
return CellType::TextBox;
|
||
}
|
||
|
||
void TableView::SetColumnComboItems(int colIndex, const std::vector<UIString>& items) {
|
||
if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
|
||
m_columns[colIndex].ComboItems = items;
|
||
}
|
||
}
|
||
|
||
int TableView::GetRowCount() const {
|
||
return (int)m_data.size();
|
||
}
|
||
|
||
void TableView::AddRow() {
|
||
std::vector<CellData> newRow(m_columns.size());
|
||
m_data.push_back(newRow);
|
||
m_rowHeights.push_back(m_defaultRowHeight);
|
||
|
||
if (m_firstColumnType == FirstColumnType::CheckBox) {
|
||
m_rowChecked.push_back(false);
|
||
}
|
||
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::InsertRow(int rowIndex) {
|
||
if (rowIndex < 0) rowIndex = 0;
|
||
if (rowIndex > (int)m_data.size()) rowIndex = (int)m_data.size();
|
||
|
||
std::vector<CellData> newRow(m_columns.size());
|
||
m_data.insert(m_data.begin() + rowIndex, newRow);
|
||
m_rowHeights.insert(m_rowHeights.begin() + rowIndex, m_defaultRowHeight);
|
||
|
||
if (m_firstColumnType == FirstColumnType::CheckBox) {
|
||
m_rowChecked.insert(m_rowChecked.begin() + rowIndex, false);
|
||
}
|
||
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::RemoveRow(int rowIndex) {
|
||
if (rowIndex >= 0 && rowIndex < (int)m_data.size()) {
|
||
m_data.erase(m_data.begin() + rowIndex);
|
||
m_rowHeights.erase(m_rowHeights.begin() + rowIndex);
|
||
|
||
if (rowIndex < (int)m_rowChecked.size()) {
|
||
m_rowChecked.erase(m_rowChecked.begin() + rowIndex);
|
||
}
|
||
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
void TableView::ClearRows() {
|
||
m_data.clear();
|
||
m_rowHeights.clear();
|
||
m_rowChecked.clear();
|
||
m_headerSelectAll = false;
|
||
m_scrollOffsetX = 0;
|
||
m_scrollOffsetY = 0;
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::AddColumn(const UIString& headerText, int width) {
|
||
ColumnInfo col;
|
||
col.HeaderText = headerText;
|
||
col.Width = (std::max)(20, width);
|
||
m_columns.push_back(col);
|
||
|
||
// 为所有行添加新列的数据
|
||
for (auto& row : m_data) {
|
||
row.push_back(CellData());
|
||
}
|
||
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::InsertColumn(int colIndex, const UIString& headerText, int width) {
|
||
if (colIndex < 0) colIndex = 0;
|
||
if (colIndex > (int)m_columns.size()) colIndex = (int)m_columns.size();
|
||
|
||
ColumnInfo col;
|
||
col.HeaderText = headerText;
|
||
col.Width = (std::max)(20, width);
|
||
m_columns.insert(m_columns.begin() + colIndex, col);
|
||
|
||
// 为所有行插入新列的数据
|
||
for (auto& row : m_data) {
|
||
row.insert(row.begin() + colIndex, CellData());
|
||
}
|
||
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::RemoveColumn(int colIndex) {
|
||
if (colIndex >= 0 && colIndex < (int)m_columns.size()) {
|
||
m_columns.erase(m_columns.begin() + colIndex);
|
||
|
||
// 从所有行删除该列的数据
|
||
for (auto& row : m_data) {
|
||
if (colIndex < (int)row.size()) {
|
||
row.erase(row.begin() + colIndex);
|
||
}
|
||
}
|
||
|
||
RefreshScrollBars();
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
int TableView::GetRowHeight(int rowIndex) const {
|
||
if (rowIndex >= 0 && rowIndex < (int)m_rowHeights.size()) {
|
||
return m_rowHeights[rowIndex];
|
||
}
|
||
return m_defaultRowHeight;
|
||
}
|
||
|
||
const std::vector<int> TableView::GetRowHeights() const {
|
||
return m_rowHeights;
|
||
}
|
||
|
||
void TableView::SetDefaultRowHeight(int height) {
|
||
m_defaultRowHeight = (std::max)(20, height);
|
||
}
|
||
|
||
void TableView::SetData(int row, int col, const UIString& value) {
|
||
// 自动扩展行
|
||
while (row >= (int)m_data.size()) {
|
||
AddRow();
|
||
}
|
||
|
||
// 确保列足够
|
||
if (col >= 0 && col < (int)m_columns.size()) {
|
||
if (col >= (int)m_data[row].size()) {
|
||
m_data[row].resize(m_columns.size());
|
||
}
|
||
m_data[row][col].Text = value;
|
||
UpdateRowHeight(row);
|
||
|
||
if (CellValueChanged) {
|
||
CellValueChanged(row, col, value);
|
||
}
|
||
}
|
||
}
|
||
|
||
UIString TableView::GetData(int row, int col) const {
|
||
if (row >= 0 && row < (int)m_data.size() &&
|
||
col >= 0 && col < (int)m_data[row].size()) {
|
||
return m_data[row][col].Text;
|
||
}
|
||
return UIString();
|
||
}
|
||
|
||
void TableView::SetRowData(int row, const std::vector<UIString>& values) {
|
||
// 自动扩展行
|
||
while (row >= (int)m_data.size()) {
|
||
AddRow();
|
||
}
|
||
|
||
for (int col = 0; col < (int)values.size() && col < (int)m_columns.size(); ++col) {
|
||
if (col >= (int)m_data[row].size()) {
|
||
m_data[row].resize(m_columns.size());
|
||
}
|
||
m_data[row][col].Text = values[col];
|
||
}
|
||
|
||
UpdateRowHeight(row);
|
||
}
|
||
|
||
std::vector<UIString> TableView::GetRowData(int row) const {
|
||
std::vector<UIString> result;
|
||
if (row >= 0 && row < (int)m_data.size()) {
|
||
for (const auto& cell : m_data[row]) {
|
||
result.push_back(cell.Text);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
std::vector<UIString> TableView::GetColData(int col) const {
|
||
std::vector<UIString> result;
|
||
if (col >= 0 && col < (int)m_columns.size()) {
|
||
for (const auto& row : m_data) {
|
||
if (col < (int)row.size()) {
|
||
result.push_back(row[col].Text);
|
||
} else {
|
||
result.push_back(UIString());
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
void TableView::SetAllData(const std::vector<std::vector<UIString>>& data) {
|
||
ClearRows();
|
||
|
||
for (const auto& rowData : data) {
|
||
AddRow();
|
||
int row = (int)m_data.size() - 1;
|
||
for (int col = 0; col < (int)rowData.size() && col < (int)m_columns.size(); ++col) {
|
||
m_data[row][col].Text = rowData[col];
|
||
}
|
||
UpdateRowHeight(row);
|
||
}
|
||
}
|
||
|
||
std::vector<std::vector<UIString>> TableView::GetAllData() const {
|
||
std::vector<std::vector<UIString>> result;
|
||
for (int row = 0; row < (int)m_data.size(); ++row) {
|
||
result.push_back(GetRowData(row));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
void TableView::SetCellChecked(int row, int col, bool checked) {
|
||
if (row >= 0 && row < (int)m_data.size() &&
|
||
col >= 0 && col < (int)m_data[row].size()) {
|
||
m_data[row][col].Checked = checked;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
bool TableView::GetCellChecked(int row, int col) const {
|
||
if (row >= 0 && row < (int)m_data.size() &&
|
||
col >= 0 && col < (int)m_data[row].size()) {
|
||
return m_data[row][col].Checked;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void TableView::SetCellComboIndex(int row, int col, int index) {
|
||
if (row >= 0 && row < (int)m_data.size() &&
|
||
col >= 0 && col < (int)m_data[row].size()) {
|
||
m_data[row][col].ComboIndex = index;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
int TableView::GetCellComboIndex(int row, int col) const {
|
||
if (row >= 0 && row < (int)m_data.size() &&
|
||
col >= 0 && col < (int)m_data[row].size()) {
|
||
return m_data[row][col].ComboIndex;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
void TableView::SetRowChecked(int row, bool checked) {
|
||
if (m_firstColumnType == FirstColumnType::CheckBox &&
|
||
row >= 0 && row < (int)m_rowChecked.size()) {
|
||
m_rowChecked[row] = checked;
|
||
|
||
// 更新全选状态
|
||
bool allChecked = true;
|
||
for (bool c : m_rowChecked) {
|
||
if (!c) {
|
||
allChecked = false;
|
||
break;
|
||
}
|
||
}
|
||
m_headerSelectAll = allChecked;
|
||
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
bool TableView::GetRowChecked(int row) const {
|
||
if (row >= 0 && row < (int)m_rowChecked.size()) {
|
||
return m_rowChecked[row];
|
||
}
|
||
return false;
|
||
}
|
||
|
||
std::vector<int> TableView::GetCheckedRows() const {
|
||
std::vector<int> result;
|
||
for (int i = 0; i < (int)m_rowChecked.size(); ++i) {
|
||
if (m_rowChecked[i]) {
|
||
result.push_back(i);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
void TableView::SelectAll() {
|
||
if (m_firstColumnType == FirstColumnType::CheckBox) {
|
||
m_headerSelectAll = true;
|
||
for (int i = 0; i < (int)m_rowChecked.size(); ++i) {
|
||
m_rowChecked[i] = true;
|
||
}
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
void TableView::DeselectAll() {
|
||
if (m_firstColumnType == FirstColumnType::CheckBox) {
|
||
m_headerSelectAll = false;
|
||
for (int i = 0; i < (int)m_rowChecked.size(); ++i) {
|
||
m_rowChecked[i] = false;
|
||
}
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
void TableView::SetCellBorderSize(int size) {
|
||
m_cellBorderSize = (std::max)(0, size);
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::SetCellBorderStyle(StrokeStyle style) {
|
||
m_cellBorderStyle = style;
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::SetCellBorderColor(const Color& color) {
|
||
m_cellBorderColor = color;
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::SetCellBackColor(const Color& color) {
|
||
m_cellBackColor = color;
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::SetCellForeColor(const Color& color) {
|
||
m_cellForeColor = color;
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::SetCellFontSize(int size) {
|
||
m_cellFontSize = (std::max)(8, size);
|
||
// 同步更新编辑框字体
|
||
m_editBox->Style.FontSize = m_cellFontSize;
|
||
// 更新所有行高
|
||
for (int i = 0; i < (int)m_data.size(); ++i) {
|
||
UpdateRowHeight(i);
|
||
}
|
||
}
|
||
|
||
void TableView::SetCellFontFamily(const std::wstring& fontFamily) {
|
||
m_cellFontFamily = fontFamily;
|
||
// 同步更新编辑框字体
|
||
m_editBox->Style.FontFamily = m_cellFontFamily;
|
||
// 更新所有行高
|
||
for (int i = 0; i < (int)m_data.size(); ++i) {
|
||
UpdateRowHeight(i);
|
||
}
|
||
}
|
||
|
||
void TableView::SetCellStyle(int row, int col, const CellStyle& style) {
|
||
if (row >= 0 && row < (int)m_data.size() &&
|
||
col >= 0 && col < (int)m_data[row].size()) {
|
||
m_data[row][col].Style = style;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
CellStyle TableView::GetCellStyle(int row, int col) const {
|
||
if (row >= 0 && row < (int)m_data.size() &&
|
||
col >= 0 && col < (int)m_data[row].size()) {
|
||
return m_data[row][col].Style;
|
||
}
|
||
return CellStyle();
|
||
}
|
||
|
||
void TableView::ResetCellStyle(int row, int col) {
|
||
if (row >= 0 && row < (int)m_data.size() &&
|
||
col >= 0 && col < (int)m_data[row].size()) {
|
||
m_data[row][col].Style.Reset();
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
void TableView::SetHeaderBackColor(const Color& color) {
|
||
m_headerBackColor = color;
|
||
Invalidate();
|
||
}
|
||
|
||
void TableView::SetHeaderForeColor(const Color& color) {
|
||
m_headerForeColor = color;
|
||
Invalidate();
|
||
}
|
||
|
||
int TableView::GetHoverRow() const {
|
||
return m_hoverRow;
|
||
}
|
||
|
||
int TableView::GetHoverCol() const {
|
||
return m_hoverCol;
|
||
}
|
||
|
||
void TableView::GetHoverCell(int* outRow, int* outCol) const {
|
||
if (outRow) *outRow = m_hoverRow;
|
||
if (outCol) *outCol = m_hoverCol;
|
||
}
|
||
|
||
const Size& TableView::GetContentSize() {
|
||
// 计算内容大小供滚动条使用
|
||
m_contentSize.Width = GetContentWidth();
|
||
m_contentSize.Height = GetContentHeight();
|
||
return m_contentSize;
|
||
}
|
||
|
||
ScrollBar* TableView::GetVScrollBar() {
|
||
return &m_vScrollBar;
|
||
}
|
||
|
||
ScrollBar* TableView::GetHScrollBar() {
|
||
return &m_hScrollBar;
|
||
}
|
||
|
||
void TableView::SetAttribute(const UIString& key, const UIString& value) {
|
||
__super::SetAttribute(key, value);
|
||
|
||
// 辅助函数:解析整数
|
||
auto parseInt = [](const UIString& v) -> int {
|
||
UIString tmp = v;
|
||
ui_text::Replace(&tmp, "px", "");
|
||
tmp = tmp.trim();
|
||
return std::atoi(tmp.c_str());
|
||
};
|
||
|
||
// 辅助函数:解析颜色
|
||
auto parseColor = [](const UIString& v) -> Color {
|
||
bool isGood = false;
|
||
return Color::Make(v, &isGood);
|
||
};
|
||
|
||
do {
|
||
// 表头高度
|
||
if (key == "header-height" || key == "headerheight") {
|
||
SetHeaderHeight(parseInt(value));
|
||
break;
|
||
}
|
||
|
||
// 第一列类型
|
||
if (key == "first-column-type" || key == "firstcolumntype") {
|
||
if (value == "index" || value == "number") {
|
||
SetFirstColumnType(FirstColumnType::Index);
|
||
} else if (value == "checkbox" || value == "check") {
|
||
SetFirstColumnType(FirstColumnType::CheckBox);
|
||
} else {
|
||
SetFirstColumnType(FirstColumnType::TextBox);
|
||
}
|
||
break;
|
||
}
|
||
|
||
// 第一列宽度
|
||
if (key == "first-column-width" || key == "firstcolumnwidth") {
|
||
SetFirstColumnWidth(parseInt(value));
|
||
break;
|
||
}
|
||
|
||
// 默认行高
|
||
if (key == "default-row-height" || key == "defaultrowheight" || key == "row-height" || key == "rowheight") {
|
||
SetDefaultRowHeight(parseInt(value));
|
||
break;
|
||
}
|
||
|
||
// 单元格边框大小
|
||
if (key == "cell-border-size" || key == "cellbordersize" || key == "border-size" || key == "bordersize") {
|
||
SetCellBorderSize(parseInt(value));
|
||
break;
|
||
}
|
||
|
||
// 单元格边框样式
|
||
if (key == "cell-border-style" || key == "cellborderstyle" || key == "border-style" || key == "borderstyle") {
|
||
if (value == "solid") {
|
||
SetCellBorderStyle(StrokeStyle::Solid);
|
||
} else if (value == "dash" || value == "dashed") {
|
||
SetCellBorderStyle(StrokeStyle::Dash);
|
||
} else if (value == "none") {
|
||
SetCellBorderStyle(StrokeStyle::None);
|
||
}
|
||
break;
|
||
}
|
||
|
||
// 单元格边框颜色
|
||
if (key == "cell-border-color" || key == "cellbordercolor" || key == "border-color" || key == "bordercolor") {
|
||
SetCellBorderColor(parseColor(value));
|
||
break;
|
||
}
|
||
|
||
// 单元格背景颜色
|
||
if (key == "cell-back-color" || key == "cellbackcolor" || key == "cell-background" || key == "cellbackground") {
|
||
SetCellBackColor(parseColor(value));
|
||
break;
|
||
}
|
||
|
||
// 单元格前景颜色(文字颜色)
|
||
if (key == "cell-fore-color" || key == "cellforecolor" || key == "cell-color" || key == "cellcolor") {
|
||
SetCellForeColor(parseColor(value));
|
||
break;
|
||
}
|
||
|
||
// 单元格字体大小
|
||
if (key == "cell-font-size" || key == "cellfontsize") {
|
||
SetCellFontSize(parseInt(value));
|
||
break;
|
||
}
|
||
|
||
// 单元格字体
|
||
if (key == "cell-font-family" || key == "cellfontfamily" || key == "cell-font" || key == "cellfont") {
|
||
SetCellFontFamily(value.unicode());
|
||
break;
|
||
}
|
||
|
||
// 表头背景颜色
|
||
if (key == "header-back-color" || key == "headerbackcolor" || key == "header-background" || key == "headerbackground") {
|
||
SetHeaderBackColor(parseColor(value));
|
||
break;
|
||
}
|
||
|
||
// 表头前景颜色(文字颜色)
|
||
if (key == "header-fore-color" || key == "headerforecolor" || key == "header-color" || key == "headercolor") {
|
||
SetHeaderForeColor(parseColor(value));
|
||
break;
|
||
}
|
||
|
||
// 选中行背景颜色
|
||
if (key == "selected-row-back-color" || key == "selectedrowbackcolor" || key == "selected-color" || key == "selectedcolor") {
|
||
SelectedRowBackColor = parseColor(value);
|
||
Invalidate();
|
||
break;
|
||
}
|
||
|
||
// 表头文字(逗号分隔)
|
||
if (key == "headers" || key == "columns") {
|
||
auto headers = value.split(",");
|
||
std::vector<UIString> headerList;
|
||
for (auto& h : headers) {
|
||
headerList.push_back(h.trim());
|
||
}
|
||
SetHeaders(headerList);
|
||
break;
|
||
}
|
||
|
||
} while (false);
|
||
}
|
||
|
||
}
|