feat: add a new awesome feature
This commit is contained in:
357
src/Button.cpp
357
src/Button.cpp
@@ -17,6 +17,116 @@ Button::Button(int x, int y, int width, int height, const std::string text, COLO
|
||||
{
|
||||
initButton(text, mode, shape, ct, cf, ch);
|
||||
}
|
||||
// ====== GBK/MBCS 安全:字符边界与省略号裁切 ======
|
||||
static inline int gbk_char_len(const std::string& s, size_t i)
|
||||
{
|
||||
unsigned char b = (unsigned char)s[i];
|
||||
if (b <= 0x7F) return 1; // ASCII
|
||||
if (b >= 0x81 && b <= 0xFE && i + 1 < s.size())
|
||||
{
|
||||
unsigned char b2 = (unsigned char)s[i + 1];
|
||||
if (b2 >= 0x40 && b2 <= 0xFE && b2 != 0x7F) return 2; // 合法双字节
|
||||
}
|
||||
return 1; // 容错
|
||||
}
|
||||
|
||||
static inline void rtrim_spaces_gbk(std::string& s)
|
||||
{
|
||||
while (!s.empty() && s.back() == ' ') s.pop_back(); // ASCII 空格
|
||||
while (s.size() >= 2)
|
||||
{ // 全角空格 A1 A1
|
||||
unsigned char a = (unsigned char)s[s.size() - 2];
|
||||
unsigned char b = (unsigned char)s[s.size() - 1];
|
||||
if (a == 0xA1 && b == 0xA1) s.resize(s.size() - 2);
|
||||
else break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool is_ascii_only(const std::string& s)
|
||||
{
|
||||
for (unsigned char c : s) if (c > 0x7F) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool is_word_boundary_char(unsigned char c)
|
||||
{
|
||||
return c == ' ' || c == '-' || c == '_' || c == '/' || c == '\\' || c == '.' || c == ':';
|
||||
}
|
||||
|
||||
// 英文优先策略:优先在“词边界”回退,再退化到逐字符;省略号为 "..."
|
||||
static std::string ellipsize_ascii_pref(const std::string& text, int maxW)
|
||||
{
|
||||
if (maxW <= 0) return "";
|
||||
if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text;
|
||||
|
||||
const std::string ell = "...";
|
||||
int ellW = textwidth(LPCTSTR(ell.c_str()));
|
||||
if (ellW > maxW)
|
||||
{ // 连 ... 都放不下
|
||||
std::string e = ell;
|
||||
while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back();
|
||||
return e; // 可能是 ".."、"." 或 ""
|
||||
}
|
||||
const int limit = maxW - ellW;
|
||||
|
||||
// 先找到能放下的最长前缀
|
||||
size_t i = 0, lastFit = 0;
|
||||
while (i < text.size())
|
||||
{
|
||||
int clen = gbk_char_len(text, i);
|
||||
size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen;
|
||||
int w = textwidth(LPCTSTR(text.substr(0, j).c_str()));
|
||||
if (w <= limit) { lastFit = j; i = j; }
|
||||
else break;
|
||||
}
|
||||
if (lastFit == 0) return ell;
|
||||
|
||||
// 在已适配前缀范围内,向左找最近的词边界
|
||||
size_t cutPos = lastFit;
|
||||
for (size_t k = lastFit; k > 0; --k)
|
||||
{
|
||||
unsigned char c = (unsigned char)text[k - 1];
|
||||
if (c <= 0x7F && is_word_boundary_char(c)) { cutPos = k - 1; break; }
|
||||
}
|
||||
|
||||
std::string head = text.substr(0, cutPos);
|
||||
rtrim_spaces_gbk(head);
|
||||
head += ell;
|
||||
return head;
|
||||
}
|
||||
|
||||
// 中文优先策略:严格逐“字符”(1/2字节)回退;省略号用全角 "…"
|
||||
static std::string ellipsize_cjk_pref(const std::string& text, int maxW, const char* ellipsis = "…")
|
||||
{
|
||||
if (maxW <= 0) return "";
|
||||
if (textwidth(LPCTSTR(text.c_str())) <= maxW) return text;
|
||||
|
||||
std::string ell = ellipsis ? ellipsis : "…";
|
||||
int ellW = textwidth(LPCTSTR(ell.c_str()));
|
||||
if (ellW > maxW)
|
||||
{ // 连省略号都放不下
|
||||
std::string e = ell;
|
||||
while (!e.empty() && textwidth(LPCTSTR(e.c_str())) > maxW) e.pop_back();
|
||||
return e;
|
||||
}
|
||||
const int limit = maxW - ellW;
|
||||
|
||||
size_t i = 0, lastFit = 0;
|
||||
while (i < text.size())
|
||||
{
|
||||
int clen = gbk_char_len(text, i);
|
||||
size_t j = text.size() < i + (size_t)clen ? text.size() : i + (size_t)clen;
|
||||
int w = textwidth(LPCTSTR(text.substr(0, j).c_str()));
|
||||
if (w <= limit) { lastFit = j; i = j; }
|
||||
else break;
|
||||
}
|
||||
if (lastFit == 0) return ell;
|
||||
|
||||
std::string head = text.substr(0, lastFit);
|
||||
rtrim_spaces_gbk(head);
|
||||
head += ell;
|
||||
return head;
|
||||
}
|
||||
|
||||
void Button::initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch)
|
||||
{
|
||||
@@ -28,6 +138,14 @@ void Button::initButton(const std::string text, StellarX::ButtonMode mode, Stell
|
||||
this->buttonHoverColor = ch;
|
||||
this->click = false;
|
||||
this->hover = false;
|
||||
|
||||
// === Tooltip 默认:文本=按钮文本;白底黑字;不透明;用当前按钮字体样式 ===
|
||||
tipTextClick = tipTextOn = tipTextOff = this->text;
|
||||
tipLabel.setText(tipTextClick);
|
||||
tipLabel.setTextColor(RGB(167, 170, 172));
|
||||
tipLabel.setTextBkColor(RGB(255, 255, 255));
|
||||
tipLabel.setTextdisap(false);
|
||||
tipLabel.textStyle = this->textStyle; // 复用按钮字体样式
|
||||
}
|
||||
|
||||
|
||||
@@ -40,103 +158,122 @@ Button::~Button()
|
||||
|
||||
void Button::draw()
|
||||
{
|
||||
if (dirty)
|
||||
if (dirty && show)
|
||||
{
|
||||
//保存当前样式和颜色
|
||||
saveStyle();
|
||||
|
||||
if (StellarX::ButtonMode::DISABLED == mode) //设置禁用按钮色
|
||||
{
|
||||
setfillcolor(RGB(96, 96, 96));
|
||||
textStyle.bStrikeOut = 1;
|
||||
setfillcolor(DISABLEDCOLOUR);
|
||||
textStyle.bStrikeOut = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 点击状态优先级最高,然后是悬停状态,最后是默认状态
|
||||
if (click)
|
||||
setfillcolor(buttonTrueColor);
|
||||
else if (hover)
|
||||
setfillcolor(buttonHoverColor);
|
||||
else
|
||||
setfillcolor(buttonFalseColor);
|
||||
COLORREF col = click ? buttonTrueColor : (hover ? buttonHoverColor : buttonFalseColor);
|
||||
setfillcolor(col);
|
||||
}
|
||||
|
||||
//
|
||||
//设置字体背景色透明
|
||||
setbkmode(TRANSPARENT);
|
||||
//边框颜色
|
||||
setlinecolor(buttonBorderColor);
|
||||
if (this->textStyle != oldStyle)
|
||||
{
|
||||
//设置字体颜色
|
||||
settextcolor(textStyle.color);
|
||||
|
||||
//设置字体样式
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
||||
}
|
||||
//设置按钮填充模式
|
||||
setfillstyle((int)buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
|
||||
//设置字体颜色
|
||||
settextcolor(textStyle.color);
|
||||
//设置字体样式
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||
|
||||
if (needCutText)
|
||||
cutButtonText();
|
||||
|
||||
//获取字符串像素高度和宽度
|
||||
if ((this->oldtext_width != this->text_width || this->oldtext_height != this->text_height)
|
||||
|| (-1 == oldtext_width && oldtext_height == -1))
|
||||
{
|
||||
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
||||
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
||||
if(isUseCutText)
|
||||
{
|
||||
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->cutText.c_str()));
|
||||
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->cutText.c_str()));
|
||||
}
|
||||
else
|
||||
{
|
||||
this->oldtext_width = this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
||||
this->oldtext_height = this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//设置按钮填充模式
|
||||
setfillstyle((int)buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
|
||||
//根据按钮形状绘制
|
||||
switch (shape)
|
||||
{
|
||||
case StellarX::ControlShape::RECTANGLE:
|
||||
fillrectangle(x, y, x + width, y + height);//有边框填充矩形
|
||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
case StellarX::ControlShape::RECTANGLE://有边框填充矩形
|
||||
fillrectangle(x, y, x + width, y + height);
|
||||
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||
:outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::B_RECTANGLE:
|
||||
solidrectangle(x, y, x + width, y + height);//无边框填充矩形
|
||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
case StellarX::ControlShape::B_RECTANGLE://无边框填充矩形
|
||||
solidrectangle(x, y, x + width, y + height);
|
||||
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||
:outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||
fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//有边框填充圆角矩形
|
||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
case StellarX::ControlShape::ROUND_RECTANGLE://有边框填充圆角矩形
|
||||
fillroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);
|
||||
isUseCutText? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||
:outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||
solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);//无边框填充圆角矩形
|
||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
case StellarX::ControlShape::B_ROUND_RECTANGLE://无边框填充圆角矩形
|
||||
solidroundrect(x, y, x + width, y + height, rouRectangleSize.ROUND_RECTANGLEwidth, rouRectangleSize.ROUND_RECTANGLEheight);
|
||||
isUseCutText? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||
:outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::CIRCLE:
|
||||
fillcircle(x + width / 2, y + height / 2, min(width, height) / 2);//有边框填充圆形
|
||||
outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(text.c_str()));
|
||||
case StellarX::ControlShape::CIRCLE://有边框填充圆形
|
||||
fillcircle(x + width / 2, y + height / 2, min(width, height) / 2);
|
||||
isUseCutText? outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(cutText.c_str()))
|
||||
:outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::B_CIRCLE:
|
||||
solidcircle(x + width / 2, y + height / 2, min(width, height) / 2);//无边框填充圆形
|
||||
outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(text.c_str()));
|
||||
case StellarX::ControlShape::B_CIRCLE://无边框填充圆形
|
||||
solidcircle(x + width / 2, y + height / 2, min(width, height) / 2);
|
||||
isUseCutText ? outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(cutText.c_str()))
|
||||
:outtextxy(x + width / 2 - text_width / 2, y + height / 2 - text_height / 2, LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::ELLIPSE:
|
||||
fillellipse(x, y, x + width, y + height);//有边框填充椭圆
|
||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
case StellarX::ControlShape::ELLIPSE://有边框填充椭圆
|
||||
fillellipse(x, y, x + width, y + height);
|
||||
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||
:outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
case StellarX::ControlShape::B_ELLIPSE:
|
||||
solidellipse(x, y, x + width, y + height);//无边框填充椭圆
|
||||
outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
case StellarX::ControlShape::B_ELLIPSE://无边框填充椭圆
|
||||
solidellipse(x, y, x + width, y + height);
|
||||
isUseCutText ? outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(cutText.c_str()))
|
||||
:outtextxy((x + (width - text_width) / 2), (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
}
|
||||
|
||||
restoreStyle();//恢复默认字体样式和颜色
|
||||
|
||||
dirty = false; //标记按钮不需要重绘
|
||||
|
||||
}
|
||||
}
|
||||
// 处理鼠标事件,检测点击和悬停状态
|
||||
// 根据按钮模式和形状进行不同的处理
|
||||
bool Button::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
if (!show)
|
||||
return false;
|
||||
|
||||
bool oldHover = hover;
|
||||
bool oldClick = click;
|
||||
bool consume = false;//是否消耗事件
|
||||
|
||||
// 记录鼠标位置(用于tip定位)
|
||||
if (msg.message == WM_MOUSEMOVE)
|
||||
{
|
||||
lastMouseX = msg.x;
|
||||
lastMouseY = msg.y;
|
||||
}
|
||||
// 检测悬停状态(根据不同形状)
|
||||
switch (shape)
|
||||
{
|
||||
@@ -159,6 +296,7 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
// 处理鼠标点击事件
|
||||
if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED)
|
||||
{
|
||||
|
||||
if (mode == StellarX::ButtonMode::NORMAL)
|
||||
{
|
||||
click = true;
|
||||
@@ -174,12 +312,14 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
// TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。
|
||||
else if (msg.message == WM_LBUTTONUP && hover && mode != StellarX::ButtonMode::DISABLED)
|
||||
{
|
||||
hideTooltip(); // 隐藏悬停提示
|
||||
if (mode == StellarX::ButtonMode::NORMAL && click)
|
||||
{
|
||||
if (onClickCallback) onClickCallback();
|
||||
click = false;
|
||||
dirty = true;
|
||||
consume = true;
|
||||
hideTooltip();
|
||||
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
@@ -190,6 +330,8 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
else if (!click && onToggleOffCallback) onToggleOffCallback();
|
||||
dirty = true;
|
||||
consume = true;
|
||||
refreshTooltipTextForState();
|
||||
hideTooltip();
|
||||
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
@@ -204,22 +346,61 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
dirty = true;
|
||||
}
|
||||
else if (hover != oldHover)
|
||||
{
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (tipEnabled)
|
||||
{
|
||||
if (hover && !oldHover)
|
||||
{
|
||||
// 刚刚进入悬停:开计时,暂不显示
|
||||
tipHoverTick = GetTickCount64();
|
||||
tipVisible = false;
|
||||
}
|
||||
if (!hover && oldHover)
|
||||
{
|
||||
// 刚移出:立即隐藏
|
||||
hideTooltip();
|
||||
}
|
||||
if (hover && !tipVisible)
|
||||
{
|
||||
// 到点就显示
|
||||
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
|
||||
{
|
||||
tipVisible = true;
|
||||
|
||||
// 定位(跟随鼠标 or 相对按钮)
|
||||
int tipX = tipFollowCursor ? (lastMouseX + tipOffsetX) : lastMouseX;
|
||||
int tipY = tipFollowCursor ? (lastMouseY + tipOffsetY) : y + height;
|
||||
// 设置文本(用户可能动态改了提示文本
|
||||
if (tipUserOverride)
|
||||
{
|
||||
if (mode == StellarX::ButtonMode::NORMAL)
|
||||
tipLabel.setText(tipTextClick);
|
||||
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
||||
}
|
||||
else
|
||||
if (mode == StellarX::ButtonMode::TOGGLE)
|
||||
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
||||
// 设置位置
|
||||
tipLabel.setX(tipX);
|
||||
tipLabel.setY(tipY);
|
||||
// 标记需要绘制
|
||||
tipLabel.setDirty(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果状态发生变化,标记需要重绘
|
||||
if (hover != oldHover || click != oldClick)
|
||||
{
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// 如果需要重绘,立即执行
|
||||
if (dirty)
|
||||
{
|
||||
draw();
|
||||
}
|
||||
if(tipEnabled && tipVisible)
|
||||
tipLabel.draw();
|
||||
return consume;
|
||||
}
|
||||
|
||||
@@ -304,6 +485,9 @@ void Button::setButtonText(const char* text)
|
||||
this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
||||
this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
||||
this->dirty = true;
|
||||
this->needCutText = true;
|
||||
if (!tipUserOverride)
|
||||
tipTextClick = tipTextOn = tipTextOff = text;
|
||||
}
|
||||
|
||||
void Button::setButtonText(std::string text)
|
||||
@@ -312,12 +496,16 @@ void Button::setButtonText(std::string text)
|
||||
this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
||||
this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
||||
this->dirty = true; // 标记需要重绘
|
||||
this->needCutText = true;
|
||||
if (!tipUserOverride)
|
||||
tipTextClick = tipTextOn = tipTextOff = text;
|
||||
}
|
||||
|
||||
void Button::setButtonShape(StellarX::ControlShape shape)
|
||||
{
|
||||
this->shape = shape;
|
||||
this->dirty = true;
|
||||
this->needCutText = true;
|
||||
}
|
||||
|
||||
//允许通过外部函数修改按钮的点击状态,并执行相应的回调函数
|
||||
@@ -329,6 +517,7 @@ void Button::setButtonClick(BOOL click)
|
||||
{
|
||||
if (onClickCallback) onClickCallback();
|
||||
dirty = true;
|
||||
hideTooltip();
|
||||
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
@@ -337,6 +526,8 @@ void Button::setButtonClick(BOOL click)
|
||||
if (click && onToggleOnCallback) onToggleOnCallback();
|
||||
else if (!click && onToggleOffCallback) onToggleOffCallback();
|
||||
dirty = true;
|
||||
refreshTooltipTextForState();
|
||||
hideTooltip();
|
||||
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
@@ -405,15 +596,6 @@ int Button::getButtonHeight() const
|
||||
return this->height;
|
||||
}
|
||||
|
||||
int Button::getButtonX() const
|
||||
{
|
||||
return this->x;
|
||||
}
|
||||
|
||||
int Button::getButtonY() const
|
||||
{
|
||||
return this->y;
|
||||
}
|
||||
|
||||
|
||||
bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
|
||||
@@ -442,4 +624,51 @@ bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, i
|
||||
return false;
|
||||
}
|
||||
|
||||
void Button::cutButtonText()
|
||||
{
|
||||
const int contentW = 1 > this->width - 2 * padX ? 1 : this->width - 2 * padX;
|
||||
// 放得下:不截断,直接用原文
|
||||
if (textwidth(LPCTSTR(this->text.c_str())) <= contentW) {
|
||||
isUseCutText = false;
|
||||
needCutText = false;
|
||||
cutText.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// 放不下:按语言偏好裁切(ASCII→词边界;CJK→逐字符,不撕裂双字节)
|
||||
if (is_ascii_only(this->text))
|
||||
{
|
||||
cutText = ellipsize_ascii_pref(this->text, contentW); // "..."
|
||||
}
|
||||
else
|
||||
{
|
||||
cutText = ellipsize_cjk_pref(this->text, contentW, "…"); // 全角省略号
|
||||
|
||||
}
|
||||
isUseCutText = true;
|
||||
needCutText = false;
|
||||
|
||||
}
|
||||
|
||||
void Button::hideTooltip()
|
||||
{
|
||||
if (tipVisible)
|
||||
{
|
||||
tipVisible = false;
|
||||
tipLabel.hide(); // 还原快照+作废,防止残影
|
||||
tipHoverTick = GetTickCount64(); // 重置计时基线
|
||||
}
|
||||
}
|
||||
|
||||
void Button::refreshTooltipTextForState()
|
||||
{
|
||||
if (!tipUserOverride) return;
|
||||
if (tipUserOverride) return; // 用户显式设置过 tipText,保持不变
|
||||
if(mode==StellarX::ButtonMode::NORMAL)
|
||||
tipLabel.setText(tipTextClick);
|
||||
else if(mode==StellarX::ButtonMode::TOGGLE)
|
||||
tipLabel.setText(click ? tipTextOn : tipTextOff);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
#include "Canvas.h"
|
||||
|
||||
Canvas::Canvas()
|
||||
:Control(0, 0, 100, 100) {}
|
||||
|
||||
Canvas::Canvas(int x, int y, int width, int height)
|
||||
:Control(x, y, width, height) {}
|
||||
|
||||
void Canvas::clearAllControls()
|
||||
{
|
||||
controls.clear();
|
||||
}
|
||||
|
||||
Canvas::Canvas()
|
||||
:Control(0,0,100,100)
|
||||
{
|
||||
}
|
||||
|
||||
Canvas::Canvas(int x, int y, int width, int height)
|
||||
:Control(x, y, width, height) {}
|
||||
|
||||
void Canvas::draw()
|
||||
{
|
||||
if (!dirty)return;
|
||||
if (!dirty && !show)return;
|
||||
saveStyle();
|
||||
|
||||
setlinecolor(canvasBorderClor);//设置线色
|
||||
@@ -41,8 +40,10 @@ void Canvas::draw()
|
||||
}
|
||||
// 绘制所有子控件
|
||||
for (auto& control : controls)
|
||||
{
|
||||
control->setDirty(true);
|
||||
control->draw();
|
||||
|
||||
}
|
||||
|
||||
restoreStyle();
|
||||
dirty = false; //标记画布不需要重绘
|
||||
@@ -50,10 +51,12 @@ void Canvas::draw()
|
||||
|
||||
bool Canvas::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
for (auto& control : controls)
|
||||
if (control->handleEvent(msg))
|
||||
return true;//事件被消费短路传递,立即返回true 否则返回false
|
||||
return false;
|
||||
if(!show)return false;
|
||||
|
||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||
if ((*it)->handleEvent(msg))
|
||||
return true; // 事件被消费短路传递,立即返回true 否则返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
void Canvas::addControl(std::unique_ptr<Control> control)
|
||||
@@ -114,3 +117,5 @@ void Canvas::setLinewidth(int width)
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -64,3 +64,51 @@ void Control::restoreStyle()
|
||||
setlinecolor(*currentBorderColor);
|
||||
setfillstyle(BS_SOLID);//恢复填充
|
||||
}
|
||||
|
||||
void Control::saveBackground(int x, int y, int w, int h)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
saveBkX = x; saveBkY = y; saveWidth = w; saveHeight = h;
|
||||
|
||||
if (saveBkImage)
|
||||
{
|
||||
//尺寸变了才重建,避免反复 new/delete
|
||||
if (saveBkImage->getwidth() != w || saveBkImage->getheight() != h)
|
||||
{
|
||||
delete saveBkImage; saveBkImage = nullptr;
|
||||
}
|
||||
}
|
||||
if (!saveBkImage) saveBkImage = new IMAGE(w, h);
|
||||
|
||||
SetWorkingImage(nullptr); // ★抓屏幕
|
||||
getimage(saveBkImage, x, y, w, h);
|
||||
hasSnap = true;
|
||||
}
|
||||
|
||||
void Control::restBackground()
|
||||
{
|
||||
if (!hasSnap || !saveBkImage) return;
|
||||
// 直接回贴屏幕(与抓取一致)
|
||||
SetWorkingImage(nullptr);
|
||||
putimage(saveBkX, saveBkY, saveBkImage);
|
||||
}
|
||||
|
||||
void Control::discardBackground()
|
||||
{
|
||||
if (saveBkImage)
|
||||
{
|
||||
delete saveBkImage;
|
||||
saveBkImage = nullptr;
|
||||
}
|
||||
hasSnap = false; saveWidth = saveHeight = 0;
|
||||
}
|
||||
|
||||
void Control::updateBackground()
|
||||
{
|
||||
if (saveBkImage)
|
||||
{
|
||||
delete saveBkImage;
|
||||
saveBkImage = nullptr;
|
||||
}
|
||||
hasSnap = false; saveWidth = saveHeight = 0;
|
||||
}
|
||||
|
||||
115
src/Dialog.cpp
115
src/Dialog.cpp
@@ -3,7 +3,7 @@
|
||||
Dialog::Dialog(Window& h,std::string text,std::string message, StellarX::MessageBoxType type, bool modal)
|
||||
: Canvas(),message(message), type(type), modal(modal), hWnd(h), titleText(text)
|
||||
{
|
||||
initializeDialog();
|
||||
show = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Dialog::~Dialog()
|
||||
|
||||
void Dialog::draw()
|
||||
{
|
||||
if(!isVisible)
|
||||
if(!show)
|
||||
{
|
||||
// 如果对话框不可见且需要清理,执行清理
|
||||
if (pendingCleanup && !isCleaning)
|
||||
@@ -24,28 +24,22 @@ void Dialog::draw()
|
||||
return;
|
||||
}
|
||||
// 如果需要初始化,则执行初始化
|
||||
if (needsInitialization && isVisible)
|
||||
if (needsInitialization && show)
|
||||
{
|
||||
initDialogSize();
|
||||
needsInitialization = false;
|
||||
}
|
||||
|
||||
if (dirty && isVisible)
|
||||
if (dirty && show)
|
||||
{
|
||||
// 保存当前绘图状态
|
||||
saveStyle();
|
||||
|
||||
|
||||
// 保存背景(仅在第一次绘制时)
|
||||
if (saveBkImage == nullptr) {
|
||||
saveBkX = x - BorderWidth;
|
||||
saveBkY = y - BorderWidth;
|
||||
saveBkWidth = width + 2 * BorderWidth;
|
||||
saveBkHeight = height + 2 * BorderWidth;
|
||||
saveBkImage = new IMAGE(saveBkWidth, saveBkHeight);
|
||||
getimage(saveBkImage, saveBkX, saveBkY, saveBkWidth, saveBkHeight);
|
||||
}
|
||||
|
||||
if (saveBkImage == nullptr)
|
||||
saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth));
|
||||
|
||||
Canvas::setBorderColor(this->borderColor);
|
||||
Canvas::setLinewidth(this->BorderWidth);
|
||||
Canvas::setCanvasBkColor(this->backgroundColor);
|
||||
@@ -82,10 +76,11 @@ void Dialog::draw()
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Dialog::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
bool consume = false;
|
||||
if (!isVisible)
|
||||
if (!show)
|
||||
{
|
||||
if (pendingCleanup && !isCleaning)
|
||||
{
|
||||
@@ -158,16 +153,6 @@ StellarX::MessageBoxResult Dialog::GetResult() const
|
||||
return this->result;
|
||||
}
|
||||
|
||||
bool Dialog::getModal() const
|
||||
{
|
||||
return modal;
|
||||
}
|
||||
|
||||
bool Dialog::IsVisible() const
|
||||
{
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
bool Dialog::model() const
|
||||
{
|
||||
return modal;
|
||||
@@ -178,16 +163,16 @@ void Dialog::Show()
|
||||
if (pendingCleanup)
|
||||
performDelayedCleanup();
|
||||
|
||||
isVisible = true;
|
||||
show = true;
|
||||
dirty = true;
|
||||
needsInitialization = true;
|
||||
close = false;
|
||||
shouldClose = false;
|
||||
shouldClose = false;
|
||||
|
||||
if (modal)
|
||||
{
|
||||
// 模态对话框需要阻塞当前线程直到对话框关闭
|
||||
while (isVisible && !close)
|
||||
while (show && !close)
|
||||
{
|
||||
// 处理消息
|
||||
ExMessage msg;
|
||||
@@ -216,25 +201,20 @@ void Dialog::Show()
|
||||
|
||||
// 模态对话框关闭后执行清理
|
||||
if (pendingCleanup && !isCleaning)
|
||||
{
|
||||
performDelayedCleanup();
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非模态对话框只需标记为可见,由主循环处理
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Dialog::Close()
|
||||
{
|
||||
if (!isVisible) return;
|
||||
if (!show) return;
|
||||
|
||||
isVisible = false;
|
||||
show = false;
|
||||
close = true;
|
||||
dirty = true;
|
||||
pendingCleanup = true; // 只标记需要清理,不立即执行
|
||||
@@ -249,6 +229,15 @@ void Dialog::Close()
|
||||
|
||||
}
|
||||
|
||||
void Dialog::setInitialization(bool init)
|
||||
{
|
||||
if (init)
|
||||
{
|
||||
initDialogSize();
|
||||
saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Dialog::initButtons()
|
||||
{
|
||||
@@ -286,8 +275,8 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto cancelButton = createDialogButton(
|
||||
(okButton.get()->getButtonX() + okButton.get()->getButtonWidth() + buttonMargin),
|
||||
okButton.get()->getButtonY(),
|
||||
(okButton.get()->getX() + okButton.get()->getButtonWidth() + buttonMargin),
|
||||
okButton.get()->getY(),
|
||||
"取消"
|
||||
);
|
||||
cancelButton->setOnClickListener([this]()
|
||||
@@ -317,8 +306,8 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto noButton = createDialogButton(
|
||||
(yesButton.get()->getButtonX() + yesButton.get()->getButtonWidth() + buttonMargin),
|
||||
yesButton.get()->getButtonY(),
|
||||
(yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin),
|
||||
yesButton.get()->getY(),
|
||||
"否"
|
||||
);
|
||||
noButton->setOnClickListener([this]()
|
||||
@@ -348,8 +337,8 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto noButton = createDialogButton(
|
||||
yesButton.get()->getButtonX() + yesButton.get()->getButtonWidth() + buttonMargin,
|
||||
yesButton.get()->getButtonY(),
|
||||
yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin,
|
||||
yesButton.get()->getY(),
|
||||
"否"
|
||||
);
|
||||
noButton->setOnClickListener([this]()
|
||||
@@ -359,8 +348,8 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto cancelButton = createDialogButton(
|
||||
noButton.get()->getButtonX() + noButton.get()->getButtonWidth() + buttonMargin,
|
||||
noButton.get()->getButtonY(),
|
||||
noButton.get()->getX() + noButton.get()->getButtonWidth() + buttonMargin,
|
||||
noButton.get()->getY(),
|
||||
"取消"
|
||||
);
|
||||
cancelButton->setOnClickListener([this]()
|
||||
@@ -393,8 +382,8 @@ void Dialog::initButtons()
|
||||
this->Close(); });
|
||||
|
||||
auto cancelButton = createDialogButton(
|
||||
retryButton.get()->getButtonX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||
retryButton.get()->getButtonY(),
|
||||
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||
retryButton.get()->getY(),
|
||||
"取消"
|
||||
);
|
||||
cancelButton->setOnClickListener([this]()
|
||||
@@ -424,8 +413,8 @@ void Dialog::initButtons()
|
||||
this->Close();
|
||||
});
|
||||
auto retryButton = createDialogButton(
|
||||
abortButton.get()->getButtonX() + abortButton.get()->getButtonWidth() + buttonMargin,
|
||||
abortButton.get()->getButtonY(),
|
||||
abortButton.get()->getX() + abortButton.get()->getButtonWidth() + buttonMargin,
|
||||
abortButton.get()->getY(),
|
||||
"重试"
|
||||
);
|
||||
retryButton->setOnClickListener([this]()
|
||||
@@ -435,8 +424,8 @@ void Dialog::initButtons()
|
||||
this->Close();
|
||||
});
|
||||
auto ignoreButton = createDialogButton(
|
||||
retryButton.get()->getButtonX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||
retryButton.get()->getButtonY(),
|
||||
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||
retryButton.get()->getY(),
|
||||
"忽略"
|
||||
);
|
||||
ignoreButton.get()->setOnClickListener([this]()
|
||||
@@ -464,7 +453,7 @@ void Dialog::initCloseButton()
|
||||
auto but = std::make_unique<Button>
|
||||
(
|
||||
(this->x + this->width - closeButtonWidth) - 3, (this->y+3), closeButtonWidth-1, closeButtonHeight,
|
||||
"×", // 按钮文本
|
||||
"X", // 按钮文本
|
||||
RGB(255, 0, 0), // 按钮被点击颜色
|
||||
this->canvasBkClor, // 按钮背景颜色
|
||||
RGB(255, 0, 0), // 按钮被悬停颜色
|
||||
@@ -513,12 +502,14 @@ void Dialog::splitMessageLines()
|
||||
}
|
||||
|
||||
// 添加最后一行(如果有内容)
|
||||
if (!currentLine.empty()) {
|
||||
if (!currentLine.empty())
|
||||
{
|
||||
lines.push_back(currentLine);
|
||||
}
|
||||
|
||||
// 如果消息为空,至少添加一个空行
|
||||
if (lines.empty()) {
|
||||
if (lines.empty())
|
||||
{
|
||||
lines.push_back("");
|
||||
}
|
||||
}
|
||||
@@ -595,15 +586,6 @@ void Dialog::initDialogSize()
|
||||
initCloseButton(); // 初始化关闭按钮
|
||||
}
|
||||
|
||||
void Dialog::initializeDialog()
|
||||
{
|
||||
needsInitialization = true;
|
||||
pendingCleanup = false;
|
||||
isCleaning = false;
|
||||
close = false;
|
||||
isVisible = false;
|
||||
|
||||
}
|
||||
|
||||
// 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、
|
||||
// 所有控件析构前恢复背景,否则会导致背景图像被错误覆盖。
|
||||
@@ -622,12 +604,10 @@ void Dialog::performDelayedCleanup()
|
||||
title.reset();
|
||||
|
||||
// 释放背景图像资源
|
||||
if (saveBkImage)
|
||||
if (saveBkImage && hasSnap)
|
||||
{
|
||||
// 恢复背景
|
||||
putimage(saveBkX, saveBkY, saveBkImage);
|
||||
delete saveBkImage;
|
||||
saveBkImage = nullptr;
|
||||
restBackground();
|
||||
discardBackground();
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
@@ -647,6 +627,11 @@ std::string Dialog::GetCaption() const
|
||||
return titleText;
|
||||
}
|
||||
|
||||
std::string Dialog::GetText() const
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
void Dialog::clearControls()
|
||||
{
|
||||
controls.clear();
|
||||
|
||||
@@ -2,26 +2,28 @@
|
||||
|
||||
namespace StellarX
|
||||
{
|
||||
MessageBoxResult MessageBox::ShowModal(Window& wnd,const std::string& text,const std::string& caption,
|
||||
MessageBoxResult MessageBox::showModal(Window& wnd,const std::string& text,const std::string& caption,
|
||||
MessageBoxType type)
|
||||
{
|
||||
Dialog dlg(wnd, caption, text, type, true); // 模态
|
||||
dlg.setInitialization(true);
|
||||
dlg.Show();
|
||||
return dlg.GetResult();
|
||||
}
|
||||
|
||||
void MessageBox::ShowAsync(Window& wnd,const std::string& text,const std::string& caption,MessageBoxType type,
|
||||
void MessageBox::showAsync(Window& wnd,const std::string& text,const std::string& caption,MessageBoxType type,
|
||||
std::function<void(MessageBoxResult)> onResult)
|
||||
{
|
||||
//去重,如果窗口内已有相同的对话框被触发,则不再创建
|
||||
if (wnd.hasNonModalDialogWithCaption(caption))
|
||||
if (wnd.hasNonModalDialogWithCaption(caption, text))
|
||||
{
|
||||
std::cout << "\a" << std::endl;
|
||||
return;
|
||||
}
|
||||
auto dlg = std::make_unique<Dialog>(wnd, caption, text, type, false); // 非模态
|
||||
auto dlg = std::make_unique<Dialog>(wnd, caption, text,
|
||||
type, false); // 非模态
|
||||
Dialog* dlgPtr = dlg.get();
|
||||
|
||||
dlgPtr->setInitialization(true);
|
||||
// 设置回调
|
||||
if (onResult)
|
||||
dlgPtr->SetResultCallback(std::move(onResult));
|
||||
|
||||
@@ -18,7 +18,7 @@ Label::Label(int x, int y, std::string text, COLORREF textcolor, COLORREF bkColo
|
||||
|
||||
void Label::draw()
|
||||
{
|
||||
if(dirty)
|
||||
if (dirty && show)
|
||||
{
|
||||
saveStyle();
|
||||
if (textBkDisap)
|
||||
@@ -32,12 +32,20 @@ void Label::draw()
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
||||
this->saveBackground(x, y,textwidth(text.c_str()),textheight(text.c_str()));
|
||||
this-> restBackground();
|
||||
outtextxy(x, y, LPCTSTR(text.c_str()));
|
||||
restoreStyle();
|
||||
this->restoreStyle();
|
||||
dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
//用于“隐藏提示框”时调用(还原并释放快照)
|
||||
void Label::hide()
|
||||
{
|
||||
restBackground(); // 还原屏幕像素
|
||||
discardBackground(); // 作废快照,防止错贴旧图
|
||||
dirty = false;
|
||||
}
|
||||
void Label::setTextdisap(bool key)
|
||||
{
|
||||
textBkDisap = key;
|
||||
|
||||
338
src/table.cpp
338
src/table.cpp
@@ -3,151 +3,222 @@
|
||||
// 使用双循环绘制行和列,考虑分页偏移
|
||||
void Table::drawTable()
|
||||
{
|
||||
dX = x;
|
||||
dY = uY;
|
||||
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||
|
||||
// 表体从“表头之下”开始
|
||||
dX = x + border;
|
||||
dY = y + border + lineHeights.at(0) + 10; // 表头高度
|
||||
uY = dY + lineHeights.at(0) + 10;
|
||||
|
||||
for (size_t i = (currentPage * rowsPerPage - rowsPerPage); i < (currentPage*rowsPerPage) && i < data.size(); i++)
|
||||
|
||||
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++)
|
||||
for (size_t j = 0; j < data[i].size(); ++j)
|
||||
{
|
||||
uX = dX + colWidths.at(j) + 20;
|
||||
uX = dX + colWidths.at(j) + 20; // 列宽 + 20
|
||||
fillrectangle(dX, dY, uX, uY);
|
||||
outtextxy(dX + 10, dY + 5, LPCTSTR(data[i][j].c_str()));
|
||||
dX += this->colWidths.at(j) + 20;
|
||||
dX += colWidths.at(j) + 20;
|
||||
}
|
||||
dX = x;
|
||||
dX = x + border;
|
||||
dY = uY;
|
||||
uY = dY + lineHeights.at(0) + 10;
|
||||
|
||||
}
|
||||
uY = y + lineHeights.at(0) + 10;
|
||||
|
||||
}
|
||||
|
||||
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) + 10;
|
||||
for(size_t i = 0; i < headers.size(); i++)
|
||||
|
||||
for (size_t i = 0; i < headers.size(); i++)
|
||||
{
|
||||
uX = dX + colWidths.at(i) + 20;
|
||||
uX = dX + colWidths.at(i) + 20; // 注意这里是 +20,和表体一致
|
||||
fillrectangle(dX, dY, uX, uY);
|
||||
outtextxy(dX + 10, dY + 5, LPCTSTR(headers[i].c_str()));
|
||||
dX += this->colWidths.at(i) + 20;
|
||||
dX += colWidths.at(i) + 20; // 列间距 20
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
// 遍历所有数据单元和表头,计算每列的最大宽度和每行的最大高度,
|
||||
// 为后续绘制表格单元格提供尺寸依据。此计算在数据变更时自动触发。
|
||||
void Table::initTextWaH()
|
||||
{
|
||||
this->colWidths.resize(this->headers.size());
|
||||
this->lineHeights.resize(this->headers.size());
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
//计算数据尺寸
|
||||
for (size_t i = 0; i < data.size(); i++)
|
||||
// 和绘制一致的单元内边距
|
||||
const int padX = 10; // 左右 padding
|
||||
const int padY = 5; // 上下 padding
|
||||
const int colGap = 20; // 列间距
|
||||
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++)
|
||||
for (size_t j = 0; j < data[i].size(); ++j)
|
||||
{
|
||||
width = textwidth(LPCTSTR(data[i][j].c_str()));
|
||||
height = textheight(LPCTSTR(data[i][j].c_str()));
|
||||
if (width > this->colWidths.at(j))
|
||||
this->colWidths.at(j) = width;
|
||||
if (height > this->lineHeights[j])
|
||||
this->lineHeights.at(j) = height;
|
||||
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 i = 0; i < this->headers.size(); i++)
|
||||
// 再用表头更新(谁大取谁)
|
||||
for (size_t j = 0; j < headers.size(); ++j)
|
||||
{
|
||||
width = textwidth(LPCTSTR(headers[i].c_str()));
|
||||
height = textheight(LPCTSTR(headers[i].c_str()));
|
||||
if (width > this->colWidths.at(i))
|
||||
this->colWidths.at(i) = width;
|
||||
if (height > this->lineHeights[i])
|
||||
this->lineHeights.at(i) = height;
|
||||
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;
|
||||
}
|
||||
|
||||
// 计算表格总宽度和高度
|
||||
this->width = 0;
|
||||
for (size_t i = 0; i < colWidths.size(); i++)
|
||||
this->width += colWidths.at(i) + 20;
|
||||
LINESTYLE currentStyle;
|
||||
// 用“所有列的最大行高”作为一行的基准高度
|
||||
int maxLineH = 0;
|
||||
|
||||
this->width += tableBorderWidth;
|
||||
for (int h : lineHeights)
|
||||
if (h > maxLineH)
|
||||
maxLineH = h;
|
||||
|
||||
this->height = lineHeights.at(0) * (rowsPerPage + 1) + rowsPerPage * 10+20 ; // 表头+数据行+页码区域
|
||||
// 列的像素宽 = 内容宽 + 左右 padding
|
||||
// 表内容总宽 = Σ(列宽 + 列间距)
|
||||
int contentW = 0;
|
||||
for (size_t j = 0; j < colWidths.size(); ++j)
|
||||
contentW += (colWidths[j] + 2 * padX) + colGap;
|
||||
|
||||
// 表头高 & 行高(与 drawHeader/drawTable 内部一致:+上下 padding)
|
||||
const int headerH = maxLineH + 2 * padY;
|
||||
const int rowH = maxLineH + 2 * padY;
|
||||
const int rowsH = rowH * rowsPerPage;
|
||||
|
||||
// 由于表格绘制会覆盖原有区域,需要先保存背景以便在下次绘制前恢复,
|
||||
// 避免重叠绘制造成的残影问题。
|
||||
// 如果背景图像不存在或尺寸不匹配,创建或重新创建
|
||||
if (saveBkImage == nullptr) {
|
||||
saveBkImage = new IMAGE(width, height);
|
||||
}
|
||||
else if (saveBkImage->getwidth() != width || saveBkImage->getheight() != height) {
|
||||
delete saveBkImage;
|
||||
saveBkImage = new IMAGE(width, height);
|
||||
}
|
||||
// 页脚:
|
||||
const int pageTextH = textheight(LPCTSTR(pageNumtext.c_str()));
|
||||
const int btnTextH = textheight(LPCTSTR("上一页"));
|
||||
const int btnPadV = 8;
|
||||
const int btnH = btnTextH + 2 * btnPadV;
|
||||
const int footerPad = 16;
|
||||
const int footerH = (pageTextH > btnH ? pageTextH : btnH) + footerPad;
|
||||
|
||||
// 最终表宽/高:内容 + 对称边框
|
||||
this->width = contentW + (border << 1);
|
||||
this->height = headerH + rowsH + footerH + (border << 1);
|
||||
}
|
||||
|
||||
void Table::initButton()
|
||||
{
|
||||
int x1, x2;
|
||||
int y1, y2;
|
||||
x1 = pX - 72;
|
||||
x2 = pX + textwidth(LPCTSTR(pageNumtext.c_str())) + 10;
|
||||
y1 = y2 = pY;
|
||||
this->prevButton = new Button(x1, y1, 62, textheight(LPCTSTR(pageNumtext.c_str())), "上一页", RGB(0, 0, 0), RGB(255, 255, 255));
|
||||
this->nextButton = new Button(x2, y2, 62, textheight(LPCTSTR(pageNumtext.c_str())), "下一页", RGB(0, 0, 0), RGB(255, 255, 255));
|
||||
this->prevButton->textStyle = this->textStyle;
|
||||
this->nextButton->textStyle = this->textStyle;
|
||||
this->prevButton->setFillMode(tableFillMode);
|
||||
this->nextButton->setFillMode(tableFillMode);
|
||||
prevButton->setOnClickListener([this]()
|
||||
{if (this->currentPage > 1)
|
||||
{
|
||||
this->currentPage--;
|
||||
this->dirty = true;
|
||||
this->draw();
|
||||
} });
|
||||
const int gap = 12; // 页码与按钮之间的固定间距
|
||||
const int padH = 12; // 按钮水平内边距
|
||||
const int padV = 0; // 按钮垂直内边距
|
||||
|
||||
int pageW = textwidth(LPCTSTR(pageNumtext.c_str()));
|
||||
int lblH = textheight(LPCTSTR(pageNumtext.c_str()));
|
||||
|
||||
// 统一按钮尺寸(用按钮文字自身宽高 + padding)
|
||||
int prevW = textwidth(LPCTSTR("上一页")) + padH * 2;
|
||||
int nextW = textwidth(LPCTSTR("下一页")) + padH * 2;
|
||||
int btnH = lblH + padV * 2;
|
||||
|
||||
nextButton->setOnClickListener([this]()
|
||||
{if (this->currentPage < (this->totalPages))
|
||||
|
||||
// 基于“页码标签”的矩形来摆放:
|
||||
// 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, "上一页", RGB(0, 0, 0), RGB(255, 255, 255));
|
||||
else
|
||||
{
|
||||
this->currentPage++;
|
||||
this->dirty = true;
|
||||
this->draw();
|
||||
}});
|
||||
prevButton->setX(prevX);
|
||||
prevButton->setY(btnY);
|
||||
}
|
||||
|
||||
if (!nextButton)
|
||||
nextButton = new Button(nextX, btnY, nextW, btnH, "下一页", 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Table::initPageNum()
|
||||
{
|
||||
if (0 == pY)
|
||||
pY = uY + lineHeights.at(0) * rowsPerPage + rowsPerPage * 10+20;
|
||||
for (size_t i = 0; i < colWidths.size(); i++)
|
||||
this->pX += colWidths.at(i) + 20;
|
||||
this->pX -= textwidth(LPCTSTR(pageNumtext.c_str()));
|
||||
this->pX /= 2;
|
||||
this->pX += x;
|
||||
this->pageNum = new Label(this->pX, pY, pageNumtext);
|
||||
// 统一坐标系
|
||||
const int border = tableBorderWidth > 0 ? tableBorderWidth : 0;
|
||||
const int baseH = lineHeights.empty() ? 0 : lineHeights.at(0);
|
||||
const int headerH = baseH + 10;
|
||||
const int rowsH = baseH * rowsPerPage + rowsPerPage * 10;
|
||||
|
||||
// 内容宽度 = 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 + 8; // +8 顶部留白
|
||||
|
||||
// 按理来说 x + (this->width - textW) / 2;就可以
|
||||
// 但是在绘制时,发现控件偏右,因此减去40
|
||||
int textW = textwidth(LPCTSTR(pageNumtext.c_str()));
|
||||
pX = x - 40 +(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);
|
||||
pageNum->setTextdisap(true); // 透明文本
|
||||
}
|
||||
|
||||
void Table::drawPageNum()
|
||||
{
|
||||
if (nullptr == pageNum)
|
||||
initPageNum();
|
||||
|
||||
pageNumtext = "第";
|
||||
pageNumtext+= std::to_string(currentPage);
|
||||
pageNumtext += "页/共";
|
||||
pageNumtext += std::to_string(totalPages);
|
||||
pageNumtext += "页";
|
||||
if (nullptr == pageNum)
|
||||
initPageNum();
|
||||
pageNum->setText(pageNumtext);
|
||||
if (StellarX::FillMode::Null == tableFillMode)
|
||||
pageNum->setTextdisap(true);
|
||||
pageNum->draw();
|
||||
|
||||
}
|
||||
@@ -156,6 +227,15 @@ void Table::drawButton()
|
||||
{
|
||||
if (nullptr == prevButton || nullptr == nextButton)
|
||||
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();
|
||||
|
||||
@@ -184,7 +264,7 @@ Table::~Table()
|
||||
|
||||
void Table::draw()
|
||||
{
|
||||
if (this->dirty)
|
||||
if (this->dirty && this->show)
|
||||
{
|
||||
// 先保存当前绘图状态
|
||||
saveStyle();
|
||||
@@ -206,47 +286,26 @@ void Table::draw()
|
||||
initTextWaH();
|
||||
isNeedCellSize = false;
|
||||
}
|
||||
|
||||
// 优化性能:仅在首次绘制或表格尺寸变化时捕获背景图像。
|
||||
// 由于表格绘制会覆盖原有区域,需要先保存背景以便在下次绘制前恢复,
|
||||
// 避免重叠绘制造成的残影问题。
|
||||
static bool firstDraw = true;
|
||||
if (firstDraw || isNeedDrawHeaders)
|
||||
|
||||
if (isNeedDrawHeaders)
|
||||
{
|
||||
// 确保在绘制任何表格内容之前捕获背景
|
||||
if (saveBkImage)
|
||||
{
|
||||
// 临时恢复样式,确保捕获正确的背景
|
||||
restoreStyle();
|
||||
if(tableBorderWidth>1)
|
||||
getimage(saveBkImage, this->x- tableBorderWidth, this->y- tableBorderWidth, this->width+ tableBorderWidth, this->height+ tableBorderWidth);
|
||||
else
|
||||
getimage(saveBkImage, this->x, this->y, this->width, this->height);
|
||||
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);
|
||||
}
|
||||
firstDraw = false;
|
||||
// 重新设置表格样式
|
||||
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 (!saveBkImage)
|
||||
saveBackground(this->x, this->y, this->width, this->height);
|
||||
// 恢复背景(清除旧内容)
|
||||
if (saveBkImage)
|
||||
{
|
||||
if (tableBorderWidth > 1)
|
||||
putimage(this->x - tableBorderWidth, this->y - tableBorderWidth, saveBkImage);
|
||||
else
|
||||
putimage(this->x,this->y,this->saveBkImage);
|
||||
}
|
||||
|
||||
restBackground();
|
||||
// 绘制表头
|
||||
|
||||
dX = x;
|
||||
@@ -273,6 +332,7 @@ void Table::draw()
|
||||
|
||||
bool Table::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
if(!show)return false;
|
||||
bool consume = false;
|
||||
if(!this->isShowPageButton)
|
||||
return consume;
|
||||
@@ -282,6 +342,8 @@ bool Table::handleEvent(const ExMessage& msg)
|
||||
if (!consume)
|
||||
consume = nextButton->handleEvent(msg);
|
||||
}
|
||||
if (dirty)
|
||||
draw();
|
||||
return consume;
|
||||
}
|
||||
|
||||
@@ -361,17 +423,17 @@ void Table::setTableFillMode(StellarX::FillMode mode)
|
||||
this->tableFillMode = mode;
|
||||
else
|
||||
this->tableFillMode = StellarX::FillMode::Solid;
|
||||
|
||||
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->prevButton->setDirty(true);
|
||||
this->nextButton->setDirty(true);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX
|
||||
|
||||
void TextBox::draw()
|
||||
{
|
||||
if(dirty)
|
||||
if (dirty && show)
|
||||
{
|
||||
saveStyle();
|
||||
setfillcolor(textBoxBkClor);
|
||||
|
||||
234
src/window.cpp
234
src/window.cpp
@@ -1,111 +1,147 @@
|
||||
#include "Window.h"
|
||||
#include"Dialog.h"
|
||||
#include "Dialog.h"
|
||||
#include <windows.h> // 确保包含 Windows API 头文件
|
||||
|
||||
Window::Window(int width, int height, int mode)
|
||||
{
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->windowMode = mode;
|
||||
|
||||
this->pendingW = this->width = width;
|
||||
this->pendingH = this->height = height;
|
||||
this->windowMode = mode;
|
||||
}
|
||||
|
||||
Window::Window(int width, int height, int mode, COLORREF bkcloc)
|
||||
{
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->windowMode = mode;
|
||||
this->wBkcolor = bkcloc;
|
||||
this->pendingW = this->width = width;
|
||||
this->pendingH = this->height = height;
|
||||
this->windowMode = mode;
|
||||
this->wBkcolor = bkcloc;
|
||||
}
|
||||
|
||||
Window::Window(int width, int height, int mode, COLORREF bkcloc, std::string headline)
|
||||
{
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->windowMode = mode;
|
||||
this->wBkcolor = bkcloc;
|
||||
this->pendingW = this->width = width;
|
||||
this->pendingH = this->height = height;
|
||||
this->windowMode = mode;
|
||||
this->wBkcolor = bkcloc;
|
||||
this->headline = headline;
|
||||
}
|
||||
|
||||
Window::~Window()
|
||||
{
|
||||
if (background)
|
||||
delete background;
|
||||
background = nullptr;
|
||||
closegraph(); // 确保关闭图形上下文
|
||||
if (background)
|
||||
delete background;
|
||||
background = nullptr;
|
||||
closegraph(); // 确保关闭图形上下文
|
||||
}
|
||||
|
||||
void Window::draw()
|
||||
|
||||
void Window::draw() {
|
||||
// 使用 EasyX 创建基本窗口
|
||||
hWnd = initgraph(width, height, windowMode);
|
||||
SetWindowText(hWnd, headline.c_str());
|
||||
// **启用窗口拉伸支持**:添加厚边框和最大化按钮样式
|
||||
LONG style = GetWindowLong(hWnd, GWL_STYLE);
|
||||
style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; // 可调整边框,启用最大化/最小化按钮
|
||||
SetWindowLong(hWnd, GWL_STYLE, style);
|
||||
// 通知窗口样式变化生效
|
||||
SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
|
||||
// 设置背景色并清屏
|
||||
setbkcolor(wBkcolor);
|
||||
cleardevice();
|
||||
// 初次绘制所有控件(双缓冲)
|
||||
BeginBatchDraw();
|
||||
for (auto& control : controls)
|
||||
{
|
||||
control->draw();
|
||||
}
|
||||
// (如果有初始对话框,也可绘制 dialogs)
|
||||
EndBatchDraw();
|
||||
}
|
||||
|
||||
void Window::draw(std::string imagePath)
|
||||
{
|
||||
hWnd = initgraph(width, height, windowMode);
|
||||
SetWindowText(hWnd,headline.c_str());
|
||||
setbkcolor(wBkcolor);
|
||||
cleardevice();
|
||||
BeginBatchDraw(); // 开始批量绘制
|
||||
// 绘制所有子控件
|
||||
for (auto& control : controls)
|
||||
control->draw();
|
||||
EndBatchDraw(); // 结束批量绘制
|
||||
}
|
||||
// 使用背景图片绘制窗口
|
||||
// @参数 pImgFile: 图片文件路径,支持常见图片格式
|
||||
// @备注: 会拉伸图片以适应窗口尺寸
|
||||
void Window::draw(std::string pImgFile)
|
||||
{
|
||||
this->background = new IMAGE(width, height);
|
||||
hWnd = initgraph(width, height, windowMode);
|
||||
SetWindowText(hWnd, headline.c_str());
|
||||
loadimage(background, pImgFile.c_str(), width, height, true);
|
||||
putimage(0,0, background);
|
||||
|
||||
// 绘制所有子控件
|
||||
BeginBatchDraw(); // 开始批量绘制
|
||||
for (auto& control : controls)
|
||||
control->draw();
|
||||
for(auto& d: dialogs)
|
||||
d->draw();
|
||||
EndBatchDraw(); // 结束批量绘制
|
||||
// 使用指定图片绘制窗口背景(铺满窗口)
|
||||
this->background = new IMAGE(width, height);
|
||||
bkImageFile = imagePath;
|
||||
hWnd = initgraph(width, height, windowMode);
|
||||
SetWindowText(hWnd, headline.c_str());
|
||||
loadimage(background, imagePath.c_str(), width, height, true);
|
||||
putimage(0, 0, background);
|
||||
// 同样应用可拉伸样式
|
||||
LONG style = GetWindowLong(hWnd, GWL_STYLE);
|
||||
style |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
|
||||
SetWindowLong(hWnd, GWL_STYLE, style);
|
||||
SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
|
||||
// 绘制控件(含对话框)到窗口
|
||||
BeginBatchDraw();
|
||||
for (auto& control : controls) control->draw();
|
||||
for (auto& dlg : dialogs) dlg->draw();
|
||||
EndBatchDraw();
|
||||
}
|
||||
|
||||
// 运行主事件循环,处理用户输入和窗口消息
|
||||
// 此方法会阻塞直到窗口关闭
|
||||
// 主消息循环优先级:对话框 > 普通控件。
|
||||
// 重绘策略:为保证视觉一致性,每次有对话框状态变化(打开/关闭)时,
|
||||
// 会强制重绘所有控件。先绘制普通控件,再绘制对话框(确保对话框在最上层)。
|
||||
void Window::runEventLoop()
|
||||
int Window::runEventLoop()
|
||||
{
|
||||
ExMessage msg;
|
||||
bool running = true;
|
||||
|
||||
while (running)
|
||||
|
||||
while (running)
|
||||
{
|
||||
bool consume = false;// 是否处理了消息
|
||||
|
||||
|
||||
// 处理所有消息
|
||||
if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true))
|
||||
{
|
||||
if (msg.message == WM_CLOSE)
|
||||
{
|
||||
running = false;
|
||||
break;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 优先处理对话框事件
|
||||
for (auto& d : dialogs)
|
||||
if (msg.message == WM_SIZE)
|
||||
{
|
||||
if (msg.wParam != SIZE_MINIMIZED)
|
||||
{
|
||||
const int nw = LOWORD(msg.lParam);
|
||||
const int nh = HIWORD(msg.lParam);
|
||||
|
||||
// 仅在尺寸真的变化时标脏
|
||||
if (nw > 0 && nh > 0 || (nw != width || nh != height))
|
||||
{
|
||||
pendingW = nw;
|
||||
pendingH = nh;
|
||||
needResizeDirty = true;
|
||||
}
|
||||
}
|
||||
continue;//在末尾重绘制窗口
|
||||
}
|
||||
// 优先处理对话框事件
|
||||
for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it)
|
||||
{
|
||||
auto& d = *it;
|
||||
if (d->IsVisible() && !d->model())
|
||||
consume = d->handleEvent(msg);
|
||||
if (consume)
|
||||
break;
|
||||
}
|
||||
|
||||
if(!consume)
|
||||
for (auto& c : controls)
|
||||
//普通控件
|
||||
if (!consume)
|
||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||
{
|
||||
consume = c->handleEvent(msg);
|
||||
consume = (*it)->handleEvent(msg);
|
||||
if (consume)
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
//如果有对话框打开或者关闭强制重绘
|
||||
//如果有对话框打开或者关闭强制重绘
|
||||
bool needredraw = false;
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
@@ -115,7 +151,7 @@ void Window::runEventLoop()
|
||||
if (needredraw || dialogClose)
|
||||
{
|
||||
// 对话框关闭后,需要手动合成一个鼠标移动消息并分发给所有普通控件,
|
||||
// 以便它们能及时更新悬停状态(hover),否则悬停状态可能保持错误状态。
|
||||
// 以便它们能及时更新悬停状态(hover),否则悬停状态可能保持错误状态。
|
||||
// 先把当前鼠标位置转换为客户区坐标,并合成一次 WM_MOUSEMOVE,先分发给控件更新 hover 状态
|
||||
POINT pt;
|
||||
if (GetCursorPos(&pt))
|
||||
@@ -123,8 +159,8 @@ void Window::runEventLoop()
|
||||
ScreenToClient(this->hWnd, &pt);
|
||||
ExMessage mm;
|
||||
mm.message = WM_MOUSEMOVE;
|
||||
mm.x =(short) pt.x;
|
||||
mm.y =(short) pt.y;
|
||||
mm.x = (short)pt.x;
|
||||
mm.y = (short)pt.y;
|
||||
// 只分发给 window 层控件(因为 dialog 已经关闭或即将关闭)
|
||||
for (auto& c : controls)
|
||||
c->handleEvent(mm);
|
||||
@@ -145,10 +181,43 @@ void Window::runEventLoop()
|
||||
needredraw = false;
|
||||
|
||||
}
|
||||
//—— 统一“收口”:尺寸变化后的** 一次性** 重绘 ——
|
||||
if (needResizeDirty)
|
||||
{
|
||||
//确保窗口不会小于初始尺寸
|
||||
if (pendingW >= width && pendingH >= height)
|
||||
Resize(nullptr, pendingW, pendingH);
|
||||
else
|
||||
Resize(nullptr, width, height);
|
||||
if (background)
|
||||
{
|
||||
delete background;
|
||||
background = new IMAGE;
|
||||
loadimage(background, bkImageFile.c_str(), pendingW, pendingH);
|
||||
putimage(0, 0, background);
|
||||
}
|
||||
|
||||
// 标记所有控件/对话框为脏,确保都补一次背景/外观
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c->setDirty(true);
|
||||
c->updateBackground();
|
||||
c->draw();
|
||||
}
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
auto dd = dynamic_cast<Dialog*>(d.get());
|
||||
dd->setDirty(true);
|
||||
dd->setInitialization(true);
|
||||
dd->draw();
|
||||
}
|
||||
needResizeDirty = false;
|
||||
}
|
||||
|
||||
// 避免CPU占用过高
|
||||
// 降低占用
|
||||
Sleep(10);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Window::setBkImage(std::string pImgFile)
|
||||
@@ -158,13 +227,44 @@ void Window::setBkImage(std::string pImgFile)
|
||||
else
|
||||
delete background;
|
||||
this->background = new IMAGE;
|
||||
this->bkImageFile = pImgFile;
|
||||
loadimage(background, pImgFile.c_str(), width, height, true);
|
||||
putimage(0, 0, background);
|
||||
BeginBatchDraw();
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c->setDirty(true);
|
||||
c->draw();
|
||||
}
|
||||
for (auto& c : dialogs)
|
||||
{
|
||||
c->setDirty(true);
|
||||
c->draw();
|
||||
}
|
||||
EndBatchDraw();
|
||||
|
||||
}
|
||||
|
||||
void Window::setBkcolor(COLORREF c)
|
||||
{
|
||||
wBkcolor = c;
|
||||
|
||||
setbkcolor(wBkcolor);
|
||||
cleardevice();
|
||||
// 初次绘制所有控件(双缓冲)
|
||||
BeginBatchDraw();
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c->setDirty(true);
|
||||
c->draw();
|
||||
}
|
||||
for (auto& c : dialogs)
|
||||
{
|
||||
c->setDirty(true);
|
||||
c->draw();
|
||||
}
|
||||
EndBatchDraw();
|
||||
|
||||
}
|
||||
|
||||
void Window::setHeadline(std::string headline)
|
||||
@@ -183,13 +283,15 @@ void Window::addDialog(std::unique_ptr<Control> dialogs)
|
||||
this->dialogs.push_back(std::move(dialogs));
|
||||
}
|
||||
|
||||
bool Window::hasNonModalDialogWithCaption(const std::string& caption) const {
|
||||
for (const auto& dptr : dialogs) {
|
||||
bool Window::hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const
|
||||
{
|
||||
for (const auto& dptr : dialogs)
|
||||
{
|
||||
if (!dptr) continue;
|
||||
// 只检查 Dialog 类型的控件
|
||||
Dialog* d = dynamic_cast<Dialog*>(dptr.get());
|
||||
//检查是否有非模态对话框可见,并且消息内容一致
|
||||
if (d && d->IsVisible() && !d->model() && d->GetCaption() == caption)
|
||||
if (d && d->IsVisible() && !d->model() && d->GetCaption() == caption && d->GetText() == message)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -203,12 +305,12 @@ HWND Window::getHwnd() const
|
||||
|
||||
int Window::getWidth() const
|
||||
{
|
||||
return this->width;
|
||||
return this->pendingW;
|
||||
}
|
||||
|
||||
int Window::getHeight() const
|
||||
{
|
||||
return this->height;
|
||||
return this->pendingH;
|
||||
}
|
||||
|
||||
std::string Window::getHeadline() const
|
||||
|
||||
Reference in New Issue
Block a user