feat: add a new awesome feature
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
#include "StellarX/Button.h"
|
||||
#include "Button.h"
|
||||
|
||||
Button::Button(int x, int y, int width, int height, const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape)
|
||||
: Control(x, y, width, height)
|
||||
@@ -52,7 +52,6 @@ void Button::draw()
|
||||
}
|
||||
else
|
||||
{
|
||||
// 确保点击状态的颜色正确显示
|
||||
// 点击状态优先级最高,然后是悬停状态,最后是默认状态
|
||||
if (click)
|
||||
setfillcolor(buttonTrueColor);
|
||||
@@ -77,7 +76,7 @@ void Button::draw()
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
||||
}
|
||||
//设置按钮填充模式
|
||||
setfillstyle(buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
|
||||
setfillstyle((int)buttonFillMode, (int)buttonFillIma, buttonFileIMAGE);
|
||||
|
||||
//获取字符串像素高度和宽度
|
||||
if ((this->oldtext_width != this->text_width || this->oldtext_height != this->text_height)
|
||||
@@ -132,10 +131,11 @@ void Button::draw()
|
||||
}
|
||||
// 处理鼠标事件,检测点击和悬停状态
|
||||
// 根据按钮模式和形状进行不同的处理
|
||||
void Button::handleEvent(const ExMessage& msg)
|
||||
bool Button::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
bool oldHover = hover;
|
||||
bool oldClick = click;
|
||||
bool consume = false;//是否消耗事件
|
||||
|
||||
// 检测悬停状态(根据不同形状)
|
||||
switch (shape)
|
||||
@@ -163,13 +163,15 @@ void Button::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
click = true;
|
||||
dirty = true;
|
||||
consume = true;
|
||||
}
|
||||
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||
{
|
||||
// TOGGLE模式在鼠标释放时处理
|
||||
}
|
||||
}
|
||||
// 处理鼠标释放事件
|
||||
// NORMAL 模式:鼠标在按钮上释放时才触发点击回调,如果移出区域则取消点击状态。
|
||||
// TOGGLE 模式:在释放时切换状态,并触发相应的开/关回调。
|
||||
else if (msg.message == WM_LBUTTONUP && hover && mode != StellarX::ButtonMode::DISABLED)
|
||||
{
|
||||
if (mode == StellarX::ButtonMode::NORMAL && click)
|
||||
@@ -177,7 +179,8 @@ void Button::handleEvent(const ExMessage& msg)
|
||||
if (onClickCallback) onClickCallback();
|
||||
click = false;
|
||||
dirty = true;
|
||||
// 使用新的flushmessage函数刷新消息队列:cite[2]:cite[3]
|
||||
consume = true;
|
||||
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
else if (mode == StellarX::ButtonMode::TOGGLE)
|
||||
@@ -186,11 +189,13 @@ void Button::handleEvent(const ExMessage& msg)
|
||||
if (click && onToggleOnCallback) onToggleOnCallback();
|
||||
else if (!click && onToggleOffCallback) onToggleOffCallback();
|
||||
dirty = true;
|
||||
// 使用新的flushmessage函数刷新消息队列:cite[2]:cite[3]
|
||||
consume = true;
|
||||
// 清除消息队列中积压的鼠标和键盘消息,防止本次点击事件被重复处理
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
}
|
||||
// 处理鼠标移出区域的情况
|
||||
|
||||
else if (msg.message == WM_MOUSEMOVE)
|
||||
{
|
||||
if (!hover && mode == StellarX::ButtonMode::NORMAL && click)
|
||||
@@ -215,6 +220,7 @@ void Button::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
draw();
|
||||
}
|
||||
return consume;
|
||||
}
|
||||
|
||||
void Button::setOnClickListener(const std::function<void()>&& callback)
|
||||
@@ -237,14 +243,17 @@ void Button::setbuttonMode(StellarX::ButtonMode mode)
|
||||
this->mode = mode;
|
||||
}
|
||||
|
||||
int Button::setROUND_RECTANGLEwidth(int width)
|
||||
void Button::setROUND_RECTANGLEwidth(int width)
|
||||
{
|
||||
return rouRectangleSize.ROUND_RECTANGLEwidth = width;
|
||||
rouRectangleSize.ROUND_RECTANGLEwidth = width;
|
||||
this->dirty = true; // 标记需要重绘
|
||||
|
||||
}
|
||||
|
||||
int Button::setROUND_RECTANGLEheight(int height)
|
||||
void Button::setROUND_RECTANGLEheight(int height)
|
||||
{
|
||||
return rouRectangleSize.ROUND_RECTANGLEheight = height;
|
||||
rouRectangleSize.ROUND_RECTANGLEheight = height;
|
||||
this->dirty = true; // 标记需要重绘
|
||||
}
|
||||
|
||||
bool Button::isClicked() const
|
||||
@@ -252,26 +261,41 @@ bool Button::isClicked() const
|
||||
return this->click;
|
||||
}
|
||||
|
||||
void Button::setFillMode(int mode)
|
||||
void Button::setFillMode(StellarX::FillMode mode)
|
||||
{
|
||||
buttonFillMode = mode;
|
||||
this->dirty = true; // 标记需要重绘
|
||||
}
|
||||
|
||||
void Button::setFillIma(StellarX::FillStyle ima)
|
||||
{
|
||||
buttonFillIma = ima;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Button::setFillIma(std::string imaNAme)
|
||||
{
|
||||
if (buttonFileIMAGE)
|
||||
{
|
||||
delete buttonFileIMAGE;
|
||||
buttonFileIMAGE = nullptr;
|
||||
}
|
||||
buttonFileIMAGE = new IMAGE;
|
||||
loadimage(buttonFileIMAGE, imaNAme.c_str(),width-x,height-y);
|
||||
loadimage(buttonFileIMAGE, imaNAme.c_str(),width,height);
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
|
||||
void Button::setButtonBorder(COLORREF Border)
|
||||
{
|
||||
buttonBorderColor = Border;
|
||||
buttonBorderColor = Border;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Button::setButtonFalseColor(COLORREF color)
|
||||
{
|
||||
this->buttonFalseColor = color;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Button::setButtonText(const char* text)
|
||||
@@ -279,6 +303,7 @@ void Button::setButtonText(const char* text)
|
||||
this->text = std::string(text);
|
||||
this->text_width = textwidth(LPCTSTR(this->text.c_str()));
|
||||
this->text_height = textheight(LPCTSTR(this->text.c_str()));
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Button::setButtonText(std::string text)
|
||||
@@ -292,6 +317,7 @@ void Button::setButtonText(std::string text)
|
||||
void Button::setButtonShape(StellarX::ControlShape shape)
|
||||
{
|
||||
this->shape = shape;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -315,7 +341,7 @@ StellarX::ControlShape Button::getButtonShape() const
|
||||
return this->shape;
|
||||
}
|
||||
|
||||
int Button::getFillMode() const
|
||||
StellarX::FillMode Button::getFillMode() const
|
||||
{
|
||||
return this->buttonFillMode;
|
||||
}
|
||||
@@ -345,6 +371,26 @@ StellarX::ControlText Button::getButtonTextStyle() const
|
||||
return this->textStyle;
|
||||
}
|
||||
|
||||
int Button::getButtonWidth() const
|
||||
{
|
||||
return this->width;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -357,10 +403,10 @@ bool Button::isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius)
|
||||
|
||||
bool Button::isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height)
|
||||
{
|
||||
int centerX = (x + width) / 2;
|
||||
int centerY = (y + height) / 2;
|
||||
int majorAxis = (width - x) / 2;
|
||||
int minorAxis = (height - y) / 2;
|
||||
int centerX = (x + width) / 2;
|
||||
int centerY = (y + height) / 2;
|
||||
int majorAxis = (width - x) / 2;
|
||||
int minorAxis = (height - y) / 2;
|
||||
double dx = mouseX - centerX;
|
||||
double dy = mouseY - centerY;
|
||||
double normalizedDistance = (dx * dx) / (majorAxis * majorAxis) + (dy * dy) / (minorAxis * minorAxis);
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
#include "StellarX/Canvas.h"
|
||||
#include "Canvas.h"
|
||||
|
||||
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;
|
||||
saveStyle();
|
||||
|
||||
setlinecolor(canvasBorderClor);//设置线色
|
||||
@@ -34,18 +45,21 @@ void Canvas::draw()
|
||||
|
||||
|
||||
restoreStyle();
|
||||
dirty = false; //标记画布不需要重绘
|
||||
}
|
||||
|
||||
void Canvas::handleEvent(const ExMessage& msg)
|
||||
bool Canvas::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
for (auto& control : controls) {
|
||||
control->handleEvent(msg);
|
||||
}
|
||||
for (auto& control : controls)
|
||||
if (control->handleEvent(msg))
|
||||
return true;//事件被消费短路传递,立即返回true 否则返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
void Canvas::addControl(std::unique_ptr<Control> control)
|
||||
{
|
||||
controls.push_back(std::move(control));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Canvas::setShape(StellarX::ControlShape shape)
|
||||
@@ -57,12 +71,14 @@ void Canvas::setShape(StellarX::ControlShape shape)
|
||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||
this->shape = shape;
|
||||
dirty = true;
|
||||
break;
|
||||
case StellarX::ControlShape::CIRCLE:
|
||||
case StellarX::ControlShape::B_CIRCLE:
|
||||
case StellarX::ControlShape::ELLIPSE:
|
||||
case StellarX::ControlShape::B_ELLIPSE:
|
||||
this->shape = StellarX::ControlShape::RECTANGLE;
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -70,26 +86,31 @@ void Canvas::setShape(StellarX::ControlShape shape)
|
||||
void Canvas::setCanvasfillMode(StellarX::FillMode mode)
|
||||
{
|
||||
this->canvasFillMode = mode;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Canvas::setBorderColor(COLORREF color)
|
||||
{
|
||||
this->canvasBorderClor = color;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Canvas::setCanvasBkColor(COLORREF color)
|
||||
{
|
||||
this->canvasBkClor = color;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Canvas::setCanvasLineStyle(StellarX::LineStyle style)
|
||||
{
|
||||
this->canvasLineStyle = style;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
void Canvas::setLinewidth(int width)
|
||||
{
|
||||
this->canvaslinewidth = width;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "StellarX/Control.h"
|
||||
#include "Control.h"
|
||||
#include<assert.h>
|
||||
|
||||
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
||||
{
|
||||
@@ -45,20 +46,21 @@ bool StellarX::ControlText::operator!=(const ControlText& text)
|
||||
// 在控件绘制前调用,确保不会影响全局绘图状态
|
||||
void Control::saveStyle()
|
||||
{
|
||||
gettextstyle(¤tFont); // 获取当前字体样式
|
||||
currentColor = gettextcolor(); // 获取当前字体颜色
|
||||
currentBorderColor = getlinecolor(); //保存当前边框颜色
|
||||
|
||||
gettextstyle(currentFont); // 获取当前字体样式
|
||||
*currentColor = gettextcolor(); // 获取当前字体颜色
|
||||
*currentBorderColor = getlinecolor(); //保存当前边框颜色
|
||||
getlinestyle(currentLineStyle); //保存当前线型
|
||||
currentBkColor = getfillcolor(); //保存当前填充色
|
||||
*currentBkColor = getfillcolor(); //保存当前填充色
|
||||
}
|
||||
// 恢复之前保存的绘图状态
|
||||
// 在控件绘制完成后调用,恢复全局绘图状态
|
||||
void Control::restoreStyle()
|
||||
{
|
||||
settextstyle(¤tFont); // 恢复默认字体样式
|
||||
settextcolor(currentColor); // 恢复默认字体颜色
|
||||
setfillcolor(currentBkColor);
|
||||
settextstyle(currentFont); // 恢复默认字体样式
|
||||
settextcolor(*currentColor); // 恢复默认字体颜色
|
||||
setfillcolor(*currentBkColor);
|
||||
setlinestyle(currentLineStyle);
|
||||
setlinecolor(currentBorderColor);
|
||||
setlinecolor(*currentBorderColor);
|
||||
setfillstyle(BS_SOLID);//恢复填充
|
||||
}
|
||||
|
||||
673
src/Dialog.cpp
Normal file
673
src/Dialog.cpp
Normal file
@@ -0,0 +1,673 @@
|
||||
#include "Dialog.h"
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Dialog::~Dialog()
|
||||
{
|
||||
}
|
||||
|
||||
void Dialog::draw()
|
||||
{
|
||||
if(!isVisible)
|
||||
{
|
||||
// 如果对话框不可见且需要清理,执行清理
|
||||
if (pendingCleanup && !isCleaning)
|
||||
{
|
||||
performDelayedCleanup();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 如果需要初始化,则执行初始化
|
||||
if (needsInitialization && isVisible)
|
||||
{
|
||||
initDialogSize();
|
||||
needsInitialization = false;
|
||||
}
|
||||
|
||||
if (dirty && isVisible)
|
||||
{
|
||||
// 保存当前绘图状态
|
||||
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);
|
||||
}
|
||||
|
||||
Canvas::setBorderColor(this->borderColor);
|
||||
Canvas::setLinewidth(this->BorderWidth);
|
||||
Canvas::setCanvasBkColor(this->backgroundColor);
|
||||
Canvas::setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||
|
||||
//设置所有控件为脏状态
|
||||
for(auto& c :this->controls)
|
||||
c->setDirty(true);
|
||||
|
||||
Canvas::draw();
|
||||
|
||||
//绘制消息文本
|
||||
settextcolor(textStyle.color);
|
||||
|
||||
//设置字体样式
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||
|
||||
|
||||
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
|
||||
for (auto line:lines)
|
||||
{
|
||||
int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标
|
||||
outtextxy(tx, ty, LPCTSTR(line.c_str()));
|
||||
ty = ty + textheight(LPCTSTR(line.c_str())) + 5; // 每行文本高度加5像素间距
|
||||
}
|
||||
|
||||
// 恢复绘图状态
|
||||
restoreStyle();
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Dialog::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
bool consume = false;
|
||||
if (!isVisible)
|
||||
{
|
||||
if (pendingCleanup && !isCleaning)
|
||||
{
|
||||
performDelayedCleanup();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果正在清理或标记为待清理,则不处理事件
|
||||
if (pendingCleanup || isCleaning)
|
||||
return false;
|
||||
// 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。
|
||||
if (modal && msg.message == WM_LBUTTONUP &&
|
||||
(msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height))
|
||||
{
|
||||
std::cout << "\a" << std::endl;
|
||||
// 模态对话框不允许点击外部区域
|
||||
return true;
|
||||
}
|
||||
|
||||
// 将事件传递给子控件处理
|
||||
if (!consume)
|
||||
consume = Canvas::handleEvent(msg);
|
||||
|
||||
// 每次事件处理后检查是否需要执行延迟清理
|
||||
if (pendingCleanup && !isCleaning)
|
||||
performDelayedCleanup();
|
||||
return consume;
|
||||
}
|
||||
|
||||
void Dialog::SetTitle(const std::string& title)
|
||||
{
|
||||
this->titleText = title;
|
||||
if (this->title)
|
||||
{
|
||||
this->title->setText(title);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Dialog::SetMessage(const std::string& message)
|
||||
{
|
||||
this->message = message;
|
||||
splitMessageLines();
|
||||
getTextSize();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Dialog::SetType(StellarX::MessageBoxType type)
|
||||
{
|
||||
this->type = type;
|
||||
// 重新初始化按钮
|
||||
initButtons();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Dialog::SetModal(bool modal)
|
||||
{
|
||||
this->modal = modal;
|
||||
}
|
||||
|
||||
|
||||
void Dialog::SetResult(StellarX::MessageBoxResult result)
|
||||
{
|
||||
this->result = result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void Dialog::Show()
|
||||
{
|
||||
if (pendingCleanup)
|
||||
performDelayedCleanup();
|
||||
|
||||
isVisible = true;
|
||||
dirty = true;
|
||||
needsInitialization = true;
|
||||
close = false;
|
||||
shouldClose = false;
|
||||
|
||||
if (modal)
|
||||
{
|
||||
// 模态对话框需要阻塞当前线程直到对话框关闭
|
||||
while (isVisible && !close)
|
||||
{
|
||||
// 处理消息
|
||||
ExMessage msg;
|
||||
if (peekmessage(&msg, EX_MOUSE | EX_KEY))
|
||||
{
|
||||
handleEvent(msg);
|
||||
|
||||
// 检查是否需要关闭
|
||||
if (shouldClose)
|
||||
{
|
||||
Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 重绘
|
||||
if (dirty)
|
||||
{
|
||||
draw();
|
||||
FlushBatchDraw();
|
||||
}
|
||||
|
||||
// 避免CPU占用过高
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
// 模态对话框关闭后执行清理
|
||||
if (pendingCleanup && !isCleaning)
|
||||
{
|
||||
performDelayedCleanup();
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非模态对话框只需标记为可见,由主循环处理
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Dialog::Close()
|
||||
{
|
||||
if (!isVisible) return;
|
||||
|
||||
isVisible = false;
|
||||
close = true;
|
||||
dirty = true;
|
||||
pendingCleanup = true; // 只标记需要清理,不立即执行
|
||||
auto& c = hWnd.getControls();
|
||||
for(auto& control:c)
|
||||
control->setDirty(true);
|
||||
|
||||
// 工厂模式下非模态触发回调 返回结果
|
||||
if (resultCallback&& !modal)
|
||||
resultCallback(this->result);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Dialog::initButtons()
|
||||
{
|
||||
controls.clear();
|
||||
|
||||
switch (this->type)
|
||||
{
|
||||
case StellarX::MessageBoxType::OK: // 只有确定按钮
|
||||
{
|
||||
auto okbutton = createDialogButton((this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||
"确定"
|
||||
);
|
||||
okbutton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::OK);
|
||||
this->Close(); });
|
||||
|
||||
okbutton->textStyle = this->textStyle;
|
||||
|
||||
this->addControl(std::move(okbutton));
|
||||
}
|
||||
break;
|
||||
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
|
||||
{
|
||||
auto okButton = createDialogButton(
|
||||
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||
"确定"
|
||||
);
|
||||
okButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::OK);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
auto cancelButton = createDialogButton(
|
||||
(okButton.get()->getButtonX() + okButton.get()->getButtonWidth() + buttonMargin),
|
||||
okButton.get()->getButtonY(),
|
||||
"取消"
|
||||
);
|
||||
cancelButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
okButton->textStyle = this->textStyle;
|
||||
cancelButton->textStyle = this->textStyle;
|
||||
|
||||
this->addControl(std::move(okButton));
|
||||
this->addControl(std::move(cancelButton));
|
||||
}
|
||||
break;
|
||||
case StellarX::MessageBoxType::YesNo: // 是和否按钮
|
||||
{
|
||||
auto yesButton = createDialogButton(
|
||||
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||
"是"
|
||||
);
|
||||
yesButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::Yes);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
auto noButton = createDialogButton(
|
||||
(yesButton.get()->getButtonX() + yesButton.get()->getButtonWidth() + buttonMargin),
|
||||
yesButton.get()->getButtonY(),
|
||||
"否"
|
||||
);
|
||||
noButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::No);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
yesButton->textStyle = this->textStyle;
|
||||
noButton->textStyle = this->textStyle;
|
||||
|
||||
this->addControl(std::move(yesButton));
|
||||
this->addControl(std::move(noButton));
|
||||
}
|
||||
break;
|
||||
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
|
||||
{
|
||||
auto yesButton = createDialogButton(
|
||||
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||
"是"
|
||||
);
|
||||
yesButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::Yes);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
auto noButton = createDialogButton(
|
||||
yesButton.get()->getButtonX() + yesButton.get()->getButtonWidth() + buttonMargin,
|
||||
yesButton.get()->getButtonY(),
|
||||
"否"
|
||||
);
|
||||
noButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::No);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
auto cancelButton = createDialogButton(
|
||||
noButton.get()->getButtonX() + noButton.get()->getButtonWidth() + buttonMargin,
|
||||
noButton.get()->getButtonY(),
|
||||
"取消"
|
||||
);
|
||||
cancelButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
yesButton->textStyle = this->textStyle;
|
||||
noButton->textStyle = this->textStyle;
|
||||
cancelButton->textStyle = this->textStyle;
|
||||
|
||||
|
||||
this->addControl(std::move(yesButton));
|
||||
this->addControl(std::move(noButton));
|
||||
this->addControl(std::move(cancelButton));
|
||||
}
|
||||
break;
|
||||
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
|
||||
{
|
||||
auto retryButton = createDialogButton(
|
||||
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
|
||||
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||
"重试"
|
||||
);
|
||||
retryButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::Retry);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
auto cancelButton = createDialogButton(
|
||||
retryButton.get()->getButtonX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||
retryButton.get()->getButtonY(),
|
||||
"取消"
|
||||
);
|
||||
cancelButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
retryButton->textStyle = this->textStyle;
|
||||
cancelButton->textStyle = this->textStyle;
|
||||
|
||||
this->addControl(std::move(retryButton));
|
||||
this->addControl(std::move(cancelButton));
|
||||
}
|
||||
break;
|
||||
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
|
||||
{
|
||||
auto abortButton = createDialogButton(
|
||||
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin* (buttonNum-1))) / 2),
|
||||
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
|
||||
"中止"
|
||||
);
|
||||
abortButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::Abort);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close();
|
||||
});
|
||||
auto retryButton = createDialogButton(
|
||||
abortButton.get()->getButtonX() + abortButton.get()->getButtonWidth() + buttonMargin,
|
||||
abortButton.get()->getButtonY(),
|
||||
"重试"
|
||||
);
|
||||
retryButton->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::Retry);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close();
|
||||
});
|
||||
auto ignoreButton = createDialogButton(
|
||||
retryButton.get()->getButtonX() + retryButton.get()->getButtonWidth() + buttonMargin,
|
||||
retryButton.get()->getButtonY(),
|
||||
"忽略"
|
||||
);
|
||||
ignoreButton.get()->setOnClickListener([this]()
|
||||
{
|
||||
this->SetResult(StellarX::MessageBoxResult::Ignore);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close();
|
||||
});
|
||||
|
||||
abortButton->textStyle = this->textStyle;
|
||||
retryButton->textStyle = this->textStyle;
|
||||
ignoreButton->textStyle = this->textStyle;
|
||||
|
||||
this->addControl(std::move(abortButton));
|
||||
this->addControl(std::move(retryButton));
|
||||
this->addControl(std::move(ignoreButton));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::initCloseButton()
|
||||
{
|
||||
//初始化关闭按钮
|
||||
auto but = std::make_unique<Button>
|
||||
(
|
||||
(this->x + this->width - closeButtonWidth) - 3, (this->y+3), closeButtonWidth-1, closeButtonHeight,
|
||||
"×", // 按钮文本
|
||||
RGB(255, 0, 0), // 按钮被点击颜色
|
||||
this->canvasBkClor, // 按钮背景颜色
|
||||
RGB(255, 0, 0), // 按钮被悬停颜色
|
||||
StellarX::ButtonMode::NORMAL,
|
||||
StellarX::ControlShape::B_RECTANGLE
|
||||
);
|
||||
but.get()->setButtonFalseColor(this->backgroundColor);
|
||||
but->setOnClickListener([this]() {
|
||||
this->SetResult(StellarX::MessageBoxResult::Cancel);
|
||||
this->hWnd.dialogClose = true;
|
||||
this->Close(); });
|
||||
|
||||
this->closeButton = but.get();
|
||||
this->addControl(std::move(but));
|
||||
}
|
||||
|
||||
void Dialog::initTitle()
|
||||
{
|
||||
this->title = std::make_unique<Label>(this->x+5,this->y+5,titleText,textStyle.color);
|
||||
title->setTextdisap(true);
|
||||
title->textStyle = this->textStyle;
|
||||
|
||||
this->addControl(std::move(title));
|
||||
}
|
||||
|
||||
void Dialog::splitMessageLines()
|
||||
{
|
||||
lines.clear(); // 先清空现有的行
|
||||
|
||||
std::string currentLine;
|
||||
for (size_t i = 0; i < message.length(); i++) {
|
||||
// 处理 换行符 \r\n \n \r
|
||||
if (i + 1 < message.length() && (message[i] == '\r' || message[i] == '\n')||(message[i] == '\r' && message[i+1] == '\n'))
|
||||
{
|
||||
if (!currentLine.empty()) {
|
||||
lines.push_back(currentLine);
|
||||
currentLine.clear();
|
||||
}
|
||||
|
||||
if (message[i] == '\r' && message[i + 1] == '\n')
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
currentLine += message[i];
|
||||
}
|
||||
|
||||
// 添加最后一行(如果有内容)
|
||||
if (!currentLine.empty()) {
|
||||
lines.push_back(currentLine);
|
||||
}
|
||||
|
||||
// 如果消息为空,至少添加一个空行
|
||||
if (lines.empty()) {
|
||||
lines.push_back("");
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::getTextSize()
|
||||
{
|
||||
saveStyle();
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||
for (auto text : lines)
|
||||
{
|
||||
int w = textwidth(LPCTSTR(text.c_str()));
|
||||
int h = textheight(LPCTSTR(text.c_str()));
|
||||
if (this->textHeight < h)
|
||||
this->textHeight = h;
|
||||
if (this->textWidth < w)
|
||||
this->textWidth = w;
|
||||
}
|
||||
restoreStyle();
|
||||
}
|
||||
// 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。
|
||||
// 对话框高度 = 标题栏 + 文本区 + 按钮区 + 各种间距。
|
||||
void Dialog::initDialogSize()
|
||||
{
|
||||
splitMessageLines(); // 分割消息行
|
||||
getTextSize(); // 获取文本最大尺寸
|
||||
|
||||
// 获取功能按钮数量
|
||||
switch (this->type)
|
||||
{
|
||||
case StellarX::MessageBoxType::OK: // 只有确定按钮
|
||||
buttonNum = 1;
|
||||
break;
|
||||
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
|
||||
case StellarX::MessageBoxType::YesNo: // 是和否按钮
|
||||
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
|
||||
buttonNum = 2;
|
||||
break;
|
||||
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
|
||||
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
|
||||
buttonNum = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
// 计算按钮区域宽度
|
||||
int buttonAreaWidth = buttonNum * functionButtonWidth +
|
||||
(buttonNum > 0 ? (buttonNum +1) * buttonMargin : 0);
|
||||
|
||||
// 计算文本区域宽度(包括边距)
|
||||
int textAreaWidth = textWidth + textToBorderMargin * 2 ;
|
||||
|
||||
// 对话框宽度取两者中的较大值,并确保最小宽度
|
||||
this->width = buttonAreaWidth > textAreaWidth ? buttonAreaWidth : textAreaWidth;
|
||||
this->width = this->width > 200 ? this->width : 200;
|
||||
|
||||
// 计算对话框高度
|
||||
// 高度 = 标题栏高度 + 文本区域高度 + 按钮区域高度 + 间距
|
||||
int textAreaHeight = textHeight * (int)lines.size() + 5*((int)lines.size()-1); // 文本行高+行间距
|
||||
this->height = closeButtonHeight + // 标题栏高度
|
||||
titleToTextMargin + // 标题到文本的间距
|
||||
textAreaHeight + // 文本区域高度
|
||||
buttonAreaHeight; // 按钮区域高度
|
||||
|
||||
// 居中定位对话框
|
||||
this->x = (hWnd.getWidth() - this->width) / 2;
|
||||
this->y = (hWnd.getHeight() - this->height) / 2;
|
||||
|
||||
//this->textStyle.nWidth = 10;
|
||||
this->textStyle.nHeight = 20;
|
||||
|
||||
initButtons(); // 初始化按钮
|
||||
initTitle(); // 初始化标题标签
|
||||
initCloseButton(); // 初始化关闭按钮
|
||||
}
|
||||
|
||||
void Dialog::initializeDialog()
|
||||
{
|
||||
needsInitialization = true;
|
||||
pendingCleanup = false;
|
||||
isCleaning = false;
|
||||
close = false;
|
||||
isVisible = false;
|
||||
|
||||
}
|
||||
|
||||
// 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、
|
||||
// 所有控件析构前恢复背景,否则会导致背景图像被错误覆盖。
|
||||
// 此方法在对话框不可见且被标记为待清理时由 draw() 或 handleEvent() 调用。
|
||||
void Dialog::performDelayedCleanup()
|
||||
{
|
||||
if (isCleaning) return;
|
||||
|
||||
isCleaning = true;
|
||||
|
||||
// 清除所有控件
|
||||
controls.clear();
|
||||
|
||||
// 重置指针
|
||||
closeButton = nullptr;
|
||||
title.reset();
|
||||
|
||||
// 释放背景图像资源
|
||||
if (saveBkImage)
|
||||
{
|
||||
// 恢复背景
|
||||
putimage(saveBkX, saveBkY, saveBkImage);
|
||||
delete saveBkImage;
|
||||
saveBkImage = nullptr;
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
needsInitialization = true;
|
||||
pendingCleanup = false;
|
||||
isCleaning = false;
|
||||
shouldClose = false;
|
||||
}
|
||||
|
||||
void Dialog::SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb)
|
||||
{
|
||||
resultCallback = std::move(cb);
|
||||
}
|
||||
|
||||
std::string Dialog::GetCaption() const
|
||||
{
|
||||
return titleText;
|
||||
}
|
||||
|
||||
void Dialog::clearControls()
|
||||
{
|
||||
controls.clear();
|
||||
// 重置按钮指针
|
||||
closeButton = nullptr;
|
||||
title.reset(); // 释放标题资源
|
||||
}
|
||||
|
||||
std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::string& text)
|
||||
{
|
||||
auto btn = std::make_unique<Button>(
|
||||
x, y, functionButtonWidth, functionButtonHeight,
|
||||
text,
|
||||
buttonTrueColor, // 点击色
|
||||
buttonFalseColor, // 背景色
|
||||
buttonHoverColor, // 悬停色
|
||||
StellarX::ButtonMode::NORMAL,
|
||||
StellarX::ControlShape::RECTANGLE
|
||||
);
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
|
||||
32
src/MessageBox.cpp
Normal file
32
src/MessageBox.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "MessageBox.h"
|
||||
|
||||
namespace StellarX
|
||||
{
|
||||
MessageBoxResult MessageBox::ShowModal(Window& wnd,const std::string& text,const std::string& caption,
|
||||
MessageBoxType type)
|
||||
{
|
||||
Dialog dlg(wnd, caption, text, type, true); // 模态
|
||||
dlg.Show();
|
||||
return dlg.GetResult();
|
||||
}
|
||||
|
||||
void MessageBox::ShowAsync(Window& wnd,const std::string& text,const std::string& caption,MessageBoxType type,
|
||||
std::function<void(MessageBoxResult)> onResult)
|
||||
{
|
||||
//去重,如果窗口内已有相同的对话框被触发,则不再创建
|
||||
if (wnd.hasNonModalDialogWithCaption(caption))
|
||||
{
|
||||
std::cout << "\a" << std::endl;
|
||||
return;
|
||||
}
|
||||
auto dlg = std::make_unique<Dialog>(wnd, caption, text, type, false); // 非模态
|
||||
Dialog* dlgPtr = dlg.get();
|
||||
|
||||
// 设置回调
|
||||
if (onResult)
|
||||
dlgPtr->SetResultCallback(std::move(onResult));
|
||||
// 交给 Window 管理生命周期
|
||||
wnd.addDialog(std::move(dlg));
|
||||
dlgPtr->Show();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "StellarX/Label.h"
|
||||
#include "Label.h"
|
||||
|
||||
Label::Label()
|
||||
:Control(0, 0, 0, 0)
|
||||
@@ -18,38 +18,46 @@ Label::Label(int x, int y, std::string text, COLORREF textcolor, COLORREF bkColo
|
||||
|
||||
void Label::draw()
|
||||
{
|
||||
saveStyle();
|
||||
if (textBkDisap)
|
||||
setbkmode(TRANSPARENT); //设置背景透明
|
||||
else
|
||||
if(dirty)
|
||||
{
|
||||
setbkmode(OPAQUE); //设置背景不透明
|
||||
setbkcolor(textBkColor); //设置背景颜色
|
||||
saveStyle();
|
||||
if (textBkDisap)
|
||||
setbkmode(TRANSPARENT); //设置背景透明
|
||||
else
|
||||
{
|
||||
setbkmode(OPAQUE); //设置背景不透明
|
||||
setbkcolor(textBkColor); //设置背景颜色
|
||||
}
|
||||
settextcolor(textColor);
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
||||
outtextxy(x, y, LPCTSTR(text.c_str()));
|
||||
restoreStyle();
|
||||
dirty = false;
|
||||
}
|
||||
settextcolor(textColor);
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
||||
outtextxy(x,y, LPCTSTR(text.c_str()));
|
||||
restoreStyle();
|
||||
}
|
||||
|
||||
void Label::setTextdisap(bool key)
|
||||
{
|
||||
textBkDisap = key;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Label::setTextColor(COLORREF color)
|
||||
{
|
||||
textColor = color;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Label::setTextBkColor(COLORREF color)
|
||||
{
|
||||
textBkColor = color;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Label::setText(std::string text)
|
||||
{
|
||||
this->text = text;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
118
src/table.cpp
118
src/table.cpp
@@ -1,4 +1,4 @@
|
||||
#include "StellarX/Table.h"
|
||||
#include "Table.h"
|
||||
// 绘制表格的当前页
|
||||
// 使用双循环绘制行和列,考虑分页偏移
|
||||
void Table::drawTable()
|
||||
@@ -7,9 +7,9 @@ void Table::drawTable()
|
||||
dY = uY;
|
||||
uY = dY + lineHeights.at(0) + 10;
|
||||
|
||||
for (int i = (currentPage * rowsPerPage - rowsPerPage); i < (currentPage*rowsPerPage) && i < data.size(); i++)
|
||||
for (size_t i = (currentPage * rowsPerPage - rowsPerPage); i < (currentPage*rowsPerPage) && i < data.size(); i++)
|
||||
{
|
||||
for (int j = 0; j < data[i].size(); j++)
|
||||
for (size_t j = 0; j < data[i].size(); j++)
|
||||
{
|
||||
uX = dX + colWidths.at(j) + 20;
|
||||
fillrectangle(dX, dY, uX, uY);
|
||||
@@ -28,7 +28,7 @@ void Table::drawHeader()
|
||||
{
|
||||
|
||||
uY = dY + lineHeights.at(0) + 10;
|
||||
for(int i = 0; i < headers.size(); i++)
|
||||
for(size_t i = 0; i < headers.size(); i++)
|
||||
{
|
||||
uX = dX + colWidths.at(i) + 20;
|
||||
fillrectangle(dX, dY, uX, uY);
|
||||
@@ -38,9 +38,8 @@ void Table::drawHeader()
|
||||
|
||||
|
||||
}
|
||||
// 初始化文本宽度和高度计算
|
||||
// 遍历所有数据和表头,计算每列的最大宽度和行高
|
||||
// 此方法在数据变更时自动调用
|
||||
// 遍历所有数据单元和表头,计算每列的最大宽度和每行的最大高度,
|
||||
// 为后续绘制表格单元格提供尺寸依据。此计算在数据变更时自动触发。
|
||||
void Table::initTextWaH()
|
||||
{
|
||||
this->colWidths.resize(this->headers.size());
|
||||
@@ -48,9 +47,9 @@ void Table::initTextWaH()
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
//计算数据尺寸
|
||||
for (int i = 0; i < data.size(); i++)
|
||||
for (size_t i = 0; i < data.size(); i++)
|
||||
{
|
||||
for (int 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()));
|
||||
@@ -61,7 +60,7 @@ void Table::initTextWaH()
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < this->headers.size(); i++)
|
||||
for (size_t i = 0; i < this->headers.size(); i++)
|
||||
{
|
||||
width = textwidth(LPCTSTR(headers[i].c_str()));
|
||||
height = textheight(LPCTSTR(headers[i].c_str()));
|
||||
@@ -73,7 +72,7 @@ void Table::initTextWaH()
|
||||
|
||||
// 计算表格总宽度和高度
|
||||
this->width = 0;
|
||||
for (int i = 0; i < colWidths.size(); i++)
|
||||
for (size_t i = 0; i < colWidths.size(); i++)
|
||||
this->width += colWidths.at(i) + 20;
|
||||
LINESTYLE currentStyle;
|
||||
|
||||
@@ -81,6 +80,9 @@ void Table::initTextWaH()
|
||||
|
||||
this->height = lineHeights.at(0) * (rowsPerPage + 1) + rowsPerPage * 10+20 ; // 表头+数据行+页码区域
|
||||
|
||||
|
||||
// 由于表格绘制会覆盖原有区域,需要先保存背景以便在下次绘制前恢复,
|
||||
// 避免重叠绘制造成的残影问题。
|
||||
// 如果背景图像不存在或尺寸不匹配,创建或重新创建
|
||||
if (saveBkImage == nullptr) {
|
||||
saveBkImage = new IMAGE(width, height);
|
||||
@@ -95,11 +97,15 @@ void Table::initButton()
|
||||
{
|
||||
int x1, x2;
|
||||
int y1, y2;
|
||||
x1 = pX - 70;
|
||||
x1 = pX - 72;
|
||||
x2 = pX + textwidth(LPCTSTR(pageNumtext.c_str())) + 10;
|
||||
y1 = y2 = pY;
|
||||
this->prevButton = new Button(x1, y1, 60, textheight(LPCTSTR(pageNumtext.c_str())), "上一页", RGB(0, 0, 0), RGB(255, 255, 255));
|
||||
this->nextButton = new Button(x2, y2, 60, textheight(LPCTSTR(pageNumtext.c_str())), "下一页", RGB(0, 0, 0), RGB(255, 255, 255));
|
||||
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)
|
||||
{
|
||||
@@ -120,23 +126,25 @@ void Table::initButton()
|
||||
void Table::initPageNum()
|
||||
{
|
||||
if (0 == pY)
|
||||
pY = uY + lineHeights.at(0) * rowsPerPage + rowsPerPage * 10+10;
|
||||
for (int i = 0; i < colWidths.size(); i++)
|
||||
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);
|
||||
//pageNum->setTextdisap(true);
|
||||
pageNum->textStyle = this->textStyle;
|
||||
if (StellarX::FillMode::Null == tableFillMode)
|
||||
pageNum->setTextdisap(true);
|
||||
}
|
||||
|
||||
void Table::drawPageNum()
|
||||
{
|
||||
if (nullptr == pageNum)
|
||||
initPageNum();
|
||||
pageNumtext = std::to_string(currentPage);
|
||||
pageNumtext += "页/第";
|
||||
pageNumtext = "第";
|
||||
pageNumtext+= std::to_string(currentPage);
|
||||
pageNumtext += "页/共";
|
||||
pageNumtext += std::to_string(totalPages);
|
||||
pageNumtext += "页";
|
||||
pageNum->setText(pageNumtext);
|
||||
@@ -154,9 +162,8 @@ void Table::drawButton()
|
||||
}
|
||||
|
||||
Table::Table(int x, int y)
|
||||
:Control(x, y, 0,0)
|
||||
{
|
||||
//this->saveBkImage = new IMAGE(this->width,this->height);
|
||||
:Control(x, y, 0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
Table::~Table()
|
||||
@@ -200,12 +207,15 @@ void Table::draw()
|
||||
isNeedCellSize = false;
|
||||
}
|
||||
|
||||
// 在绘制表格之前捕获背景
|
||||
// 只有在第一次绘制或者尺寸变化时才需要重新捕获背景
|
||||
// 优化性能:仅在首次绘制或表格尺寸变化时捕获背景图像。
|
||||
// 由于表格绘制会覆盖原有区域,需要先保存背景以便在下次绘制前恢复,
|
||||
// 避免重叠绘制造成的残影问题。
|
||||
static bool firstDraw = true;
|
||||
if (firstDraw || isNeedDrawHeaders) {
|
||||
if (firstDraw || isNeedDrawHeaders)
|
||||
{
|
||||
// 确保在绘制任何表格内容之前捕获背景
|
||||
if (saveBkImage) {
|
||||
if (saveBkImage)
|
||||
{
|
||||
// 临时恢复样式,确保捕获正确的背景
|
||||
restoreStyle();
|
||||
if(tableBorderWidth>1)
|
||||
@@ -229,7 +239,8 @@ void Table::draw()
|
||||
}
|
||||
|
||||
// 恢复背景(清除旧内容)
|
||||
if (saveBkImage) {
|
||||
if (saveBkImage)
|
||||
{
|
||||
if (tableBorderWidth > 1)
|
||||
putimage(this->x - tableBorderWidth, this->y - tableBorderWidth, saveBkImage);
|
||||
else
|
||||
@@ -260,15 +271,18 @@ void Table::draw()
|
||||
}
|
||||
}
|
||||
|
||||
void Table::handleEvent(const ExMessage& msg)
|
||||
bool Table::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
bool consume = false;
|
||||
if(!this->isShowPageButton)
|
||||
return;
|
||||
return consume;
|
||||
else
|
||||
{
|
||||
prevButton->handleEvent(msg);
|
||||
nextButton->handleEvent(msg);
|
||||
consume = prevButton->handleEvent(msg);
|
||||
if (!consume)
|
||||
consume = nextButton->handleEvent(msg);
|
||||
}
|
||||
return consume;
|
||||
}
|
||||
|
||||
void Table::setHeaders(std::initializer_list<std::string> headers)
|
||||
@@ -281,21 +295,32 @@ void Table::setHeaders(std::initializer_list<std::string> headers)
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Table::setData(const std::vector<std::string>& data)
|
||||
void Table::setData( std::vector<std::string> data)
|
||||
{
|
||||
if (data.size() < headers.size())
|
||||
for (int i = 0; data.size() <= headers.size(); i++)
|
||||
data.push_back("");
|
||||
this->data.push_back(data);
|
||||
totalPages = (this->data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||
if (totalPages < 1)
|
||||
totalPages = 1;
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Table::setData(const std::initializer_list<std::vector<std::string>>& data)
|
||||
void Table::setData( std::initializer_list<std::vector<std::string>> data)
|
||||
{
|
||||
for (auto lis : data)
|
||||
this->data.push_back(lis);
|
||||
totalPages = (this->data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||
if (lis.size() < headers.size())
|
||||
{
|
||||
for (size_t i = lis.size(); i< headers.size(); i++)
|
||||
lis.push_back("");
|
||||
this->data.push_back(lis);
|
||||
}
|
||||
else
|
||||
this->data.push_back(lis);
|
||||
|
||||
totalPages = ((int)this->data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||
if (totalPages < 1)
|
||||
totalPages = 1;
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
@@ -305,7 +330,7 @@ void Table::setData(const std::initializer_list<std::vector<std::string>>& data)
|
||||
void Table::setRowsPerPage(int rows)
|
||||
{
|
||||
this->rowsPerPage = rows;
|
||||
totalPages = (data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||
totalPages = ((int)data.size() + rowsPerPage - 1) / rowsPerPage;
|
||||
if (totalPages < 1)
|
||||
totalPages = 1;
|
||||
isNeedCellSize = true; // 标记需要重新计算单元格尺寸
|
||||
@@ -315,16 +340,19 @@ void Table::setRowsPerPage(int rows)
|
||||
void Table::showPageButton(bool isShow)
|
||||
{
|
||||
this->isShowPageButton = isShow;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Table::setTableBorder(COLORREF color)
|
||||
{
|
||||
this->tableBorderClor = color;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Table::setTableBk(COLORREF color)
|
||||
{
|
||||
this->tableBkClor = color;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Table::setTableFillMode(StellarX::FillMode mode)
|
||||
@@ -333,16 +361,30 @@ 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);
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Table::setTableLineStyle(StellarX::LineStyle style)
|
||||
{
|
||||
this->tableLineStyle = style;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Table::setTableBorderWidth(int width)
|
||||
{
|
||||
this->tableBorderWidth = width;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
int Table::getCurrentPage() const
|
||||
@@ -352,7 +394,7 @@ int Table::getCurrentPage() const
|
||||
|
||||
int Table::getTotalPages() const
|
||||
{
|
||||
return this->totalPages;;
|
||||
return this->totalPages;
|
||||
}
|
||||
|
||||
int Table::getRowsPerPage() const
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// TextBox.cpp
|
||||
#include "StellarX/TextBox.h"
|
||||
#include "TextBox.h"
|
||||
|
||||
|
||||
TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX::TextBoxmode mode, StellarX::ControlShape shape)
|
||||
@@ -48,16 +48,17 @@ void TextBox::draw()
|
||||
outtextxy(x + 10, (y + (height - text_height) / 2), LPCTSTR(text.c_str()));
|
||||
break;
|
||||
}
|
||||
|
||||
restoreStyle();
|
||||
dirty = false; //标记不需要重绘
|
||||
}
|
||||
restoreStyle();
|
||||
dirty = false; //标记不需要重绘
|
||||
|
||||
}
|
||||
|
||||
void TextBox::handleEvent(const ExMessage& msg)
|
||||
bool TextBox::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
bool hover = false;
|
||||
bool oldClick = click;
|
||||
bool consume = false;
|
||||
|
||||
switch (shape)
|
||||
{
|
||||
@@ -66,17 +67,22 @@ void TextBox::handleEvent(const ExMessage& msg)
|
||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||
hover = (msg.x > x && msg.x < (x + width) && msg.y > y && msg.y < (y + height));//判断鼠标是否在矩形按钮内
|
||||
consume = false;
|
||||
break;
|
||||
}
|
||||
if (hover && msg.message == WM_LBUTTONUP)
|
||||
{
|
||||
click = true;
|
||||
if(StellarX::TextBoxmode::INPUT_MODE == mode)
|
||||
dirty = InputBox(LPTSTR(text.c_str()), maxCharLen,"输入框",NULL,text.c_str(), NULL ,NULL,false);
|
||||
{
|
||||
dirty = InputBox(LPTSTR(text.c_str()), (int)maxCharLen, "输入框", NULL, text.c_str(), NULL, NULL, false);
|
||||
consume = true;
|
||||
}
|
||||
else if (StellarX::TextBoxmode::READONLY_MODE == mode)
|
||||
{
|
||||
dirty = false;
|
||||
InputBox(NULL, maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
|
||||
InputBox(NULL, (int)maxCharLen, "输出框(输入无效!)", NULL, text.c_str(), NULL, NULL, false);
|
||||
consume = true;
|
||||
}
|
||||
flushmessage(EX_MOUSE | EX_KEY);
|
||||
}
|
||||
@@ -85,17 +91,20 @@ void TextBox::handleEvent(const ExMessage& msg)
|
||||
|
||||
if (click)
|
||||
click = false;
|
||||
return consume;
|
||||
}
|
||||
|
||||
void TextBox::setMode(StellarX::TextBoxmode mode)
|
||||
{
|
||||
this->mode = mode;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void TextBox::setMaxCharLen(int len)
|
||||
void TextBox::setMaxCharLen(size_t len)
|
||||
{
|
||||
if (len > 0)
|
||||
maxCharLen = len;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void TextBox::setTextBoxshape(StellarX::ControlShape shape)
|
||||
@@ -107,12 +116,14 @@ void TextBox::setTextBoxshape(StellarX::ControlShape shape)
|
||||
case StellarX::ControlShape::ROUND_RECTANGLE:
|
||||
case StellarX::ControlShape::B_ROUND_RECTANGLE:
|
||||
this->shape = shape;
|
||||
this->dirty = true;
|
||||
break;
|
||||
case StellarX::ControlShape::CIRCLE:
|
||||
case StellarX::ControlShape::B_CIRCLE:
|
||||
case StellarX::ControlShape::ELLIPSE:
|
||||
case StellarX::ControlShape::B_ELLIPSE:
|
||||
this->shape = StellarX::ControlShape::RECTANGLE;
|
||||
this->dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -120,11 +131,13 @@ void TextBox::setTextBoxshape(StellarX::ControlShape shape)
|
||||
void TextBox::setTextBoxBorder(COLORREF color)
|
||||
{
|
||||
textBoxBorderClor = color;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void TextBox::setTextBoxBk(COLORREF color)
|
||||
{
|
||||
textBoxBkClor = color;
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
std::string TextBox::getText() const
|
||||
|
||||
141
src/window.cpp
141
src/window.cpp
@@ -1,4 +1,5 @@
|
||||
#include "StellarX/Window.h"
|
||||
#include "Window.h"
|
||||
#include"Dialog.h"
|
||||
|
||||
Window::Window(int width, int height, int mode)
|
||||
{
|
||||
@@ -59,24 +60,93 @@ void Window::draw(std::string pImgFile)
|
||||
BeginBatchDraw(); // 开始批量绘制
|
||||
for (auto& control : controls)
|
||||
control->draw();
|
||||
for(auto& d: dialogs)
|
||||
d->draw();
|
||||
EndBatchDraw(); // 结束批量绘制
|
||||
|
||||
}
|
||||
// 运行主事件循环,处理用户输入和窗口消息
|
||||
// 此方法会阻塞直到窗口关闭
|
||||
// 主消息循环优先级:对话框 > 普通控件。
|
||||
// 重绘策略:为保证视觉一致性,每次有对话框状态变化(打开/关闭)时,
|
||||
// 会强制重绘所有控件。先绘制普通控件,再绘制对话框(确保对话框在最上层)。
|
||||
void Window::runEventLoop()
|
||||
{
|
||||
ExMessage msg;
|
||||
bool running = true;
|
||||
while (running) {
|
||||
msg = getmessage(EX_MOUSE | EX_KEY);
|
||||
if (msg.message == WM_CLOSE) {
|
||||
running = false;
|
||||
continue;
|
||||
|
||||
while (running)
|
||||
{
|
||||
bool consume = false;// 是否处理了消息
|
||||
|
||||
// 处理所有消息
|
||||
if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true))
|
||||
{
|
||||
if (msg.message == WM_CLOSE)
|
||||
{
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 优先处理对话框事件
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
if (d->IsVisible() && !d->model())
|
||||
consume = d->handleEvent(msg);
|
||||
if (consume)
|
||||
break;
|
||||
}
|
||||
|
||||
if(!consume)
|
||||
for (auto& c : controls)
|
||||
{
|
||||
consume = c->handleEvent(msg);
|
||||
if (consume)
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto& c : controls)
|
||||
c->handleEvent(msg);
|
||||
flushmessage(EX_MOUSE |EX_KEY |EX_CHAR|EX_WINDOW);
|
||||
//如果有对话框打开或者关闭强制重绘
|
||||
bool needredraw = false;
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
needredraw = d->IsVisible();
|
||||
if (needredraw)break;
|
||||
}
|
||||
if (needredraw || dialogClose)
|
||||
{
|
||||
// 对话框关闭后,需要手动合成一个鼠标移动消息并分发给所有普通控件,
|
||||
// 以便它们能及时更新悬停状态(hover),否则悬停状态可能保持错误状态。
|
||||
// 先把当前鼠标位置转换为客户区坐标,并合成一次 WM_MOUSEMOVE,先分发给控件更新 hover 状态
|
||||
POINT pt;
|
||||
if (GetCursorPos(&pt))
|
||||
{
|
||||
ScreenToClient(this->hWnd, &pt);
|
||||
ExMessage mm;
|
||||
mm.message = WM_MOUSEMOVE;
|
||||
mm.x =(short) pt.x;
|
||||
mm.y =(short) pt.y;
|
||||
// 只分发给 window 层控件(因为 dialog 已经关闭或即将关闭)
|
||||
for (auto& c : controls)
|
||||
c->handleEvent(mm);
|
||||
}
|
||||
|
||||
BeginBatchDraw();
|
||||
// 先绘制普通控件
|
||||
for (auto& c : controls)
|
||||
c->draw();
|
||||
// 然后绘制对话框(确保对话框在最上层)
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
if (!d->model() && d->IsVisible())
|
||||
d->setDirty(true);
|
||||
d->draw();
|
||||
}
|
||||
EndBatchDraw();
|
||||
needredraw = false;
|
||||
|
||||
}
|
||||
|
||||
// 避免CPU占用过高
|
||||
Sleep(10);
|
||||
}
|
||||
}
|
||||
@@ -108,6 +178,59 @@ void Window::addControl(std::unique_ptr<Control> control)
|
||||
this->controls.push_back(std::move(control));
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!dptr) continue;
|
||||
// 只检查 Dialog 类型的控件
|
||||
Dialog* d = dynamic_cast<Dialog*>(dptr.get());
|
||||
//检查是否有非模态对话框可见,并且消息内容一致
|
||||
if (d && d->IsVisible() && !d->model() && d->GetCaption() == caption)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
HWND Window::getHwnd() const
|
||||
{
|
||||
return hWnd;
|
||||
}
|
||||
|
||||
int Window::getWidth() const
|
||||
{
|
||||
return this->width;
|
||||
}
|
||||
|
||||
int Window::getHeight() const
|
||||
{
|
||||
return this->height;
|
||||
}
|
||||
|
||||
std::string Window::getHeadline() const
|
||||
{
|
||||
return this->headline;
|
||||
}
|
||||
|
||||
COLORREF Window::getBkcolor() const
|
||||
{
|
||||
return this->wBkcolor;
|
||||
}
|
||||
|
||||
IMAGE* Window::getBkImage() const
|
||||
{
|
||||
return this->background;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Control>>& Window::getControls()
|
||||
{
|
||||
return this->controls;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user