Files
EzUI/sources/TextBox.cpp

838 lines
24 KiB
C++
Raw Normal View History

2026-01-24 22:42:46 +08:00
#include "TextBox.h"
#undef min
#undef max
namespace ezui {
TextBox::TextBox(Object* parentObject) :Control(parentObject)
{
Init();
}
TextBox::~TextBox() {
m_timer->Stop();
if (m_textLayout) { delete m_textLayout; }
if (m_font) { delete m_font; }
}
void TextBox::Init()
{
this->GetScrollBar()->SetWidth(5);
this->GetScrollBar()->Parent = this;
this->GetScrollBar()->OffsetCallback = [=](int offset) {
this->Offset(offset);
};
Style.Cursor = LoadCursor(Cursor::IBEAM);
m_timer = new Timer(this);
m_timer->Interval = 500;
m_timer->Tick = [this](Timer* t) {
HWND hWnd = this->Hwnd();//在对象析构前获取句柄
BeginInvoke([this, hWnd]() {//捕获hWnd
if (!::IsWindow(hWnd))return;//如果窗口已经销毁 则不往下执行
if (!this->IsEnabled() || this->IsReadOnly()) {
m_bCareShow = false;
return;
}
if (!m_careRect.IsEmptyArea() && m_focus) {
m_bCareShow = !m_bCareShow;
this->Invalidate();
}
});
};
}
void TextBox::OnRemove() {
__super::OnRemove();
m_timer->Stop();
}
void TextBox::SetPadding(int px, int py) {
2026-01-28 15:00:12 +08:00
// 兼容旧接口:水平=px 垂直=py
SetPadding4(px, py, px, py);
}
void TextBox::SetPadding4(int left, int top, int right, int bottom) {
left = std::max(0, left); top = std::max(0, top); right = std::max(0, right); bottom = std::max(0, bottom);
if (m_padLeft != left || m_padTop != top || m_padRight != right || m_padBottom != bottom) {
m_padLeft = left; m_padTop = top; m_padRight = right; m_padBottom = bottom;
Analysis();
Invalidate();
}
}
bool TextBox::ApplyStyleProperty(const UIString& key, const UIString& value) {
// 先让基类尝试
if (__super::ApplyStyleProperty(key, value)) return true;
auto parseInt = [](const UIString& v)->int {
UIString tmp = v; ui_text::Replace(&tmp, "px", ""); tmp = tmp.trim();
return std::atoi(tmp.c_str());
};
// 新增:四边独立 & 兼容旧属性
if (key == "padding-left") { SetPadding4(parseInt(value), m_padTop, m_padRight, m_padBottom); return true; }
if (key == "padding-right") { SetPadding4(m_padLeft, m_padTop, parseInt(value), m_padBottom); return true; }
if (key == "padding-top") { SetPadding4(m_padLeft, parseInt(value), m_padRight, m_padBottom); return true; }
if (key == "padding-bottom") { SetPadding4(m_padLeft, m_padTop, m_padRight, parseInt(value)); return true; }
if (key == "padding-x") { int p = parseInt(value); SetPadding4(p, m_padTop, p, m_padBottom); return true; }
if (key == "padding-y") { int p = parseInt(value); SetPadding4(m_padLeft, p, m_padRight, p); return true; }
if (key == "padding") {
UIString v = value; ui_text::Replace(&v, " ", ","); auto arr = v.split(",");
if (arr.size() == 1) { int p = parseInt(arr[0]); SetPadding4(p, p, p, p); return true; }
if (arr.size() == 2) { // 兼容旧语义:水平 垂直
int px = parseInt(arr[0]); int py = parseInt(arr[1]); SetPadding4(px, py, px, py); return true; }
if (arr.size() == 3) { // CSS: top horizontal bottom
int pTop = parseInt(arr[0]); int pH = parseInt(arr[1]); int pBottom = parseInt(arr[2]); SetPadding4(pH, pTop, pH, pBottom); return true; }
if (arr.size() == 4) { // top right bottom left
int pTop = parseInt(arr[0]); int pRight = parseInt(arr[1]); int pBottom = parseInt(arr[2]); int pLeft = parseInt(arr[3]); SetPadding4(pLeft, pTop, pRight, pBottom); return true; }
return true; // 不合法也拦截
}
return false;
}
2026-01-24 22:42:46 +08:00
void TextBox::SetAutoWidth(bool flag)
{
//需要屏蔽
}
void TextBox::SetAutoHeight(bool flag)
{
//需要屏蔽
}
void TextBox::OnKeyChar(const KeyboardEventArgs& arg)
{
__super::OnKeyChar(arg);
WPARAM wParam = arg.wParam;
LPARAM lParam = arg.lParam;
if (wParam < 32)return;//控制字符
if (IsReadOnly()) return;//只读
WCHAR buf[2]{ (WCHAR)wParam ,0 };//
InsertUnicode(std::wstring(buf));//插入新的字符
Analysis();//分析字符串
Invalidate();//刷新
}
#undef max
#undef min
void TextBox::BuildSelectedRect() {
m_selectRects.clear();
if (m_textLayout) {
//获取起点和终点
Point point1, point2;
if ((m_A_TextPos + m_A_isTrailingHit) < (m_B_TextPos + m_B_isTrailingHit)) {
point1 = m_textLayout->HitTestTextPosition(m_A_TextPos, m_A_isTrailingHit);
point2 = m_textLayout->HitTestTextPosition(m_B_TextPos, m_B_isTrailingHit);
}
else {
point2 = m_textLayout->HitTestTextPosition(m_A_TextPos, m_A_isTrailingHit);
point1 = m_textLayout->HitTestTextPosition(m_B_TextPos, m_B_isTrailingHit);
}
// 为了稳定行判定,对基线 Y 加一个行高一半偏移放到行中部
float sampleY1 = point1.Y + m_textLayout->GetFontHeight() / 2.0f;
float sampleY2 = point2.Y + m_textLayout->GetFontHeight() / 2.0f;
2026-01-24 22:42:46 +08:00
auto lineRects = m_textLayout->GetLineRects();
for (auto& lr : lineRects) {
bool firstLine = (sampleY1 >= lr.Y && sampleY1 < lr.Y + lr.Height + 0.5f);
bool lastLine = (sampleY2 >= lr.Y && sampleY2 < lr.Y + lr.Height + 0.5f);
if (firstLine && lastLine) { // 同一行
float leftX = point1.X;
float rightX = point2.X;
if (rightX < leftX) std::swap(leftX, rightX);
float width = std::max(0.0f, rightX - leftX);
m_selectRects.push_back(Rect(leftX, lr.Y, width, lr.Height));
2026-01-24 22:42:46 +08:00
}
else if (firstLine) { // 首行:到行尾
float width = std::max(0.0f, (lr.X + lr.Width) - point1.X);
m_selectRects.push_back(Rect(point1.X, lr.Y, width, lr.Height));
}
else if (lastLine) { // 末行:从行首
2026-01-24 22:42:46 +08:00
float width = std::max(0.0f, point2.X - lr.X);
m_selectRects.push_back(Rect(lr.X, lr.Y, width, lr.Height));
}
else if (sampleY1 < lr.Y && sampleY2 >= lr.Y + lr.Height) { // 中间整行
2026-01-24 22:42:46 +08:00
m_selectRects.push_back(lr);
}
}
}
}
bool TextBox::SelectedAll() {
if (m_textLayout && !m_text.empty()) {
m_pointA = Point{ 0,0 };
m_A_isTrailingHit = FALSE;
m_A_TextPos = 0;
m_pointB = Point{ m_fontBox.Width ,0 };
m_B_isTrailingHit = TRUE;
m_B_TextPos = m_text.size() - 1;
BuildSelectedRect();
return true;
}
return false;
}
bool TextBox::GetSelectedRange(int* outPos, int* outCount) {
if (m_selectRects.size() > 0) {
int pos, count;
if ((m_A_TextPos + m_A_isTrailingHit) < (m_B_TextPos + m_B_isTrailingHit)) {
int pos1 = m_A_TextPos;
if (m_A_isTrailingHit == 1) {
pos1 += 1;
}
int pos2 = m_B_TextPos;
if (m_B_isTrailingHit == 0) {
pos2 -= 1;
}
pos = pos1;
count = std::abs(pos2 - pos1) + 1;
}
else {
int pos1 = m_A_TextPos;
if (m_A_isTrailingHit == 0) {
pos1 -= 1;
}
int pos2 = m_B_TextPos;
if (m_B_isTrailingHit == 1) {
pos2 += 1;
}
pos = pos2;
count = std::abs(pos2 - pos1) + 1;
}
*outPos = pos;
*outCount = count;
if (*outCount > 0) {
return true;
}
}
return false;
}
void TextBox::InsertUnicode(const std::wstring& str, bool isEnd) {
2026-01-24 22:42:46 +08:00
DeleteRange();//先删除是否有选中的区域
if (m_textPos < 0) m_textPos = 0; // 处理光标位置
2026-01-24 22:42:46 +08:00
if (m_textPos > (int)m_text.size()) {
m_textPos = m_text.size();
}
if (isEnd) m_textPos = m_text.size(); // 是否让光标移到末尾
2026-01-24 22:42:46 +08:00
m_text.insert(m_textPos, str);
m_textPos += str.size();
if (TextChanged) {
TextChanged(UIString(m_text));
}
}
bool TextBox::DeleteRange() {
int pos, count;
if (GetSelectedRange(&pos, &count)) {//删除选中的
//isTrailingHit = FALSE;
m_textPos = pos;
m_text.erase(pos, count);
if (TextChanged) {
TextChanged(UIString(m_text));
}
return true;
}
return false;
}
bool TextBox::Copy() {
int pos, count;
if (!GetSelectedRange(&pos, &count))return false;
std::wstring wBuf(m_text.substr(pos, count));
return ezui::CopyToClipboard(wBuf, Hwnd());
}
bool TextBox::Paste() {
std::wstring wBuf;
bool bRet = ezui::GetClipboardData(&wBuf, Hwnd());
UIString u8Str(wBuf);
if (!m_allowManualLineBreak) {
//不允许手动换行时不允许有换行符
2026-01-24 22:42:46 +08:00
ui_text::Replace(&u8Str, "\r", "");
ui_text::Replace(&u8Str, "\n", "");
}
InsertUnicode(u8Str.unicode());//插入新的字符
return bRet;
}
void TextBox::OnBackspace() {
if (m_text.size() <= 0)return;
if (!DeleteRange()) {//先看看有没有有选中的需要删除
//否则删除单个字符
m_textPos--;
if (m_textPos > -1) {
m_text.erase(m_textPos, 1);
if (TextChanged) {
TextChanged(UIString(m_text));
}
}
}
}
void TextBox::OnKeyDown(const KeyboardEventArgs& arg)
{
__super::OnKeyDown(arg);
WPARAM wParam = arg.wParam;
LPARAM lParam = arg.lParam;
//判断是否按下shift+enter仅当允许手动换行时
if (m_allowManualLineBreak && (GetKeyState(VK_SHIFT) & 0x8000) && wParam == VK_RETURN) {
2026-01-24 22:42:46 +08:00
InsertUnicode(L"\n");//插入换行符
Analysis();//分析字符串
Invalidate();//刷新
return;
}
//判断是否按下Ctrl键
if (GetKeyState(VK_CONTROL) & 0x8000) {
do
{
if (wParam == 'A') {//ctrl+a全选
SelectedAll();//全选
Invalidate();//刷新
break;
}
if (wParam == 'C') {//ctrl+c 复制
Copy();
break;
}
if (wParam == 'X') {//ctrl+x 剪切
if (IsReadOnly()) {
break;
}
if (Copy()) {
DeleteRange();//因为是剪切 所以要删除选中的这段
Analysis();
Invalidate();//刷新
}
break;
}
if (wParam == 'V') {//ctrl+v 粘贴
if (IsReadOnly()) {
break;
}
Paste();
Analysis();//分析字符串
Invalidate();//刷新
break;
}
if (wParam == 'Z') {//ctrl+z撤销
if (IsReadOnly()) {
break;
}
break;
}
} while (false);
return;
}
if (wParam == VK_BACK) { //退格键(删除字符)
if (IsReadOnly()) {
return;
}
OnBackspace();//退格键的操作在里面
Analysis();//重新分析
Invalidate();//刷新
return;
}
if (wParam == VK_LEFT) {
m_textPos--;
m_bCareShow = true;
m_selectRects.clear();
BuildCare();
Invalidate();
return;
}
if (wParam == VK_RIGHT) {
++m_textPos;
m_bCareShow = true;
m_selectRects.clear();
BuildCare();
Invalidate();
return;
}
}
void TextBox::Analysis()
{
if (m_text.size() > (size_t)(this->m_maxLen)) {
m_text.erase((size_t)(this->m_maxLen));
}
m_scrollX = 0;
m_scrollY = 0;
m_pointA = Point();
m_A_isTrailingHit = 0;
m_A_TextPos = 0;
m_pointB = Point();
m_B_isTrailingHit = 0;
m_B_TextPos = 0;
m_careRect = Rect();
m_selectRects.clear();
if (m_font == NULL) return;
if (m_textLayout) delete m_textLayout;
std::wstring* drawText = &this->m_text;
if (!m_passwordChar.empty()) {
drawText = new std::wstring;
int count = m_passwordChar.size() * m_text.size();
for (size_t i = 0; i < m_text.size(); ++i)
{
*drawText += m_passwordChar;
}
}
else {
*drawText = m_text;
}
// 可用绘制宽高(考虑内边距)
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
int innerH = std::max(0, Height() - (m_padTop + m_padBottom));
if (!m_autoWrap && !m_allowManualLineBreak) {//单行编辑框(两者都不允许)
2026-01-24 22:42:46 +08:00
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
bool bAlignLeft = (int(this->TextAlign) & int(HAlign::Left));
float width = bAlignLeft ? EZUI_FLOAT_MAX : (float)innerW;
m_textLayout = new TextLayout(*drawText, *m_font, SizeF{ width,(float)innerH }, this->TextAlign);
2026-01-24 22:42:46 +08:00
m_fontBox = m_textLayout->GetFontBox();
if (m_fontBox.Width < innerW) {
2026-01-24 22:42:46 +08:00
m_scrollX = 0;
}
if (!bAlignLeft && m_fontBox.Width > innerW) {
2026-01-24 22:42:46 +08:00
ezui::TextAlign tmp = this->TextAlign;
tmp = ezui::TextAlign((int)tmp & ~(int)HAlign::Center);
tmp = ezui::TextAlign((int)tmp & ~(int)HAlign::Right);
tmp = ezui::TextAlign((int)tmp | (int)HAlign::Left);
m_textLayout->SetTextAlign(tmp);
}
if (m_fontBox.Width > innerW && m_scrollX + m_fontBox.Width < innerW) {
m_scrollX = innerW - m_fontBox.Width;
2026-01-24 22:42:46 +08:00
}
}
else {//多行编辑框(至少允许一种换行方式)
// 根据 m_autoWrap 决定是否启用自动换行
if (m_autoWrap) {
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);
} else {
m_font->Get()->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
}
m_textLayout = new TextLayout(*drawText, *m_font, SizeF{ (float)innerW, EZUI_FLOAT_MAX }, TextAlign::TopLeft);
2026-01-24 22:42:46 +08:00
m_fontBox = m_textLayout->GetFontBox();
}
if (drawText != &this->m_text) {
delete drawText;
}
// 内容尺寸:多行时加入 padding 以保证最后一行不被裁掉,单行保持原逻辑
if (m_autoWrap || m_allowManualLineBreak) {
this->SetContentSize({ m_fontBox.Width + m_padLeft + m_padRight, m_fontBox.Height + m_padTop + m_padBottom });
}
else {
this->SetContentSize({ m_fontBox.Width , m_fontBox.Height });
}
if (m_autoWrap || m_allowManualLineBreak) {
2026-01-24 22:42:46 +08:00
this->GetScrollBar()->RefreshScroll();
}
BuildCare();
}
void TextBox::BuildCare() {
if (!m_textLayout) return;
if (m_textPos < 0) {
m_textPos = 0;
}
if (m_textPos > (int)m_text.size()) {
m_textPos = m_text.size();
}
Point pt = m_textLayout->HitTestTextPosition(m_textPos, FALSE);
m_careRect.X = pt.X;
m_careRect.Y = pt.Y;
m_careRect.Height = m_textLayout->GetFontHeight();
m_careRect.Width = 1 * this->GetScale();
if (!m_autoWrap && !m_allowManualLineBreak) {
// 使光标保持在可视内区(考虑 padding
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
int caretDrawX = m_careRect.X + m_scrollX; // 布局坐标 + 滚动
if (caretDrawX < 0) { // 左越界
m_scrollX -= caretDrawX;
2026-01-24 22:42:46 +08:00
}
if (caretDrawX > innerW) { // 右越界
int offsetX = innerW - caretDrawX;
m_scrollX += offsetX;
}
}
else { // 多行:处理垂直滚动保持光标可见
int innerH = std::max(0, Height() - (m_padTop + m_padBottom));
int caretDrawY = m_careRect.Y + m_scrollY; // 仅布局+滚动
if (caretDrawY < 0) {
m_scrollY -= caretDrawY;
}
int caretBottom = caretDrawY + m_careRect.Height;
if (caretBottom > innerH) {
m_scrollY -= (caretBottom - innerH);
2026-01-24 22:42:46 +08:00
}
}
}
void TextBox::OnMouseDown(const MouseEventArgs& arg) {
__super::OnMouseDown(arg);
m_lastX = 0;
m_lastY = 0;
auto mbtn = arg.Button;
auto point = arg.Location;
if (mbtn == MouseButton::Left) {
m_down = true;
m_point_Start = ConvertPoint(point);
if (m_textLayout) {
int fontHeight;
m_selectRects.clear();
m_pointA = m_textLayout->HitTestPoint(m_point_Start, &m_A_TextPos, &m_A_isTrailingHit, &fontHeight);
m_careRect.X = m_pointA.X;
m_careRect.Y = m_pointA.Y;
m_careRect.Width = 1;
m_careRect.Height = fontHeight;
m_textPos = m_A_TextPos;
if (m_A_isTrailingHit) {
++m_textPos;
}
}
Invalidate();
}
}
void TextBox::OnMouseWheel(const MouseEventArgs& arg)
{
__super::OnMouseWheel(arg);
if (!m_autoWrap && !m_allowManualLineBreak) {//单行
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
2026-01-24 22:42:46 +08:00
int textWidth = m_fontBox.Width;
if (arg.ZDelta > 0 && textWidth > innerW) {
2026-01-24 22:42:46 +08:00
m_scrollX += std::abs(arg.ZDelta) * 0.5;
if (m_scrollX > 0) {
m_scrollX = 0;
}
Invalidate();
}
else if (arg.ZDelta<0 && textWidth>innerW) {
2026-01-24 22:42:46 +08:00
m_scrollX -= std::abs(arg.ZDelta) * 0.5;
if (-m_scrollX + innerW > textWidth) {
m_scrollX = -(textWidth - innerW);
2026-01-24 22:42:46 +08:00
}
Invalidate();
}
}
}
ScrollBar* TextBox::GetScrollBar()
{
return &m_vScrollbar;
}
void TextBox::Offset(int _sliderY) {
this->m_scrollY = _sliderY;
Invalidate();
}
void TextBox::OnLayout()
{
__super::OnLayout();
m_scrollX = 0;
m_scrollY = 0;
m_selectRects.clear();
if ((!m_autoWrap && !m_allowManualLineBreak) && Height() != m_lastHeight) {
2026-01-24 22:42:46 +08:00
m_lastHeight = Height();
Analysis();
}
if ((m_autoWrap || m_allowManualLineBreak) && Width() != m_lastWidth) {
2026-01-24 22:42:46 +08:00
m_lastWidth = Width();
Analysis();
}
this->SetContentSize({ m_fontBox.Width ,(m_autoWrap || m_allowManualLineBreak) ? m_fontBox.Height : Height() });
2026-01-24 22:42:46 +08:00
this->GetScrollBar()->RefreshScroll();
this->EndLayout();
}
Point TextBox::ConvertPoint(const Point& pt) {
// 将控件坐标转换为文本布局坐标:去掉滚动与内边距偏移
int _x = -m_scrollX - m_padLeft;
int _y = -m_scrollY - m_padTop;
return Point{ pt.X + _x, pt.Y + _y };
2026-01-24 22:42:46 +08:00
}
void TextBox::OnMouseMove(const MouseEventArgs& arg)
{
__super::OnMouseMove(arg);
auto point = arg.Location;
if (m_down) {
m_point_End = ConvertPoint(point);
if (m_textLayout) {
int fontHeight;
m_selectRects.clear();//
m_pointB = m_textLayout->HitTestPoint(m_point_End, &m_B_TextPos, &m_B_isTrailingHit, &fontHeight);
BuildSelectedRect();
if (!m_autoWrap && !m_allowManualLineBreak) {//单行
// 计算去掉左 padding 的鼠标相对文本区域坐标
int innerW = std::max(0, Width() - (m_padLeft + m_padRight));
2026-01-24 22:42:46 +08:00
int textWidth = m_fontBox.Width;
int localX = point.X - m_padLeft; // 鼠标在文本可编辑区域内的 X
if (m_lastX > point.X) { // 向左拖
2026-01-24 22:42:46 +08:00
m_lastX = point.X;
if (textWidth > innerW && m_scrollX < 0 && localX < 0) {
2026-01-24 22:42:46 +08:00
m_scrollX += 3;
Invalidate();
return;
}
}
if (m_lastX < point.X) { // 向右拖
2026-01-24 22:42:46 +08:00
m_lastX = point.X;
if (textWidth > innerW && localX > innerW) {
2026-01-24 22:42:46 +08:00
m_scrollX -= 3;
if (-m_scrollX + innerW > textWidth) {
m_scrollX = -(textWidth - innerW);
2026-01-24 22:42:46 +08:00
}
Invalidate();
return;
}
}
}
Invalidate();
}
}
}
void TextBox::OnMouseUp(const MouseEventArgs& arg)
{
__super::OnMouseUp(arg);
m_down = false;
m_lastX = 0;
m_lastY = 0;
Invalidate();
}
void TextBox::OnFocus(const FocusEventArgs& arg)
{
__super::OnFocus(arg);
m_focus = true;
m_bCareShow = true;
m_timer->Start();
Invalidate();
}
void TextBox::OnKillFocus(const KillFocusEventArgs& arg)
{
__super::OnKillFocus(arg);
m_down = false;
m_focus = false;
m_bCareShow = false;
m_timer->Stop();
this->Invalidate();
}
const UIString TextBox::GetText()
{
return UIString(this->m_text);
}
void TextBox::SetText(const UIString& text)
{
this->m_text = text.unicode();
Analysis();
}
bool TextBox::IsMultiLine()
{
return m_autoWrap || m_allowManualLineBreak;
2026-01-24 22:42:46 +08:00
}
void TextBox::SetMultiLine(bool autoWrap, bool allowManualLineBreak)
2026-01-24 22:42:46 +08:00
{
if (this->m_autoWrap != autoWrap || this->m_allowManualLineBreak != allowManualLineBreak) {
this->m_autoWrap = autoWrap;
this->m_allowManualLineBreak = allowManualLineBreak;
2026-01-24 22:42:46 +08:00
Analysis();
}
}
void TextBox::SetReadOnly(bool bReadOnly)
{
this->m_readOnly = bReadOnly;
}
bool TextBox::IsReadOnly()
{
return this->m_readOnly;
}
void TextBox::SetMaxLength(int maxLen)
{
this->m_maxLen = maxLen;
}
void TextBox::SetPlaceholderText(const UIString& text)
{
this->m_placeholder = text.unicode();
}
void TextBox::SetPasswordChar(const UIString& passwordChar)
{
this->m_passwordChar = passwordChar.unicode();
}
2026-01-28 15:00:12 +08:00
void TextBox::SetFocusAndCaret(int caretPos)
{
// 设置焦点状态
m_focus = true;
m_bCareShow = true;
m_timer->Start();
// 设置光标位置
if (caretPos < 0) {
m_textPos = (int)m_text.size(); // -1 表示末尾
} else {
m_textPos = caretPos;
}
// 确保 m_textPos 在有效范围内
if (m_textPos < 0) m_textPos = 0;
if (m_textPos > (int)m_text.size()) m_textPos = (int)m_text.size();
// 清除选中状态
m_selectRects.clear();
m_pointA = Point();
m_pointB = Point();
m_A_TextPos = m_textPos;
m_B_TextPos = m_textPos;
// 需要重绘以确保 m_font 和 m_textLayout 被初始化
// 之后 OnForePaint 中会调用 BuildCare
Invalidate();
}
2026-01-24 22:42:46 +08:00
Rect TextBox::GetCareRect()
{
Rect rect(m_careRect);
rect.X += m_scrollX + m_padLeft; // 滚动 + padding
rect.Y += m_scrollY + m_padTop;
2026-01-24 22:42:46 +08:00
return rect;
}
void TextBox::Insert(const UIString& str, bool isEnd)
2026-01-24 22:42:46 +08:00
{
InsertUnicode(str.unicode(), isEnd);
2026-01-24 22:42:46 +08:00
Analysis();//分析字符串
}
void TextBox::SetAttribute(const UIString& key, const UIString& value) {
__super::SetAttribute(key, value);
do
{
if (key == "padding" || key == "padding-x" || key == "padding-y") {
// 走统一样式解析,直接调用 ApplyStyleProperty
ApplyStyleProperty(key, value);
break;
}
2026-01-24 22:42:46 +08:00
if (key == "valign") {
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)VAlign::Top);
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)VAlign::Mid);
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)VAlign::Bottom);
VAlign v = VAlign::Mid;
if (value == "top") {
v = VAlign::Top;
}
else if (value == "bottom") {
v = VAlign::Bottom;
}
this->TextAlign = ezui::TextAlign((int)this->TextAlign | (int)v);
break;
}
if (key == "halign") {
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)HAlign::Left);
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)HAlign::Center);
this->TextAlign = ezui::TextAlign((int)this->TextAlign & ~(int)HAlign::Right);
HAlign h = HAlign::Center;
if (value == "left") {
h = HAlign::Left;
}
else if (value == "right") {
h = HAlign::Right;
}
this->TextAlign = ezui::TextAlign((int)this->TextAlign | (int)h);
break;
}
if (key == "passwordchar") {
this->SetPasswordChar(value);
break;
}
if (key == "placeholder") {
this->SetPlaceholderText(value);
break;
}
if (key == "text" || key == "value") {
this->SetText(value);
break;
}
if (key == "readonly") {
if (value == "true") {
this->SetReadOnly(true);
break;
}
if (value == "false") {
this->SetReadOnly(false);
break;
}
}
if (key == "multiline") {
if (value == "true") {
// 兼容旧版multiline=true 表示自动换行+手动换行
this->m_autoWrap = true;
this->m_allowManualLineBreak = true;
2026-01-24 22:42:46 +08:00
break;
}
if (value == "false") {
this->m_autoWrap = false;
this->m_allowManualLineBreak = false;
2026-01-24 22:42:46 +08:00
break;
}
}
} while (false);
}
void TextBox::OnForePaint(PaintEventArgs& e) {
std::wstring fontFamily = GetFontFamily();
auto fontSize = GetFontSize();
if (fontSize == 0)return;
if (m_font == NULL || ((m_font != NULL) && (m_font->GetFontFamily() != fontFamily || m_font->GetFontSize() != fontSize))) {
if (m_font != NULL) {
delete m_font;
}
m_font = new Font(fontFamily, fontSize);
Analysis();
}
2026-01-28 15:00:12 +08:00
// 如果有焦点但光标未初始化,重新构建光标
if (m_focus && m_careRect.IsEmptyArea() && m_textLayout) {
BuildCare();
}
2026-01-24 22:42:46 +08:00
Color fontColor = GetForeColor();
e.Graphics.SetFont(fontFamily, fontSize);
if (m_text.empty()) {
bool bAlignCenter = (int(this->TextAlign) & int(HAlign::Center));
if ((!bAlignCenter) || (bAlignCenter && !m_focus)) {//避免光标和placeholder重叠
Color placeholderColor = fontColor;
placeholderColor.SetA(fontColor.GetA() * 0.6);
e.Graphics.SetColor(placeholderColor);
RectF ph((float)m_padLeft, (float)m_padTop, (float)Width() - (float)(m_padLeft + m_padRight), (float)Height() - (float)(m_padTop + m_padBottom));
Font phFont(fontFamily, fontSize);
e.Graphics.SetFont(phFont);
e.Graphics.DrawString(m_placeholder, ph, (m_autoWrap || m_allowManualLineBreak) ? TextAlign::TopLeft : this->TextAlign);
2026-01-24 22:42:46 +08:00
}
}
if (m_selectRects.size() > 0) {
Color selectedColor = fontColor;
selectedColor.SetA(fontColor.GetA() * 0.35);
e.Graphics.SetColor(selectedColor);
for (auto& it : m_selectRects) {
if (!it.IsEmptyArea()) {
RectF rect(it);
rect.X += m_scrollX + m_padLeft; // 加上内边距
rect.Y += m_scrollY + m_padTop;
2026-01-24 22:42:46 +08:00
e.Graphics.FillRectangle(rect);
}
}
}
if (m_textLayout) {
e.Graphics.SetColor(fontColor);
PointF base{ (float)m_scrollX + (float)m_padLeft, (float)m_scrollY + (float)m_padTop };
e.Graphics.DrawTextLayout(*m_textLayout, base);
2026-01-24 22:42:46 +08:00
}
if (!m_careRect.IsEmptyArea() && m_focus) {
if (m_bCareShow) {
RectF rect(m_careRect.X + m_padLeft, m_careRect.Y + m_padTop, m_careRect.Width, m_careRect.Height);
2026-01-24 22:42:46 +08:00
rect.X += m_scrollX;//偏移
rect.Y += m_scrollY;
if (ezui::IsFloatEqual(rect.X, this->Width())) {
rect.X = this->Width() - rect.Width; //末尾微调
2026-01-24 22:42:46 +08:00
}
rect.Width = rect.Width * this->GetScale();
e.Graphics.SetColor(fontColor);
e.Graphics.FillRectangle(rect);
}
}
}
}