2025-11-28 00:35:46 +09:00

587 lines
18 KiB
C++

/*************************************************************************************************
* Description: Implementation of the custom list control.
*
* See EntryPoint.cpp for a full description of this sample.
*
*
* Copyright (C) Microsoft Corporation. All rights reserved.
*
* This source code is intended only as a supplement to Microsoft
* Development Tools and/or on-line documentation. See these other
* materials for detailed information regarding Microsoft code samples.
*
* THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
* PARTICULAR PURPOSE.
*
*************************************************************************************************/
#include "CustomControl.h"
#include "AccServer.h"
// CustomListControl class.
//
CustomListControl::CustomListControl(HWND hwnd) :
m_selectedIndex(-1), m_controlHwnd(hwnd), m_pAccServer(NULL)
{
}
// Destructor.
//
CustomListControl::~CustomListControl()
{
// Free the items in the collection.
for (int i = static_cast<int>(m_itemCollection.size()) - 1; i >= 0; i--)
{
CustomListControlItem* pItem = m_itemCollection.at(i);
delete pItem;
}
// Destroy the accessible object.
if (m_pAccServer!= NULL)
{
// Notify the accessibility object that the control no longer exists.
m_pAccServer->SetControlIsAlive(FALSE);
// Release the reference created in WM_GETOBJECT.
m_pAccServer->Release();
}
}
void CustomListControl::SetAccServer(AccServer* pAccServer)
{
m_pAccServer = pAccServer;
}
AccServer* CustomListControl::GetAccServer()
{
return m_pAccServer;
}
// Adds an item to the end of the list.
//
bool CustomListControl::AddItem(ContactStatus status, WCHAR* name)
{
if (GetCount() >= MaxItems)
{
return false;
}
CustomListControlItem* newItem = new (std::nothrow) CustomListControlItem(name);
if (newItem)
{
newItem->SetStatus(status);
// Add to collection.
m_itemCollection.push_back(newItem);
// Send WinEvent.
NotifyWinEvent(EVENT_OBJECT_CREATE, m_controlHwnd, OBJID_CLIENT,
(LONG)m_itemCollection.size());
// Initialize selection when first item is added.
if (GetSelectedIndex() < 0)
{
SelectItem(0);
}
// Force visual refresh.
InvalidateRect(m_controlHwnd, NULL, TRUE);
return true;
}
return false;
}
// Gets the item at the specified index.
//
LISTITERATOR CustomListControl::GetItemAt(int index)
{
return m_itemCollection.begin() + index;
}
// Removes the specified item.
//
bool CustomListControl::RemoveSelected()
{
int index = GetSelectedIndex();
LISTITERATOR itemToDelete = GetItemAt(index);
// Don't allow deletion of the last remaining item. This is just to
// simplify the logic of the sample.
if (GetCount() == 1)
{
return FALSE;
}
CustomListControlItem* pItem = static_cast<CustomListControlItem*>(*itemToDelete);
// Remove from list.
m_itemCollection.erase(itemToDelete);
// Delete object.
delete pItem;
// Select at the same index; if we deleted the bottom item,
// the index will be decremented.
SelectItem(GetSelectedIndex());
// Raise WinEvent.
NotifyWinEvent(EVENT_OBJECT_DESTROY, m_controlHwnd, OBJID_CLIENT, static_cast<LONG>(index) + 1);
return TRUE;
}
// Gets the index of the item at a point on the Y coordinate within the list.
//
int CustomListControl::IndexFromY(int y)
{
int index = y / ItemHeight;
if ((index < 0) || (GetCount() <= index))
{
index = -1;
}
return index;
}
// Sets the selected item.
//
void CustomListControl::SelectItem(int index)
{
m_selectedIndex = index;
if (m_selectedIndex >= static_cast<int>(m_itemCollection.size()))
{
m_selectedIndex = static_cast<int>(m_itemCollection.size()) - 1;
}
// Raise WinEvents.
NotifyWinEvent(EVENT_OBJECT_SELECTION, m_controlHwnd, OBJID_CLIENT, m_selectedIndex + 1);
if (GetIsFocused())
{
NotifyWinEvent(EVENT_OBJECT_FOCUS, m_controlHwnd, OBJID_CLIENT, m_selectedIndex + 1);
}
// Force refresh.
InvalidateRect(m_controlHwnd, NULL, TRUE);
}
// Gets the index of the selected item.
//
int CustomListControl::GetSelectedIndex()
{
return m_selectedIndex;
}
// Gets the focused state.
//
bool CustomListControl::GetIsFocused()
{
return m_hasFocus;
}
// Sets the focused state.
//
void CustomListControl::SetIsFocused(bool isFocused)
{
m_hasFocus = isFocused;
}
// Gets the count of items in the list.
//
int CustomListControl::GetCount()
{
return static_cast<int>(m_itemCollection.size());
}
// Gets the bounds of the specified item.
//
bool CustomListControl::GetItemScreenRect(int index, RECT* pRetVal)
{
if ((pRetVal == NULL) || (index >= static_cast<int>(m_itemCollection.size())) || (index < 0))
{
return false;
}
// Get the container rectangle.
RECT parentRect;
GetClientRect(m_controlHwnd, &parentRect);
// Align to size of contents.
InflateRect(&parentRect, -4, -4);
// Convert top left corner to screen coordinates.
POINT upperLeft;
upperLeft.x = parentRect.left;
upperLeft.y = parentRect.top;
ClientToScreen(m_controlHwnd, &upperLeft);
// Get coordinates of list item.
pRetVal->left = upperLeft.x;
pRetVal->right = pRetVal->left + parentRect.right - parentRect.left;
pRetVal->top = upperLeft.y + (ItemHeight * index);
pRetVal->bottom = pRetVal->top + ItemHeight;
return true;
}
// Responds to double-click on an item. For simplicity, we simply show the name
// of the selected contact.
// This method is simply to demonstrate how a default action is invoked by
// IAccessible::accDoDefaultAction.
//
void CustomListControl::OnDoubleClick()
{
LISTITERATOR item = GetItemAt(GetSelectedIndex());
CustomListControlItem* pItem = static_cast<CustomListControlItem*>(*item);
HWND h = GetParent(m_controlHwnd);
WCHAR* name = pItem->GetName();
MessageBox(h, name, TEXT("Contact"), MB_OK);
}
// Registers the control class.
//
void RegisterListControl(HINSTANCE hInstance)
{
WNDCLASS wc = {};
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wc.lpfnWndProc = ControlWndProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = TEXT("CONTACTLIST");
RegisterClass(&wc);
}
// Handles window messages for the HWND that contains the custom control.
//
LRESULT CALLBACK ControlWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
// Create the control object.
CustomListControl* pCustomList = new (std::nothrow) CustomListControl(hwnd);
// Save the class instance as window data so that its members
// can be accessed from within this function.
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pCustomList);
break;
}
case WM_DESTROY:
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
// Destroy the control.
delete pCustomList;
break;
}
case WM_GETOBJECT:
{
// Return the IAccessible object.
if (static_cast<LONG>(lParam) == OBJID_CLIENT)
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
// Create the accessible object.
AccServer* pAccServer = pCustomList->GetAccServer();
if (pAccServer == NULL)
{
pAccServer = new (std::nothrow) AccServer(hwnd, pCustomList);
pCustomList->SetAccServer(pAccServer);
}
if (pAccServer != NULL) // NULL if out of memory.
{
LRESULT Lresult = LresultFromObject(IID_IAccessible, wParam,
static_cast<IAccessible*>(pAccServer));
return Lresult;
}
else return 0;
}
break;
}
case WM_PAINT:
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
// Set up graphics context.
PAINTSTRUCT paintStruct;
HDC hdc = BeginPaint(hwnd, &paintStruct);
RECT clientRect;
GetClientRect(hwnd, &clientRect);
// Save the context.
HGDIOBJ oldHgdi = SelectObject(hdc, GetStockObject(BLACK_PEN));
// Draw items.
// Create and select a null pen so the rectangle isn't outlined.
HPEN nullPen = CreatePen(PS_NULL, 1, RGB(0,0,0));
SelectObject(hdc, nullPen);
// Erase the whole window.
Rectangle(hdc, clientRect.left, clientRect.top, clientRect.right,
clientRect.bottom);
// Create and select the font.
HFONT font = GetFont(8);
HGDIOBJ oldFont = SelectObject(hdc, font);
// Set transparency for text.
SetBkMode(hdc, TRANSPARENT);
int itemHeight = pCustomList->ItemHeight;
// Create brushes
HBRUSH unfocusedFillBrush = GetSysColorBrush(COLOR_BTNFACE);
HBRUSH focusedFillBrush = GetSysColorBrush(COLOR_HIGHLIGHT);
HBRUSH onlineFillBrush = CreateSolidBrush(RGB(0, 192, 0)); // Green.
HBRUSH offlineFillBrush = CreateSolidBrush(RGB(255, 0, 0)); // Red.
if (pCustomList->GetCount() > 0)
{
for (int i = 0; i < pCustomList->GetCount(); i++)
{
// Get the rectangle for the item.
RECT itemRect;
itemRect.left = clientRect.left + 2;
itemRect.top = clientRect.top + 2 + itemHeight * i;
itemRect.right = clientRect.right - 2;
itemRect.bottom = itemRect.top + itemHeight;
// Set the default text color.
SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
// Set up the appearance of the focused item.
// It's different depending on whether the list control has focus.
if (i == pCustomList->GetSelectedIndex())
{
if (pCustomList->GetIsFocused())
{
SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
HGDIOBJ oldBrush = SelectObject(hdc, focusedFillBrush);
Rectangle(hdc, itemRect.left+1, itemRect.top+1,
itemRect.right, itemRect.bottom);
SelectObject(hdc, oldBrush);
}
else
{
HGDIOBJ oldBrush = SelectObject(hdc, unfocusedFillBrush);
Rectangle(hdc, itemRect.left, itemRect.top, itemRect.right, itemRect.bottom);
SelectObject(hdc, oldBrush);
}
DrawFocusRect(hdc, &itemRect);
}
// Get the item.
LISTITERATOR item = pCustomList->GetItemAt(i);
CustomListControlItem* pItem = static_cast<CustomListControlItem*>(*item);
// Draw the text.
TextOut(hdc, itemRect.left + pCustomList->ImageWidth + 5, itemRect.top + 2,
pItem->GetName(), static_cast<int>(wcslen(pItem->GetName())));
// Draw the status icon.
if (pItem->GetStatus() == Status_Online)
{
SelectObject(hdc, onlineFillBrush);
Rectangle(hdc, itemRect.left + 2, itemRect.top + 3,
itemRect.left + pCustomList->ImageWidth + 2, itemRect.top + 3 + pCustomList->ImageHeight);
}
else
{
SelectObject(hdc, offlineFillBrush);
Ellipse(hdc, itemRect.left + 2, itemRect.top + 3,
itemRect.left + pCustomList->ImageWidth + 2, itemRect.top + 3 + pCustomList->ImageHeight);
}
} // for each item.
}
EndPaint(hwnd, &paintStruct);
// Restore context.
SelectObject(hdc, oldFont);
SelectObject(hdc, oldHgdi);
// Clean brushes.
DeleteObject(font);
DeleteObject(nullPen);
DeleteObject(focusedFillBrush);
DeleteObject(unfocusedFillBrush);
DeleteObject(onlineFillBrush);
DeleteObject(offlineFillBrush);
break;
}
case WM_SETFOCUS:
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
if (pCustomList != NULL)
{
pCustomList->SetIsFocused(TRUE);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
}
case WM_KILLFOCUS:
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
pCustomList->SetIsFocused(FALSE);
InvalidateRect(hwnd, NULL, TRUE);
break;
}
case CUSTOMLB_DELETEITEM:
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
pCustomList->RemoveSelected();
InvalidateRect(hwnd, NULL, TRUE);
break;
}
case CUSTOMLB_ADDITEM:
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
if (pCustomList->GetCount() < pCustomList->MaxItems)
{
pCustomList->AddItem(static_cast<ContactStatus>(wParam), (WCHAR*)lParam);
}
break;
}
case WM_GETDLGCODE:
{
// Trap arrow keys.
return DLGC_WANTARROWS | DLGC_WANTCHARS;
break;
}
case WM_LBUTTONDBLCLK:
case CUSTOMLB_DEFERDOUBLECLICK:
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
// Check that the click was on an item. If CUSTOMLB_DEFERDOUBLECLICK,
// lParam is 0.
int itemClicked = pCustomList->IndexFromY(HIWORD(lParam));
if (itemClicked >= 0)
{
pCustomList->OnDoubleClick();
}
break;
}
case WM_LBUTTONDOWN:
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
// Get the item under the cursor. This is -1 if the user clicked on a blank space.
int y = HIWORD(lParam);
int item = pCustomList->IndexFromY(y);
// Set the focus to the control regardless of whether the selection is valid.
SetFocus(hwnd);
if (item >= 0)
{
pCustomList->SelectItem(item);
}
break;
}
case WM_KEYDOWN:
// Move the selection with up/down arrows.
{
// Retrieve the control.
CustomListControl* pCustomList = GetControl(hwnd);
switch (wParam)
{
case VK_UP:
if (pCustomList->GetSelectedIndex() > 0)
{
pCustomList->SelectItem(pCustomList->GetSelectedIndex() - 1);
}
return 0;
break;
case VK_DOWN:
if (pCustomList->GetSelectedIndex() < pCustomList->GetCount() - 1)
{
pCustomList->SelectItem(pCustomList->GetSelectedIndex() + 1);
}
return 0;
break;
}
break; // WM_KEYDOWN
}
} // switch (message)
return DefWindowProc(hwnd, message, wParam, lParam);
}
// CustomListControlItem class
//
CustomListControlItem::CustomListControlItem(WCHAR* name)
{
// In case of failure, name will be set to NULL, which is acceptable.
m_name = _wcsdup(name);
}
CustomListControlItem::~CustomListControlItem()
{
free(m_name);
}
// Gets the status (online/offline) of this contact.
//
ContactStatus CustomListControlItem::GetStatus()
{
return m_status;
}
// Sets the status (online/offline) of this contact.
//
void CustomListControlItem::SetStatus(ContactStatus status)
{
m_status = status;
}
// Gets the name of the contact.
//
WCHAR* CustomListControlItem::GetName()
{
return m_name;
}
// Helper functions.
//
// Retrieves a font for list items.
//
HFONT GetFont(LONG height)
{
// Get a handle to the ANSI fixed-pitch font, and copy
// information about the font to a LOGFONT structure.
static LOGFONT lf;
GetObject(GetStockObject(ANSI_VAR_FONT), sizeof(LOGFONT), &lf);
// Change the font size.
lf.lfHeight = height;
// Create the font and return its handle.
return CreateFont(lf.lfHeight, lf.lfWidth,
lf.lfEscapement, lf.lfOrientation, lf.lfWeight,
lf.lfItalic, lf.lfUnderline, lf.lfStrikeOut, lf.lfCharSet,
lf.lfOutPrecision, lf.lfClipPrecision, lf.lfQuality,
lf.lfPitchAndFamily, lf.lfFaceName);
}