📄 platwin.cxx
字号:
SIZE textSize = {0, 0};
int len = widestItem ? strlen(widestItem) : 0;
if (unicodeMode) {
wchar_t tbuf[MAX_US_LEN];
len = UCS2FromUTF8(widestItem, len, tbuf, sizeof(tbuf)/sizeof(wchar_t)-1);
tbuf[len] = L'\0';
::GetTextExtentPoint32W(hdc, tbuf, len, &textSize);
} else {
::GetTextExtentPoint32(hdc, widestItem, len, &textSize);
}
TEXTMETRIC tm;
::GetTextMetrics(hdc, &tm);
maxCharWidth = tm.tmMaxCharWidth;
SelectFont(hdc, oldFont);
::ReleaseDC(lb, hdc);
int widthDesired = Platform::Maximum(textSize.cx, (len + 1) * tm.tmAveCharWidth);
if (width < widthDesired)
width = widthDesired;
rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
if (Length() > rows)
rcDesired.right += ::GetSystemMetrics(SM_CXVSCROLL);
AdjustWindowRect(&rcDesired);
return rcDesired;
}
int ListBoxX::TextOffset() const {
int pixWidth = const_cast<XPMSet*>(&xset)->GetWidth();
return pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2);
}
int ListBoxX::CaretFromEdge() {
PRectangle rc;
AdjustWindowRect(&rc);
return TextOffset() + TextInset.x + (0 - rc.left) - 1;
}
void ListBoxX::Clear() {
::SendMessage(lb, LB_RESETCONTENT, 0, 0);
maxItemCharacters = 0;
widestItem = NULL;
lti.Clear();
}
void ListBoxX::Append(char *s, int type) {
int index = ::SendMessage(lb, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(s));
if (index < 0)
return;
ListItemData *newItem = lti.Append(s, type);
unsigned int len = static_cast<unsigned int>(strlen(s));
if (maxItemCharacters < len) {
maxItemCharacters = len;
widestItem = newItem->text;
}
}
int ListBoxX::Length() {
return lti.Count();
}
void ListBoxX::Select(int n) {
// We are going to scroll to centre on the new selection and then select it, so disable
// redraw to avoid flicker caused by a painting new selection twice in unselected and then
// selected states
SetRedraw(false);
CentreItem(n);
::SendMessage(lb, LB_SETCURSEL, n, 0);
SetRedraw(true);
}
int ListBoxX::GetSelection() {
return ::SendMessage(lb, LB_GETCURSEL, 0, 0);
}
// This is not actually called at present
int ListBoxX::Find(const char *) {
return LB_ERR;
}
void ListBoxX::GetValue(int n, char *value, int len) {
ListItemData item = lti.Get(n);
strncpy(value, item.text, len);
value[len-1] = '\0';
}
void ListBoxX::RegisterImage(int type, const char *xpm_data) {
xset.Add(type, xpm_data);
}
void ListBoxX::ClearRegisteredImages() {
xset.Clear();
}
void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
RECT rcBox = pDrawItem->rcItem;
rcBox.left += TextOffset();
if (pDrawItem->itemState & ODS_SELECTED) {
RECT rcImage = pDrawItem->rcItem;
rcImage.right = rcBox.left;
// The image is not highlighted
::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1));
::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT));
::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
} else {
::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW));
::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT));
}
ListItemData item = lti.Get(pDrawItem->itemID);
int pixId = item.pixId;
const char *text = item.text;
int len = strlen(text);
RECT rcText = rcBox;
::InsetRect(&rcText, TextInset.x, TextInset.y);
if (unicodeMode) {
wchar_t tbuf[MAX_US_LEN];
int tlen = UCS2FromUTF8(text, len, tbuf, sizeof(tbuf)/sizeof(wchar_t)-1);
tbuf[tlen] = L'\0';
::DrawTextW(pDrawItem->hDC, tbuf, tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
} else {
::DrawText(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
}
if (pDrawItem->itemState & ODS_SELECTED) {
::DrawFocusRect(pDrawItem->hDC, &rcBox);
}
// Draw the image, if any
XPM *pxpm = xset.Get(pixId);
if (pxpm) {
Surface *surfaceItem = Surface::Allocate();
if (surfaceItem) {
surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
//surfaceItem->SetUnicodeMode(unicodeMode);
//surfaceItem->SetDBCSMode(codePage);
int left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x;
PRectangle rcImage(left, pDrawItem->rcItem.top,
left + xset.GetWidth(), pDrawItem->rcItem.bottom);
pxpm->Draw(surfaceItem, rcImage);
delete surfaceItem;
::SetTextAlign(pDrawItem->hDC, TA_TOP);
}
}
}
}
void ListBoxX::AppendListItem(const char *startword, const char *numword) {
ListItemData *item = lti.AllocItem();
item->text = startword;
if (numword) {
int pixId = 0;
char ch;
while ( (ch = *++numword) != '\0' ) {
pixId = 10 * pixId + (ch - '0');
}
item->pixId = pixId;
} else {
item->pixId = -1;
}
unsigned int len = static_cast<unsigned int>(strlen(item->text));
if (maxItemCharacters < len) {
maxItemCharacters = len;
widestItem = item->text;
}
}
void ListBoxX::SetList(const char *list, char separator, char typesep) {
// Turn off redraw while populating the list - this has a significant effect, even if
// the listbox is not visible.
SetRedraw(false);
Clear();
int size = strlen(list) + 1;
char *words = new char[size];
if (words) {
lti.SetWords(words);
memcpy(words, list, size);
char *startword = words;
char *numword = NULL;
int i = 0;
for (; words[i]; i++) {
if (words[i] == separator) {
words[i] = '\0';
if (numword)
*numword = '\0';
AppendListItem(startword, numword);
startword = words + i + 1;
numword = NULL;
} else if (words[i] == typesep) {
numword = words + i;
}
}
if (startword) {
if (numword)
*numword = '\0';
AppendListItem(startword, numword);
}
// Finally populate the listbox itself with the correct number of items
int count = lti.Count();
::SendMessage(lb, LB_INITSTORAGE, count, 0);
for (int j=0; j<count; j++) {
::SendMessage(lb, LB_ADDSTRING, 0, j+1);
}
}
SetRedraw(true);
}
void ListBoxX::AdjustWindowRect(PRectangle *rc) const {
::AdjustWindowRectEx(reinterpret_cast<RECT*>(rc), WS_THICKFRAME, false, WS_EX_WINDOWEDGE);
}
int ListBoxX::ItemHeight() const {
int itemHeight = lineHeight + (TextInset.y * 2);
int pixHeight = const_cast<XPMSet*>(&xset)->GetHeight() + (ImageInset.y * 2);
if (itemHeight < pixHeight) {
itemHeight = pixHeight;
}
return itemHeight;
}
int ListBoxX::MinClientWidth() const {
return 12 * (aveCharWidth+aveCharWidth/3);
}
Point ListBoxX::MinTrackSize() const {
PRectangle rc(0, 0, MinClientWidth(), ItemHeight());
AdjustWindowRect(&rc);
return Point(rc.Width(), rc.Height());
}
Point ListBoxX::MaxTrackSize() const {
PRectangle rc(0, 0,
maxCharWidth * maxItemCharacters + TextInset.x * 2 +
TextOffset() + ::GetSystemMetrics(SM_CXVSCROLL),
ItemHeight() * lti.Count());
AdjustWindowRect(&rc);
return Point(rc.Width(), rc.Height());
}
void ListBoxX::SetRedraw(bool on) {
::SendMessage(lb, WM_SETREDRAW, static_cast<BOOL>(on), 0);
if (on)
::InvalidateRect(lb, NULL, TRUE);
}
void ListBoxX::ResizeToCursor() {
PRectangle rc = GetPosition();
Point pt;
::GetCursorPos(reinterpret_cast<POINT*>(&pt));
pt.x += dragOffset.x;
pt.y += dragOffset.y;
switch (resizeHit) {
case HTLEFT:
rc.left = pt.x;
break;
case HTRIGHT:
rc.right = pt.x;
break;
case HTTOP:
rc.top = pt.y;
break;
case HTTOPLEFT:
rc.top = pt.y;
rc.left = pt.x;
break;
case HTTOPRIGHT:
rc.top = pt.y;
rc.right = pt.x;
break;
case HTBOTTOM:
rc.bottom = pt.y;
break;
case HTBOTTOMLEFT:
rc.bottom = pt.y;
rc.left = pt.x;
break;
case HTBOTTOMRIGHT:
rc.bottom = pt.y;
rc.right = pt.x;
break;
}
Point ptMin = MinTrackSize();
Point ptMax = MaxTrackSize();
// We don't allow the left edge to move at present, but just in case
rc.left = Platform::Maximum(Platform::Minimum(rc.left, rcPreSize.right - ptMin.x), rcPreSize.right - ptMax.x);
rc.top = Platform::Maximum(Platform::Minimum(rc.top, rcPreSize.bottom - ptMin.y), rcPreSize.bottom - ptMax.y);
rc.right = Platform::Maximum(Platform::Minimum(rc.right, rcPreSize.left + ptMax.x), rcPreSize.left + ptMin.x);
rc.bottom = Platform::Maximum(Platform::Minimum(rc.bottom, rcPreSize.top + ptMax.y), rcPreSize.top + ptMin.y);
SetPosition(rc);
}
void ListBoxX::StartResize(WPARAM hitCode) {
rcPreSize = GetPosition();
POINT cursorPos;
::GetCursorPos(&cursorPos);
switch (hitCode) {
case HTRIGHT:
case HTBOTTOM:
case HTBOTTOMRIGHT:
dragOffset.x = rcPreSize.right - cursorPos.x;
dragOffset.y = rcPreSize.bottom - cursorPos.y;
break;
case HTTOPRIGHT:
dragOffset.x = rcPreSize.right - cursorPos.x;
dragOffset.y = rcPreSize.top - cursorPos.y;
break;
// Note that the current hit test code prevents the left edge cases ever firing
// as we don't want the left edge to be moveable
case HTLEFT:
case HTTOP:
case HTTOPLEFT:
dragOffset.x = rcPreSize.left - cursorPos.x;
dragOffset.y = rcPreSize.top - cursorPos.y;
break;
case HTBOTTOMLEFT:
dragOffset.x = rcPreSize.left - cursorPos.x;
dragOffset.y = rcPreSize.bottom - cursorPos.y;
break;
default:
return;
}
::SetCapture(GetHWND());
resizeHit = hitCode;
}
int ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
int hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
// There is an apparent bug in the DefWindowProc hit test code whereby it will
// return HTTOPXXX if the window in question is shorter than the default
// window caption height + frame, even if one is hovering over the bottom edge of
// the frame, so workaround that here
if (hit >= HTTOP && hit <= HTTOPRIGHT) {
int minHeight = GetSystemMetrics(SM_CYMINTRACK);
PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
int yPos = GET_Y_LPARAM(lParam);
if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
hit += HTBOTTOM - HTTOP;
}
}
// Nerver permit resizing that moves the left edge. Allow movement of top or bottom edge
// depending on whether the list is above or below the caret
switch (hit) {
case HTLEFT:
case HTTOPLEFT:
case HTBOTTOMLEFT:
hit = HTERROR;
break;
case HTTOP:
case HTTOPRIGHT: {
PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
// Valid only if caret below list
if (location.y < rc.top)
hit = HTERROR;
}
break;
case HTBOTTOM:
case HTBOTTOMRIGHT: {
PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition();
// Valid only if caret above list
if (rc.bottom < location.y)
hit = HTERROR;
}
break;
}
return hit;
}
void ListBoxX::OnDoubleClick() {
if (doubleClickAction != NULL) {
doubleClickAction(doubleClickActionData);
}
}
Point ListBoxX::GetClientExtent() const {
PRectangle rc = const_cast<ListBoxX*>(this)->GetClientPosition();
return Point(rc.Width(), rc.Height());
}
void ListBoxX::CentreItem(int n) {
// If below mid point, scroll up to centre, but with more items below if uneven
if (n >= 0) {
Point extent = GetClientExtent();
int visible = extent.y/ItemHeight();
if (visible < Length()) {
int top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0);
int half = (visible - 1) / 2;
if (n > (top + half))
::SendMessage(lb, LB_SETTOPINDEX, n - half , 0);
}
}
}
// Performs a double-buffered paint operation to avoid flicker
void ListBoxX::Paint(HDC hDC) {
Point extent = GetClientExtent();
HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
HDC bitmapDC = ::CreateCompatibleDC(hDC);
HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
// The list background is mainly erased during painting, but can be a small
// unpainted area when at the end of a non-integrally sized list with a
// vertical scroll bar
RECT rc = { 0, 0, extent.x, extent.y };
::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
// Paint the entire client area and vertical scrollbar
::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
// Select a stock brush to prevent warnings from BoundsChecker
::SelectObject(bitmapDC, GetStockFont(WHITE_BRUSH));
SelectBitmap(bitmapDC, hBitmapOld);
::DeleteDC(bitmapDC);
::DeleteObject(hBitmap);
}
LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_ERASEBKGND:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -