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

655 lines
16 KiB
C++

//////////////////////////////////////////////////////////////////////////
// Slider.cpp: Custom slider control.
//
// THIS CODE AND INFORMATION IS 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.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////
#include "wincontrol.h"
#include "Slider.h"
#include <windowsx.h>
namespace SliderControl
{
LPCTSTR ClassName = TEXT("SLIDER_CLASS");
LPCTSTR InstanceData = TEXT("SLIDER_PROP");
const LONG DEFAULT_MIN = 0;
const LONG DEFAULT_MAX = 100;
const LONG DEFAULT_THUMB = 0;
// GOALS of the slider class:
//
// - Application can get/set logical position.
// - When the usser clicks on the client area, the slider jumps to the clicked position.
// (This behavior is different from the slider control in the Windows common controls,
// and is more like the behavior of the seek bar in Windows Media Player.)
// Window messages:
// set thumb position (the "thumb" is the part of the slider that the user drags)
// get thumb position
// set background brush
// set thumb image
// Window notifications:
// User clicked on thumb
// User dragged thumb
// User click on non-thumb
// data:
// image of thumb
struct Slider_Info
{
// Logical units
LONG posMin; // minimum logical position
LONG posMax; // maximum logical position
LONG posThumb; // current logical position
// Client area
SIZE pxThumbSize; // real size of thumb bitmap (constant until bitmap changes
Rect rcThumb; // client area of the thumb (changes with position)
// state
BOOL bThumbDown; // User is dragging the thumb?
// GDI objects
HBRUSH hBackground;
HBITMAP hbmThumb; // Thumb bitmap
};
LRESULT CALLBACK Slider_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// Message handlers
LRESULT OnCreate(HWND hwnd);
LRESULT OnNcDestroy(HWND hwnd, Slider_Info *pInfo);
LRESULT OnPaint(HWND hwnd, Slider_Info *pInfo);
LRESULT OnLButtonDown(HWND hwnd, LONG x, LONG y, Slider_Info *pInfo);
LRESULT OnLButtonUp(HWND hwnd, LONG x, LONG y, Slider_Info *pInfo);
LRESULT OnMouseMove(HWND hwnd, LONG x, LONG y, Slider_Info *pInfo);
LRESULT OnReleaseSlider(HWND hwnd, Slider_Info *pInfo);
// Private message handlers
LRESULT OnSetThumbBitmap(HWND hwnd, WORD nID, Slider_Info *pInfo);
LRESULT OnSetBackground(HWND hwnd, HBRUSH hBrush, Slider_Info *pInfo);
LRESULT OnSetMinMax(HWND hwnd, LONG posMin, LONG posMax, Slider_Info *pInfo);
LRESULT OnSetPosition(HWND hwnd, LONG pos, Slider_Info *pInfo);
LRESULT OnGetPosition(HWND hwnd, Slider_Info *pInfo);
//--------------------------------------------------------------------------------------
// SetInfo
// Description: Store the control's instance information.
//--------------------------------------------------------------------------------------
inline BOOL SetInfo(HWND hwnd, Slider_Info *pInfo)
{
return SetProp(hwnd, InstanceData, pInfo);
}
//--------------------------------------------------------------------------------------
// GetInfo
// Description: Get the control's instance information.
//--------------------------------------------------------------------------------------
inline Slider_Info * GetInfo(HWND hwnd)
{
return (Slider_Info*)GetProp(hwnd, InstanceData);
}
//--------------------------------------------------------------------------------------
// GetThumbRect
// Description: Get the current position of the thumb rectangle.
//
// The position is updated in the rcThumb member of pInfo.
//--------------------------------------------------------------------------------------
void GetThumbRect(HWND hwnd, Slider_Info *pInfo)
{
// 1. Convert logical position to pixels:
// logical width = max position - min position
// pixel width = client area - thumb width
// logical thumb position = current position - min position
// pixel thumb position = (logical thumb position / logical width) * pixel width
Rect rc;
GetClientRect(hwnd, &rc);
LONG logWidth = pInfo->posMax - pInfo->posMin;
LONG pixWidth = rc.Width() - pInfo->pxThumbSize.cx;
LONG logPosition = pInfo->posThumb - pInfo->posMin;
LONG left = MulDiv(logPosition, pixWidth, logWidth);
// 2. Center vertically
LONG top = (rc.Height() - pInfo->pxThumbSize.cy) / 2;
pInfo->rcThumb.Set(
left,
top,
left + pInfo->pxThumbSize.cx,
top + pInfo->pxThumbSize.cy
);
}
//--------------------------------------------------------------------------------------
// PixelToLogical
// Description: Convert a pixel position to a logical position.
//
// Returns the logical position.
//--------------------------------------------------------------------------------------
LONG PixelToLogical(HWND hwnd, LONG x, Slider_Info *pInfo)
{
// % of pixel width * logical width, max = logical max
// (x / pixel width) * logical width + logical min
Rect rc;
GetClientRect(hwnd, &rc);
LONG pixWidth = rc.Width();
LONG logWidth = pInfo->posMax - pInfo->posMin;
LONG pos = MulDiv(x, logWidth, pixWidth) + pInfo->posMin;
// clamp to slider min and max
return max(pInfo->posMin, min(pos, pInfo->posMax));
}
//--------------------------------------------------------------------------------------
// NotifyParent
// Description: Send the parent window a WM_NOTIFY message with our current status.
//
// hwnd: Control window.
// code: WM_NOTIFY code. (One of the SLIDER_NOTIFY_xxx constants.)
// pInfo: Instance data.
//--------------------------------------------------------------------------------------
void NotifyParent(HWND hwnd, UINT code, Slider_Info *pInfo)
{
HWND hParent = GetParent(hwnd);
if (hParent)
{
NMSLIDER_INFO nminfo;
nminfo.hdr.hwndFrom = hwnd;
nminfo.hdr.idFrom = (UINT_PTR)GetMenu(hwnd);
nminfo.hdr.code = code;
nminfo.bSelected = pInfo->bThumbDown;
nminfo.position = pInfo->posThumb;
SendMessage(hParent, WM_NOTIFY, (WPARAM)nminfo.hdr.idFrom, (LPARAM)&nminfo);
}
}
//--------------------------------------------------------------------------------------
// Slider_WndProc
// Description: Window proc for the control.
//--------------------------------------------------------------------------------------
LRESULT CALLBACK Slider_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
Slider_Info * const pInfo = GetInfo(hwnd);
switch (uMsg)
{
case WM_CREATE:
return OnCreate(hwnd);
case WM_PAINT:
return OnPaint(hwnd, pInfo);
case WM_NCDESTROY:
return OnNcDestroy(hwnd, pInfo);
case WM_LBUTTONDOWN:
return OnLButtonDown(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), pInfo);
case WM_LBUTTONUP:
return OnLButtonUp(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), pInfo);
case WM_MOUSEMOVE:
return OnMouseMove(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), pInfo);
case WM_ENABLE:
if (wParam == FALSE) // Window is disabled. Stop tracking.
{
return OnReleaseSlider(hwnd, pInfo);
}
break;
case WM_CAPTURECHANGED:
// The window lost focus while the slider was tracking the mouse OR
// the slider released the mouse capture itself.
return OnReleaseSlider(hwnd, pInfo);
// Custom messages
case WM_SLIDER_SET_THUMB_BITMAP:
return OnSetThumbBitmap(hwnd, (WORD)wParam, pInfo);
case WM_SLIDER_SET_BACKGROUND:
return OnSetBackground(hwnd, (HBRUSH)wParam, pInfo);
case WM_SLIDER_SET_MIN_MAX:
return OnSetMinMax(hwnd, (LONG)wParam, (LONG)lParam, pInfo);
case WM_SLIDER_SET_POSITION:
return OnSetPosition(hwnd, (LONG)wParam, pInfo);
case WM_SLIDER_GET_POSITION:
return OnGetPosition(hwnd, pInfo);
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
};
LRESULT OnCreate(HWND hwnd)
{
Slider_Info *pInfo = new Slider_Info();
if (!pInfo)
{
return (LRESULT)-1;
}
ZeroMemory(pInfo, sizeof(Slider_Info));
pInfo->posMin = DEFAULT_MIN;
pInfo->posMax = DEFAULT_MAX;
pInfo->posThumb = DEFAULT_THUMB;
pInfo->bThumbDown = FALSE;
pInfo->hBackground = CreateSolidBrush(RGB(0xFF, 0x80, 0x80));
if (SetInfo(hwnd, pInfo))
{
return 0;
}
else
{
delete pInfo;
return -1;
}
}
LRESULT OnNcDestroy(HWND /*hwnd*/, Slider_Info *pInfo)
{
if (pInfo)
{
DeleteObject(pInfo->hBackground);
DeleteObject(pInfo->hbmThumb);
delete pInfo;
}
return 0;
}
LRESULT OnPaint(HWND hwnd, Slider_Info *pInfo)
{
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hwnd, &ps);
// Draw the background
if (pInfo->hBackground)
{
FillRect(hdc, &ps.rcPaint, pInfo->hBackground);
}
// Draw the thumb
if (pInfo->hbmThumb)
{
HDC hdcCompat = CreateCompatibleDC(hdc);
SelectObject(hdcCompat, pInfo->hbmThumb);
BOOL bResult = BitBlt(
hdc,
pInfo->rcThumb.left,
pInfo->rcThumb.top,
pInfo->pxThumbSize.cx,
pInfo->pxThumbSize.cy,
hdcCompat,
0, 0,
SRCCOPY
);
assert(bResult);
DeleteDC(hdcCompat);
}
EndPaint(hwnd, &ps);
return 0;
}
void SetSliderPosition(HWND hwnd, LONG pos, Slider_Info *pInfo)
{
// Invalidate the old thumb rect
InvalidateRect(hwnd, &pInfo->rcThumb, FALSE);
pInfo->posThumb = pos;
GetThumbRect(hwnd, pInfo);
// Invalidate the new thumb rect
InvalidateRect(hwnd, &pInfo->rcThumb, FALSE);
}
LRESULT OnLButtonDown(HWND hwnd, LONG x, LONG /*y*/, Slider_Info *pInfo)
{
// Move the slider to the mouse position.
SetSliderPosition(hwnd, PixelToLogical(hwnd, x, pInfo), pInfo);
// Set the thumb-down flag.
pInfo->bThumbDown = TRUE;
// Start capturing mouse moves so we can update the slider position.
SetCapture(hwnd);
// Notify the owner window that the control was selected.
NotifyParent(hwnd, SLIDER_NOTIFY_SELECT, pInfo);
return 0;
}
LRESULT OnLButtonUp(HWND hwnd, LONG /*x*/, LONG /*y*/, Slider_Info *pInfo)
{
return OnReleaseSlider(hwnd, pInfo);
}
LRESULT OnMouseMove(HWND hwnd, LONG x, LONG /*y*/, Slider_Info *pInfo)
{
// If the control is selected, update the slider position
// and notify the owner window.
if (pInfo->bThumbDown)
{
SetSliderPosition(hwnd, PixelToLogical(hwnd, x, pInfo), pInfo);
NotifyParent(hwnd, SLIDER_NOTIFY_DRAG, pInfo);
}
return 0;
}
// OnReleaseSlider: Stop tracking the slider movement.
LRESULT OnReleaseSlider(HWND hwnd, Slider_Info *pInfo)
{
if (pInfo->bThumbDown)
{
// Reset the thumb-down flag.
pInfo->bThumbDown = FALSE;
InvalidateRect(hwnd, &pInfo->rcThumb, FALSE);
// Stop capturing mouse moves.
ReleaseCapture();
// Notify the owner window that the control was deselected.
NotifyParent(hwnd, SLIDER_NOTIFY_RELEASE, pInfo);
}
return 0;
}
//--------------------------------------------------------------------------------------
// OnSetThumbBitmap
// Description: Sets the bitmap image for the slider thumb.
//
// Handler for WM_SLIDER_SET_THUMB_BITMAP message.
//--------------------------------------------------------------------------------------
LRESULT OnSetThumbBitmap(HWND hwnd, WORD nID, Slider_Info *pInfo)
{
HBITMAP hbm = LoadBitmap(GetInstance(), MAKEINTRESOURCE(nID));
if (hbm == NULL)
{
return FALSE;
}
BITMAP bm;
GetObject(hbm, sizeof(BITMAP), &bm);
pInfo->pxThumbSize.cx = bm.bmWidth;
pInfo->pxThumbSize.cy = bm.bmHeight;
if (pInfo->hbmThumb)
{
DeleteObject(pInfo->hbmThumb);
}
pInfo->hbmThumb = hbm;
GetThumbRect(hwnd, pInfo);
return TRUE;
}
//--------------------------------------------------------------------------------------
// OnSetBackground
// Description: Sets the background brush.
//
// Handler for WM_SLIDER_SET_BACKGROUND message.
//--------------------------------------------------------------------------------------
LRESULT OnSetBackground(HWND /*hwnd*/, HBRUSH hBrush, Slider_Info *pInfo)
{
if (pInfo->hBackground)
{
DeleteObject(pInfo->hBackground);
}
pInfo->hBackground = hBrush;
return TRUE;
}
//--------------------------------------------------------------------------------------
// OnSetMinMax
// Description: Sets the slider range.
//
// Handler for WM_SLIDER_SET_MIN_MAX message.
//--------------------------------------------------------------------------------------
LRESULT OnSetMinMax(HWND hwnd, LONG posMin, LONG posMax, Slider_Info *pInfo)
{
pInfo->posMin = posMin;
pInfo->posMax = posMax;
if (pInfo->posThumb < posMin)
{
SetSliderPosition(hwnd, posMin, pInfo);
}
else if (pInfo->posThumb > posMax)
{
SetSliderPosition(hwnd, posMax, pInfo);
}
return TRUE;
}
//--------------------------------------------------------------------------------------
// OnSetPosition
// Description: Sets the slider range.
//
// Handler for WM_SLIDER_SET_POSITION message.
//--------------------------------------------------------------------------------------
LRESULT OnSetPosition(HWND hwnd, LONG pos, Slider_Info *pInfo)
{
if (pos < pInfo->posMin || pos > pInfo->posMax)
{
return FALSE;
}
else
{
SetSliderPosition(hwnd, pos, pInfo);
return TRUE;
}
}
//--------------------------------------------------------------------------------------
// OnSetPosition
// Description: Sets the slider range.
//
// Handler for WM_SLIDER_GET_POSITION message.
//--------------------------------------------------------------------------------------
LRESULT OnGetPosition(HWND /*hwnd*/, Slider_Info *pInfo)
{
return pInfo->posThumb;
}
} // namespace SliderControl
//--------------------------------------------------------------------------------------
// Slider_Init
// Description: Initializes the slider window class.
//
// Call this function before using the slider control.
//--------------------------------------------------------------------------------------
HRESULT Slider_Init()
{
WNDCLASSEX wce;
ZeroMemory(&wce, sizeof(wce));
wce.cbSize = sizeof(WNDCLASSEX);
wce.lpfnWndProc = SliderControl::Slider_WndProc;
wce.hInstance = GetInstance();
wce.lpszClassName = SliderControl::ClassName;
wce.cbWndExtra = sizeof(SliderControl::Slider_Info); // Reserve space for slider instance data
ATOM a = RegisterClassEx(&wce);
if (a == 0)
{
return __HRESULT_FROM_WIN32(GetLastError());
}
else
{
return S_OK;
}
}
//--------------------------------------------------------------------------------------
// Slider_Create
// Description: Creates an instance of the slider control.
//--------------------------------------------------------------------------------------
HRESULT Slider_Create(HWND hParent, const Rect rc, DWORD_PTR id, HWND *pHwnd)
{
HWND hwnd = CreateWindowEx(
WS_EX_WINDOWEDGE,
SliderControl::ClassName,
NULL,
WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
rc.left, rc.top, rc.Width(), rc.Height(),
hParent,
(HMENU)id,
GetInstance(),
NULL
);
if (hwnd == 0)
{
return E_FAIL;
}
else
{
*pHwnd = hwnd;
return S_OK;
}
}
// Wrapper class for the flat C functions.
HRESULT Slider::Create(HWND hParent, const Rect& rcSize, DWORD_PTR id)
{
if (m_hwnd != 0)
{
return E_FAIL;
}
return Slider_Create(hParent, rcSize, id, &m_hwnd);
}
HRESULT Slider::SetThumbBitmap(UINT nId)
{
if (SendMessage(WM_SLIDER_SET_THUMB_BITMAP, nId, 0))
{
return S_OK;
}
else
{
return E_FAIL;
}
}
HRESULT Slider::SetBackground(HBRUSH hBackground)
{
if (SendMessage(WM_SLIDER_SET_BACKGROUND, (WPARAM)hBackground, 0))
{
return S_OK;
}
else
{
return E_FAIL;
}
}
LONG Slider::GetPosition() const
{
return (LONG)SendMessage(WM_SLIDER_GET_POSITION, 0, 0);
}
HRESULT Slider::SetPosition(LONG pos)
{
if (SendMessage(WM_SLIDER_SET_POSITION, pos, 0))
{
return S_OK;
}
else
{
return E_FAIL;
}
}
HRESULT Slider::SetRange(LONG min, LONG max)
{
if (SendMessage(WM_SLIDER_SET_MIN_MAX, min, max))
{
return S_OK;
}
else
{
return E_FAIL;
}
}