I need to create on my windows form c# application a custom save file dialog.
I'm used this example as starting point
https://www.codeproject.com/Articles/8086/Extending-the-save-file-dialog-class-in-NET
I have modified the code and add 2 edit control for Width and Height.
I'd like that when change text on width and lost focus, the height texbox update value proporzionally,
But I don't kwow how manage lost focus on edit control.
Here my 2 controls:
int textWidthHandle = CreateWindowEx(0, "Edit", width, ES_NUMBER | WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_BORDER, point.X + 108, point.Y + 8, 47, 20, parent, 0, 0, 0);
SendMessage((int)textWidthHandle, WM_SETFONT, fontHandle, 0);
int textHeightHandle = CreateWindowEx(0, "Edit", height, ES_NUMBER | WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_BORDER, point.X + 199, point.Y + 8, 47, 20, parent, 0, 0, 0);
SendMessage((int)textHeightHandle, WM_SETFONT, fontHandle, 0);
Can you help me please?
Here my full code:
public enum EncodingType
{
A75 = 0,
A76 = 1,
A77 = 2,
A78 = 3,
A79 = 4,
A80 = 5,
A81 = 6,
A82 = 7,
A83 = 8,
A84 = 9,
A85 = 10,
A86 = 11,
A87 = 12,
A88 = 13,
A89 = 14,
A90 = 15,
A91 = 16,
A92 = 17,
A93 = 18,
A94 = 19,
A95 = 20,
A96 = 21,
A97 = 22,
A98 = 23,
A99 = 24,
A100 = 25,
}
public class SaveFileDialogWithEncoding
{
private delegate int OFNHookProcDelegate(int hdlg, int msg, int wParam, int lParam);
private int m_LabelHandle = 0;
private int m_ComboHandle = 0;
private int m_WidthHandle = 0;
private int m_HeightHandle = 0;
private string m_Filter = "";
private string m_DefaultExt = "";
private string m_FileName = "";
private EncodingType m_EncodingType;
private int m_width;
private int m_height;
private Screen m_ActiveScreen;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct OPENFILENAME
{
public int lStructSize;
public IntPtr hwndOwner;
public int hInstance;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpstrFilter;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpstrCustomFilter;
public int nMaxCustFilter;
public int nFilterIndex;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpstrFile;
public int nMaxFile;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpstrFileTitle;
public int nMaxFileTitle;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpstrInitialDir;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpstrTitle;
public int Flags;
public short nFileOffset;
public short nFileExtension;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpstrDefExt;
public int lCustData;
public OFNHookProcDelegate lpfnHook;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpTemplateName;
//only if on nt 5.0 or higher
public int pvReserved;
public int dwReserved;
public int FlagsEx;
}
[DllImport("Comdlg32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool GetSaveFileName(ref OPENFILENAME lpofn);
[DllImport("Comdlg32.dll")]
private static extern int CommDlgExtendedError();
[DllImport("user32.dll")]
private static extern bool SetWindowPos(int hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private struct POINT
{
public int X;
public int Y;
}
private struct NMHDR
{
public int HwndFrom;
public int IdFrom;
public int Code;
}
[DllImport("user32.dll")]
private static extern bool GetWindowRect(int hWnd, ref RECT lpRect);
[DllImport("user32.dll")]
private static extern int GetParent(int hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SetWindowText(int hWnd, string lpString);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern bool SendMessage(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);
[DllImport("user32.dll")]
private static extern int SendMessage(int hWnd, int Msg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(int hWnd, int Msg, int wParam, string lParam);
[DllImport("user32.dll")]
private static extern bool DestroyWindow(int hwnd);
private const int OFN_ENABLEHOOK = 0x00000020;
private const int OFN_EXPLORER = 0x00080000;
private const int OFN_FILEMUSTEXIST = 0x00001000;
private const int OFN_HIDEREADONLY = 0x00000004;
private const int OFN_CREATEPROMPT = 0x00002000;
private const int OFN_NOTESTFILECREATE = 0x00010000;
private const int OFN_OVERWRITEPROMPT = 0x00000002;
private const int OFN_PATHMUSTEXIST = 0x00000800;
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOMOVE = 0x0002;
private const int SWP_NOZORDER = 0x0004;
private const int WM_KILLFOCUS = 0x0008;
private const int WM_INITDIALOG = 0x110;
private const int WM_DESTROY = 0x2;
private const int WM_SETFONT = 0x0030;
private const int WM_GETFONT = 0x0031;
private const int WM_COMMAND = 0x0111;
private const int WM_GETTEXT = 0x000D;
private const int CBS_DROPDOWNLIST = 0x0003;
private const int CBS_HASSTRINGS = 0x0200;
private const int CB_ADDSTRING = 0x0143;
private const int CB_SETCURSEL = 0x014E;
private const int CB_GETCURSEL = 0x0147;
private const uint WS_VISIBLE = 0x10000000;
private const uint WS_CHILD = 0x40000000;
private const uint WS_TABSTOP = 0x00010000;
private const uint WS_BORDER = 0x00800000;
private const int ES_NUMBER = 0x2000;
private const int CDN_FILEOK = -606;
private const int WM_NOTIFY = 0x004E;
private const int WM_KEYDOWN = 0x0100;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetDlgItem(int hDlg, int nIDDlgItem);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int CreateWindowEx(int dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int x, int y, int nWidth, int nHeight, int hWndParent, int hMenu, int hInstance, int lpParam);
[DllImport("user32.dll")]
private static extern bool ScreenToClient(int hWnd, ref POINT lpPoint);
private int HookProc(int hdlg, int msg, int wParam, int lParam)
{
switch (msg)
{
case WM_INITDIALOG:
//we need to centre the dialog
Rectangle sr = m_ActiveScreen.Bounds;
RECT cr = new RECT();
int parent = GetParent(hdlg);
GetWindowRect(parent, ref cr);
int x = (sr.Right + sr.Left - (cr.Right - cr.Left)) / 2;
int y = (sr.Bottom + sr.Top - (cr.Bottom - cr.Top)) / 2;
SetWindowPos(parent, 0, x, y, cr.Right - cr.Left, cr.Bottom - cr.Top + 32, SWP_NOZORDER);
//we need to find the label to position our new label under
int fileTypeWindow = GetDlgItem(parent, 0x441);
RECT aboveRect = new RECT();
GetWindowRect(fileTypeWindow, ref aboveRect);
//now convert the label's screen co-ordinates to client co-ordinates
POINT point = new POINT();
point.X = aboveRect.Left;
point.Y = aboveRect.Bottom;
ScreenToClient(parent, ref point);
//create the label
int labelHandle = CreateWindowEx(0, "STATIC", "mylabel", WS_VISIBLE | WS_CHILD | WS_TABSTOP, point.X, point.Y + 16, 200, 100, parent, 0, 0, 0);
SetWindowText(labelHandle, "Jpeg Compression:");
int fontHandle = SendMessage(fileTypeWindow, WM_GETFONT, 0, 0);
SendMessage(labelHandle, WM_SETFONT, fontHandle, 0);
//we now need to find the combo-box to position the new combo-box under
int fileComboWindow = GetDlgItem(parent, 0x470);
aboveRect = new RECT();
GetWindowRect(fileComboWindow, ref aboveRect);
point = new POINT();
point.X = aboveRect.Left;
point.Y = aboveRect.Bottom;
ScreenToClient(parent, ref point);
POINT rightPoint = new POINT();
rightPoint.X = aboveRect.Right;
rightPoint.Y = aboveRect.Top;
ScreenToClient(parent, ref rightPoint);
Form1 f = (Form1)(Application.OpenForms[0]);
//we create the new combobox
int comboHandle = CreateWindowEx(0, "ComboBox", "mycombobox", WS_VISIBLE | WS_CHILD | CBS_HASSTRINGS | CBS_DROPDOWNLIST | WS_TABSTOP, point.X, point.Y + 8, 50, 100, parent, 0, 0, 0);
SendMessage(comboHandle, WM_SETFONT, fontHandle, 0);
//create the label
int labelHandle2 = CreateWindowEx(0, "STATIC", "mylabel2", WS_VISIBLE | WS_CHILD | WS_TABSTOP, point.X + 73, point.Y + 12, 100, 100, parent, 0, 0, 0);
SetWindowText(labelHandle2, "Width:");
SendMessage(labelHandle2, WM_SETFONT, fontHandle, 0);
Size s = f.GetResolution();
string width = s.Width != 0 ? s.Width.ToString() : "";
string height = s.Height != 0 ? s.Height.ToString() : "";
if (s.Width > 0)
m_width = s.Width;
if (s.Height > 0)
m_height = s.Height;
int textWidthHandle = CreateWindowEx(0, "Edit", width, ES_NUMBER | WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_BORDER, point.X + 108, point.Y + 8, 47, 20, parent, 0, 0, 0);
SendMessage((int)textWidthHandle, WM_SETFONT, fontHandle, 0);
//create the label
int labelHandle3 = CreateWindowEx(0, "STATIC", "mylabel3", WS_VISIBLE | WS_CHILD | WS_TABSTOP, point.X + 162, point.Y + 12, 100, 100, parent, 0, 0, 0);
SetWindowText(labelHandle3, "Height:");
SendMessage(labelHandle3, WM_SETFONT, fontHandle, 0);
int textHeightHandle = CreateWindowEx(0, "Edit", height, ES_NUMBER | WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_BORDER, point.X + 199, point.Y + 8, 47, 20, parent, 0, 0, 0);
SendMessage((int)textHeightHandle, WM_SETFONT, fontHandle, 0);
//and add the encodings we want to offer
SendMessage(comboHandle, CB_ADDSTRING, 0, "75");
SendMessage(comboHandle, CB_ADDSTRING, 0, "76");
SendMessage(comboHandle, CB_ADDSTRING, 0, "77");
SendMessage(comboHandle, CB_ADDSTRING, 0, "78");
SendMessage(comboHandle, CB_ADDSTRING, 0, "79");
SendMessage(comboHandle, CB_ADDSTRING, 0, "80");
SendMessage(comboHandle, CB_ADDSTRING, 0, "81");
SendMessage(comboHandle, CB_ADDSTRING, 0, "82");
SendMessage(comboHandle, CB_ADDSTRING, 0, "83");
SendMessage(comboHandle, CB_ADDSTRING, 0, "84");
SendMessage(comboHandle, CB_ADDSTRING, 0, "85");
SendMessage(comboHandle, CB_ADDSTRING, 0, "86");
SendMessage(comboHandle, CB_ADDSTRING, 0, "87");
SendMessage(comboHandle, CB_ADDSTRING, 0, "88");
SendMessage(comboHandle, CB_ADDSTRING, 0, "89");
SendMessage(comboHandle, CB_ADDSTRING, 0, "90");
SendMessage(comboHandle, CB_ADDSTRING, 0, "91");
SendMessage(comboHandle, CB_ADDSTRING, 0, "92");
SendMessage(comboHandle, CB_ADDSTRING, 0, "93");
SendMessage(comboHandle, CB_ADDSTRING, 0, "94");
SendMessage(comboHandle, CB_ADDSTRING, 0, "95");
SendMessage(comboHandle, CB_ADDSTRING, 0, "96");
SendMessage(comboHandle, CB_ADDSTRING, 0, "97");
SendMessage(comboHandle, CB_ADDSTRING, 0, "98");
SendMessage(comboHandle, CB_ADDSTRING, 0, "99");
SendMessage(comboHandle, CB_ADDSTRING, 0, "100");
m_EncodingType = EncodingType.A94;
SendMessage(comboHandle, CB_SETCURSEL, (int)m_EncodingType, 0);
//remember the handles of the controls we have created so we can destroy them after
m_LabelHandle = labelHandle;
m_ComboHandle = comboHandle;
m_WidthHandle = textWidthHandle;
m_HeightHandle = textHeightHandle;
break;
case WM_DESTROY:
//destroy the handles we have created
if (m_ComboHandle != 0)
{
DestroyWindow(m_ComboHandle);
}
if (m_WidthHandle != 0)
{
DestroyWindow(m_WidthHandle);
}
if (m_HeightHandle != 0)
{
DestroyWindow(m_HeightHandle);
}
if (m_LabelHandle != 0)
{
DestroyWindow(m_LabelHandle);
}
break;
case WM_NOTIFY:
//we need to intercept the CDN_FILEOK message
//which is sent when the user selects a filename
NMHDR nmhdr = (NMHDR)Marshal.PtrToStructure(new IntPtr(lParam), typeof(NMHDR));
if (nmhdr.Code == CDN_FILEOK)
{
//a file has been selected
//we need to get the encoding
m_EncodingType = (EncodingType)SendMessage(m_ComboHandle, CB_GETCURSEL, 0, 0);
int max_length = GetWindowTextLength((IntPtr)m_WidthHandle);
StringBuilder widthtext = new StringBuilder(max_length + 1);
SendMessage((IntPtr)m_WidthHandle, WM_GETTEXT, widthtext.Capacity, widthtext);
int retWidth;
Int32.TryParse(widthtext.ToString(), out retWidth);
m_width = retWidth;
int max_lengthh = GetWindowTextLength((IntPtr)m_HeightHandle);
StringBuilder heighttext = new StringBuilder(max_lengthh + 1);
SendMessage((IntPtr)m_HeightHandle, WM_GETTEXT, heighttext.Capacity, heighttext);
int retHeight;
Int32.TryParse(heighttext.ToString(), out retHeight);
m_height = retHeight;
}
break;
}
return 0;
}
public string DefaultExt
{
get
{
return m_DefaultExt;
}
set
{
m_DefaultExt = value;
}
}
public string Filter
{
get
{
return m_Filter;
}
set
{
m_Filter = value;
}
}
public string FileName
{
get
{
return m_FileName;
}
set
{
m_FileName = value;
}
}
public EncodingType EncodingType
{
get
{
return m_EncodingType;
}
set
{
m_EncodingType = value;
}
}
public int Width
{
get
{
return m_width;
}
set
{
m_width = value;
}
}
public int Height
{
get
{
return m_height;
}
set
{
m_height = value;
}
}
public DialogResult ShowDialog()
{
//set up the struct and populate it
OPENFILENAME ofn = new OPENFILENAME();
ofn.lStructSize = Marshal.SizeOf(ofn);
ofn.lpstrFilter = m_Filter.Replace('|', '\0') + '\0';
ofn.lpstrFile = m_FileName + new string(' ', 512);
ofn.nMaxFile = ofn.lpstrFile.Length;
ofn.lpstrFileTitle = System.IO.Path.GetFileName(m_FileName) + new string(' ', 512);
ofn.nMaxFileTitle = ofn.lpstrFileTitle.Length;
Form1 f = (Form1)(Application.OpenForms[0]);
RadioButton radioButtonIta = (RadioButton)f.Controls["radioButtonIta"];
if (radioButtonIta.Checked)
{
ofn.lpstrTitle = "Salva immagine come";
}
else
{
ofn.lpstrTitle = "Save image as";
}
ofn.lpstrDefExt = m_DefaultExt;
//position the dialog above the active window
ofn.hwndOwner = Form.ActiveForm.Handle;
//we need to find out the active screen so the dialog box is
//centred on the correct display
m_ActiveScreen = Screen.FromControl(Form.ActiveForm);
//set up some sensible flags
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOTESTFILECREATE | OFN_ENABLEHOOK | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
//this is where the hook is set. Note that we can use a C# delegate in place of a C function pointer
ofn.lpfnHook = new OFNHookProcDelegate(HookProc);
//if we're running on Windows 98/ME then the struct is smaller
if (System.Environment.OSVersion.Platform != PlatformID.Win32NT)
{
ofn.lStructSize -= 12;
}
//show the dialog
if (!GetSaveFileName(ref ofn))
{
int ret = CommDlgExtendedError();
if (ret != 0)
{
throw new ApplicationException("Couldn't show file open dialog - " + ret.ToString());
}
return DialogResult.Cancel;
}
m_FileName = ofn.lpstrFile;
return DialogResult.OK;
}
}
Thanks
Related
I need to get the whole window's display area size, including controls that are hidden. In order to do that, I get the value from top from the very first window and the value of bottom from very last control. However, I'm getting a gap/white are in the end of the display area. See what's like in the below image. The display area is supposed to be until the Edit control's borders is seen. What am I missing?
I'm getting this size by:
int scrollHeight(void)
{
int minTop = 0;
int maxBottom = 0;
RECT rt = {0};
if(allControls_indx == 0) {
return 0;
}
#if 0
assert(allControls[0] == hMainWindow);
assert(allControls[allControls_indx - 1] == hEdit1);
#endif
if(!GetWindowRect(allControls[0], &rt)) {
ErrorExit(NAMEOF(scrollHeight), __LINE__, __FILENAME__);
}
minTop = rt.top;
if(!GetWindowRect(allControls[allControls_indx - 1], &rt)) {
ErrorExit(NAMEOF(scrollHeight), __LINE__, __FILENAME__);
}
maxBottom = rt.bottom;
return maxBottom - minTop;
}
set as scrollbar's page size like this:
setUpScrollBar(hwnd, scrollHeight());
where setUpScrollBar is defined as:
void setUpScrollBar(HWND hwnd, int h)
{
RECT rc = { 0 };
GetClientRect(hwnd, &rc);
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
si.nMin = 0;
si.nMax = h;
si.nPage = (rc.bottom - rc.top);
si.nPos = 0;
si.nTrackPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
}
full code:
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <Commctrl.h>
#include <crtdbg.h>
#include <strsafe.h>
#include <string.h>
#include <assert.h>
#ifdef UNICODE
#define STRSPLIT wcsrchr
#else
#define STRSPLIT strrchr
#endif
#define __FILENAME__ (STRSPLIT(TEXT(__FILE__), '/') ? STRSPLIT(TEXT(__FILE__), '/') + 1 : TEXT(__FILE__))
#define NAMEOF(s) TEXT(#s)
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK CreateTabProc(HWND, UINT, WPARAM, LPARAM);
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename);
void InitComControls();
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename);
DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename);
void InitComControls();
void CreateTab(HWND hwnd);
void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text);
void CreateButtons(HWND hwnd);
RECT GetLocalCoordinates(HWND hWnd);
int scrollHeight(void);
int getHeight(HWND control);
void setUpScrollBar(HWND hwnd, int);
void pushControl(HWND);
int displayArea(void);
void setScrollBarSize(HWND hwnd, int s);
inline int my_max(int a, int b);
inline int my_min(int a, int b);
void displayText(HWND h);
HINSTANCE ghInstance;
HWND hTab;
HWND hLabel1, hLabel2;
HWND hEdit1;
HWND hRemoveButton;
enum
{
IDBUTTON_REMOVE = 50
};
#define MAX_CONTROLS 8
static const wchar_t *title[] = { L"Button A1", L"Button B2", L"Button C3",
L"Button D4", L"Button E5", L"Button F6",
L"Button G" , L"Button 001", L"Button 002",
L"Button 003", L"Button 004",
L"Button 005", L"Button 006" };
HWND hButton[sizeof(title)/sizeof(title[0])] = {0};
HWND allControls[MAX_CONTROLS];
HWND hMainWindow;
int allControls_indx = 0;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow)
{
MSG msg = {0};
HWND hwnd;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Window";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
InitComControls();
if(!RegisterClass(&wc)) {
ErrorExit(NAMEOF(RegisterClass), __LINE__, __FILENAME__);
}
int width = 500;
int height = 350/2; // half than the usual size, so that the scrollbar show up and we can test it
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
int cx = (screenWidth - width) / 2;
int cy = (screenHeight - height) / 2;
hwnd = CreateWindowW(wc.lpszClassName, L"main window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
cx, cy, width, height, NULL, NULL,
hInstance, NULL);
ghInstance = hInstance;
while (GetMessage(&msg, NULL, 0, 0))
{
if (!IsDialogMessage(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static int g_scrollY;
switch(msg)
{
case WM_CREATE:
hMainWindow = hwnd;
pushControl(hwnd); // push main window too
hLabel1 = CreateWindowW(L"Static", L"This is label 1...",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
50, 10, 130, 25, hwnd, (HMENU) 18, NULL, NULL);
pushControl(hLabel1);
hLabel2 = CreateWindowW(L"Static", L"This is label 2...",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
50, 40, 130, 25, hwnd, (HMENU) 19, NULL, NULL);
hRemoveButton = CreateWindow(L"Button", L"Remove",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
200, 40, 80, 25,
hwnd,
(HMENU) IDBUTTON_REMOVE,
NULL,
NULL);
pushControl(hLabel2);
CreateTab(hwnd);
CreateButtons(hwnd);
setUpScrollBar(hwnd, scrollHeight());
break;
case WM_VSCROLL:
{
int action = LOWORD(wParam);
//HWND hScroll = (HWND)lParam;
int pos = -1;
if (action == SB_THUMBPOSITION || action == SB_THUMBTRACK) {
pos = HIWORD(wParam);
} else if (action == SB_LINEDOWN) {
pos = g_scrollY + 30;
} else if (action == SB_LINEUP) {
pos = g_scrollY - 30;
}
if (pos == -1)
break;
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS;
si.nPos = pos;
si.nTrackPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
GetScrollInfo(hwnd, SB_VERT, &si);
pos = si.nPos;
POINT pt;
pt.x = 0;
pt.y = pos - g_scrollY;
HDC hdc = GetDC(hwnd);
LPtoDP(hdc, &pt, 1);
ReleaseDC(hwnd, hdc);
ScrollWindow(hwnd, 0, -pt.y, NULL, NULL);
g_scrollY = pos;
return 0;
}
break;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case IDBUTTON_REMOVE:
ShowWindow(hEdit1, SW_HIDE);
SetScrollRange(hwnd, SB_VERT, 0, displayArea(), TRUE);
break;
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
// this is like scrollHeight but control that are hidden
// aren't considered part of the display area.
int displayArea(void)
{
int size = scrollHeight();
// start i with 1 so we skip the mainwindow.
// we are interested in the "children controls"
for(int i = 1; i < MAX_CONTROLS && allControls[i]; i++)
{
HWND h = allControls[i];
// if it not visible, remove it from display area
if(!IsWindowVisible(h)) {
size -= getHeight(h);
}
}
return size;
}
void pushControl(HWND hwnd)
{
if(allControls_indx > MAX_CONTROLS) {
assert(!"no room for extra controls");
}
allControls[allControls_indx++] = hwnd;
}
void CreateTab(HWND hwnd)
{
hTab =
CreateWindow(WC_TABCONTROLW, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP,
100, 80, 400, 250,
hwnd,
(HMENU) 1,
NULL,
NULL);
InsertTabItem(hTab, 2, L"Tab 1");
InsertTabItem(hTab, 3, L"Tab b");
pushControl(hTab);
}
void CreateButtons(HWND hwnd)
{
RECT rt = GetLocalCoordinates(hTab);
TabCtrl_AdjustRect(hTab, FALSE, &rt);
RECT rt2 = {0};
GetWindowRect(hTab, &rt2);
int tab_width = rt2.right - rt2.left;
int tab_height = rt2.bottom - rt2.top;
int id = 4;
const int cy_breakSize = 25;
int cx_initPos = rt.left;
int cy_initPos = rt.top;
int cx = cx_initPos;
int cy = cy_initPos;
const int button_width = 80;
const int button_height = 25;
const int cx_margin = 10;
int nMaxButtonPerRow = tab_width / (button_width + cx_margin);
for(int i = 0; i < sizeof(title)/sizeof(title[0]); ++i)
{
if(i != 0 && (i % nMaxButtonPerRow) == 0) {
cy += cy_breakSize;
cx = cx_initPos;
}
hButton[i] =
CreateWindow(L"button", title[i],
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
cx,
cy,
button_width,
button_height,
hwnd, (HMENU) id++, NULL, NULL);
cx += button_width;
}
const int edit_width = 180;
const int edit_height = 25;
hEdit1 = CreateWindow(L"Edit", L"Hello, world!",
WS_VISIBLE | WS_CHILD | WS_BORDER,
cx,
// put below tab control's display area
getHeight(hTab) + cx,
edit_width,
edit_height,
hwnd,
(HMENU) id++,
NULL, NULL);
pushControl(hEdit1);
cx += edit_width;
}
void setUpScrollBar(HWND hwnd, int h)
{
RECT rc = { 0 };
GetClientRect(hwnd, &rc);
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
si.nMin = 0;
si.nMax = h;
si.nPage = (rc.bottom - rc.top);
si.nPos = 0;
si.nTrackPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
}
void setScrollBarSize(HWND hwnd, int s)
{
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_RANGE;
si.nMin = 0;
si.nMax = s;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
}
int getHeight(HWND control)
{
RECT rt;
if(!GetWindowRect(control, &rt)) {
ErrorExit(NAMEOF(getHeight), __LINE__, __FILENAME__);
}
return rt.bottom - rt.top;
}
void displayText(HWND h)
{
int len = GetWindowTextLength(h);
if(len == 0) {
return;
}
wchar_t buffer[len + 1];
memset(buffer, 0, sizeof(buffer));
GetWindowText(h, buffer, len+1);
MessageBox(NULL, buffer, L"control text = ", MB_OK);
}
int scrollHeight(void)
{
int minTop = 0;
int maxBottom = 0;
RECT rt = {0};
if(allControls_indx == 0) {
return 0;
}
#if 0
assert(allControls[0] == hMainWindow);
assert(allControls[allControls_indx - 1] == hEdit1);
#endif
if(!GetWindowRect(allControls[0], &rt)) {
ErrorExit(NAMEOF(scrollHeight), __LINE__, __FILENAME__);
}
minTop = rt.top;
if(!GetWindowRect(allControls[allControls_indx - 1], &rt)) {
ErrorExit(NAMEOF(scrollHeight), __LINE__, __FILENAME__);
}
maxBottom = rt.bottom;
return maxBottom - minTop;
}
RECT GetLocalCoordinates(HWND hWnd)
{
RECT Rect;
GetWindowRect(hWnd, &Rect);
MapWindowPoints(HWND_DESKTOP, GetParent(hWnd), (LPPOINT) &Rect, 2);
return Rect;
}
void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text)
{
TCITEMW tci = {0};
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = lstrlenW(text);
SendMessage(tabHwnd, TCM_INSERTITEM, id, (LPARAM) &tci);
}
void InitComControls()
{
INITCOMMONCONTROLSEX icex;
/* initialize this component is required to use tab control,
it seems.
*/
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icex);
}
// display the error message from last error seen then
// exit the application, with that last error code seen.
// to test function, do something like:
// if(!GetProcessId(NULL))
// errorExit(TEXT("GetProcessId"));
// not quite a unittest but yeah.
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename)
{
DWORD dw = ShowLastError(lpszFunction, line, filename);
ExitProcess(dw);
}
DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename)
{
#define MAX_DIGITS 16
/*
* NOTE!!: calling GetLastError() must be done before calling
* any other function, that would reset the GetLastError(), making
* this function report error about the wrong function.
*/
DWORD dw = GetLastError();
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
lpDisplayBuf = (LPVOID) LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) +
lstrlen((LPCTSTR)lpszFunction) + 40 +
(line > 0 ? MAX_DIGITS : 0) +
(filename != NULL ? lstrlen(filename) : 0)) *
sizeof(TCHAR)
);
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with %d: %s"),
lpszFunction, dw, lpMsgBuf
);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
return dw;
}
I think there is no problem with your calculation method. But you added the handle of the main window hwnd for the first time, which leads to the fact that the minTop you get in GetWindowRect(allControls[0], &rt) is the top coordinate of the main window, which caused a gap at the end of the display area.
However, you should actually get the coordinates of minTop from hLabel1 so that you won't attend the question.
In addition, you forgot to add hRemoveButton to the allControls array.
Solution: You can choose not to add the main window to the allControls array, or use GetWindowRect(allControls[1], &rt) to get the coordinates of minTop.
if (!GetWindowRect(allControls[1], &rt)) {
ErrorExit((LPWSTR)NAMEOF(scrollHeight), __LINE__, (LPWSTR)__FILENAME__);
}
minTop = rt.top;
Edit:
The reason why part of the edit box is cropped is that your hLabel1 is set to a height of 10 compared to the top, so when the top of hLabel1 is used as minTop, the length of the bottom is reduced by 10 units.
You need to add the y size of the topmost control:
return maxBottom - minTop + y; //The y size of the topmost control.
How do I calculate the value to set into SCROLLINFO.nMax to my window? currently I'm looping over all the controls in the window, keeping track of the smallest top and highest bottom values, so that I could get the size of the whole window, including those out of display area, not currently being show but still there. But something is wrong, I'm getting a big empty area in the end of scrollbar, it seems nMax is too large. what am I missing? once you scroll down entirely, it's supposed the window until the edit box show up. What the gab looks like now:
I'm getting this size like this:
int scrollHeight(void)
{
int mTop = 0;
int mBottom = 0;
RECT rt = {0};
for(int i = 0; i < MAX_CONTROLS; i++)
{
HWND h = allControls[i];
if(h == NULL) break;
memset(&rt, 0, sizeof(RECT));
if(!GetWindowRect(h, &rt))
{
ErrorExit(NAMEOF(scrollHeight), __LINE__, __FILENAME__);
}
mBottom = my_max(mBottom, rt.bottom);
mTop = my_min(mTop, rt.top);
}
return mBottom - mTop;
}
setting the scrollbar like this:
void setUpScrollBar(HWND hwnd)
{
RECT rc = { 0 };
GetClientRect(hwnd, &rc);
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
si.nMin = 0;
si.nMax = scrollHeight();
si.nPage = (rc.bottom - rc.top);
si.nPos = 0;
si.nTrackPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
}
full code:
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <Commctrl.h>
#include <crtdbg.h>
#include <strsafe.h>
#include <string.h>
#include <assert.h>
#ifdef UNICODE
#define STRSPLIT wcsrchr
#else
#define STRSPLIT strrchr
#endif
#define __FILENAME__ (STRSPLIT(TEXT(__FILE__), '/') ? STRSPLIT(TEXT(__FILE__), '/') + 1 : TEXT(__FILE__))
#define NAMEOF(s) TEXT(#s)
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK CreateTabProc(HWND, UINT, WPARAM, LPARAM);
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename);
void InitComControls();
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename);
DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename);
void InitComControls();
void CreateTab(HWND hwnd);
void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text);
void CreateButtons(HWND hwnd);
RECT GetLocalCoordinates(HWND hWnd);
int scrollHeight(void);
int getHeight(HWND control);
void setUpScrollBar(HWND hwnd);
void pushControl(HWND);
inline int my_max(int a, int b);
inline int my_min(int a, int b);
void showText(HWND hwnd);
HINSTANCE ghInstance;
HWND hTab;
HWND hLabel1, hLabel2;
HWND hEdit1;
#define MAX_CONTROLS 8
static const wchar_t *title[] = { L"Button A1", L"Button B2", L"Button C3",
L"Button D4", L"Button E5", L"Button F6",
L"Button G" , L"Button 001", L"Button 002",
L"Button 003", L"Button 004",
L"Button 005", L"Button 006" };
HWND hButton[sizeof(title)/sizeof(title[0])] = {0};
HWND allControls[MAX_CONTROLS];
int allControls_indx = 0;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow)
{
MSG msg = {0};
HWND hwnd;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Window";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
InitComControls();
if(!RegisterClass(&wc)) {
ErrorExit(NAMEOF(RegisterClass), __LINE__, __FILENAME__);
}
int width = 500;
int height = 350/2; // half than the usual size, so that the scrollbar show up and we can test it
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
int cx = (screenWidth - width) / 2;
int cy = (screenHeight - height) / 2;
hwnd = CreateWindowW(wc.lpszClassName, L"Window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
cx, cy, width, height, NULL, NULL,
hInstance, NULL);
pushControl(hwnd); // push main window too
ghInstance = hInstance;
while (GetMessage(&msg, NULL, 0, 0))
{
if (!IsDialogMessage(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static int g_scrollY;
switch(msg)
{
case WM_CREATE:
hLabel1 = CreateWindowW(L"Static", L"This is label 1...",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
50, 10, 130, 25, hwnd, (HMENU) 18, NULL, NULL);
pushControl(hLabel1);
hLabel2 = CreateWindowW(L"Static", L"This is label 2...",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
50, 40, 130, 25, hwnd, (HMENU) 19, NULL, NULL);
pushControl(hLabel2);
CreateTab(hwnd);
CreateButtons(hwnd);
setUpScrollBar(hwnd);
//ChangeToDefaultFont(hwnd);
break;
case WM_VSCROLL:
{
int action = LOWORD(wParam);
//HWND hScroll = (HWND)lParam;
int pos = -1;
if (action == SB_THUMBPOSITION || action == SB_THUMBTRACK) {
pos = HIWORD(wParam);
} else if (action == SB_LINEDOWN) {
pos = g_scrollY + 30;
} else if (action == SB_LINEUP) {
pos = g_scrollY - 30;
}
if (pos == -1)
break;
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS;
si.nPos = pos;
si.nTrackPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
GetScrollInfo(hwnd, SB_VERT, &si);
pos = si.nPos;
POINT pt;
pt.x = 0;
pt.y = pos - g_scrollY;
HDC hdc = GetDC(hwnd);
LPtoDP(hdc, &pt, 1);
ReleaseDC(hwnd, hdc);
ScrollWindow(hwnd, 0, -pt.y, NULL, NULL);
g_scrollY = pos;
return 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
void pushControl(HWND hwnd)
{
if(allControls_indx > MAX_CONTROLS) {
assert(!"no room for extra controls");
}
allControls[allControls_indx++] = hwnd;
}
void CreateTab(HWND hwnd)
{
hTab =
CreateWindow(WC_TABCONTROLW, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP,
100, 80, 400, 250,
hwnd,
(HMENU) 1,
NULL,
NULL);
InsertTabItem(hTab, 2, L"Tab 1");
InsertTabItem(hTab, 3, L"Tab b");
pushControl(hTab);
}
void CreateButtons(HWND hwnd)
{
RECT rt = GetLocalCoordinates(hTab);
TabCtrl_AdjustRect(hTab, FALSE, &rt);
RECT rt2 = {0};
GetWindowRect(hTab, &rt2);
int tab_width = rt2.right - rt2.left;
int tab_height = rt2.bottom - rt2.top;
int id = 4;
const int cy_breakSize = 25;
int cx_initPos = rt.left;
int cy_initPos = rt.top;
int cx = cx_initPos;
int cy = cy_initPos;
const int button_width = 80;
const int button_height = 25;
const int cx_margin = 10;
int nMaxButtonPerRow = tab_width / (button_width + cx_margin);
for(int i = 0; i < sizeof(title)/sizeof(title[0]); ++i)
{
if(i != 0 && (i % nMaxButtonPerRow) == 0) {
cy += cy_breakSize;
cx = cx_initPos;
}
hButton[i] =
CreateWindow(L"button", title[i],
WS_VISIBLE | WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL,
cx,
cy,
button_width,
button_height,
hwnd, (HMENU) id++, NULL, NULL);
cx += button_width;
}
const int edit_width = 180;
const int edit_height = 25;
hEdit1 = CreateWindow(L"Edit", L"Hello, world!",
WS_VISIBLE | WS_CHILD | WS_BORDER,
cx,
// put below tab control's display area
getHeight(hTab) + cx,
edit_width,
edit_height,
hwnd,
(HMENU) id++,
NULL, NULL);
pushControl(hEdit1);
cx += edit_width;
}
void setUpScrollBar(HWND hwnd)
{
RECT rc = { 0 };
GetClientRect(hwnd, &rc);
SCROLLINFO si = { 0 };
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
si.nMin = 0;
si.nMax = scrollHeight();
si.nPage = (rc.bottom - rc.top);
si.nPos = 0;
si.nTrackPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
}
int getHeight(HWND control)
{
RECT rt;
if(!GetWindowRect(control, &rt)) {
ErrorExit(NAMEOF(getHeight), __LINE__, __FILENAME__);
}
return rt.bottom - rt.top;
}
int scrollHeight(void)
{
int mTop = 0;
int mBottom = 0;
RECT rt = {0};
for(int i = 0; i < MAX_CONTROLS; i++)
{
HWND h = allControls[i];
if(h == NULL) break;
memset(&rt, 0, sizeof(RECT));
if(!GetWindowRect(h, &rt))
{
ErrorExit(NAMEOF(scrollHeight), __LINE__, __FILENAME__);
}
mBottom = my_max(mBottom, rt.bottom);
mTop = my_min(mTop, rt.top);
}
return mBottom - mTop;
}
inline int my_max(int a, int b)
{
return a > b ? a : b;
}
inline int my_min(int a, int b)
{
return a < b ? a : b;
}
RECT GetLocalCoordinates(HWND hWnd)
{
RECT Rect;
GetWindowRect(hWnd, &Rect);
MapWindowPoints(HWND_DESKTOP, GetParent(hWnd), (LPPOINT) &Rect, 2);
return Rect;
}
void InsertTabItem(HWND tabHwnd, UINT id, LPWSTR text)
{
TCITEMW tci = {0};
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = lstrlenW(text);
SendMessage(tabHwnd, TCM_INSERTITEM, id, (LPARAM) &tci);
}
void InitComControls()
{
INITCOMMONCONTROLSEX icex;
/* initialize this component is required to use tab control,
it seems.
*/
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icex);
}
// display the error message from last error seen then
// exit the application, with that last error code seen.
// to test function, do something like:
// if(!GetProcessId(NULL))
// errorExit(TEXT("GetProcessId"));
// not quite a unittest but yeah.
void ErrorExit(LPWSTR lpszFunction, int line, LPWSTR filename)
{
DWORD dw = ShowLastError(lpszFunction, line, filename);
ExitProcess(dw);
}
DWORD ShowLastError(LPWSTR lpszFunction, int line, LPWSTR filename)
{
#define MAX_DIGITS 16
/*
* NOTE!!: calling GetLastError() must be done before calling
* any other function, that would reset the GetLastError(), making
* this function report error about the wrong function.
*/
DWORD dw = GetLastError();
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
lpDisplayBuf = (LPVOID) LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) +
lstrlen((LPCTSTR)lpszFunction) + 40 +
(line > 0 ? MAX_DIGITS : 0) +
(filename != NULL ? lstrlen(filename) : 0)) *
sizeof(TCHAR)
);
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with %d: %s"),
lpszFunction, dw, lpMsgBuf
);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
return dw;
}
This line in your scrollHeight() function is the problem:
mTop = my_min(mTop, rt.top);
mTop starts out as 0, so will always be the minimum. Instead of the distance between the top and bottom controls you end up with the distance from the top of the screen to the bottom of the bottom control (note that because you're using GetWindowRect you are dealing with screen coordinates, not client coordinates - so this magnifies the problem).
If you change that line to the following it gives a much better result:
mTop = mTop ? my_min(mTop, rt.top) : rt.top;
I'm using GetTextExtentPoint32() to measure the text size and calculate the width and height of my combobox. However, it seems to be adding margin proportional to the amount of text, that I'd to get rid of. It seems the larger the string is, the larger margin I get on right. How do I it doesn't add this margin? a small one is acceptable but look at the second image, the big empty space on right.
With small strings, I find this margin acceptable:
However, with big ones, I get this big white space on right side, that I'd to get rid of:
I'm measuring the text size like this:
int len = wcslen(s);
HDC dc = GetDC(hwndCombo);
SIZE sz;
assert(GetTextExtentPoint32(dc, s, len, &sz));
ReleaseDC(hwndCombo, dc);
int weight = sz.cx + 10;
int height = sz.cy;
assert(SetWindowPos(hwndCombo, HWND_TOP, 0, 0, weight, height, SWP_NOMOVE));
full code:
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")
#pragma comment(lib, "Comdlg32.lib")
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <winuser.h>
#include <assert.h>
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int largestIndex();
HFONT getDefaultFont();
void SetDefaultFont(HWND hwnd);
HFONT hDefaultSystemFont;
HINSTANCE g_hinst;
const wchar_t *items[] =
{
L"Windows", L"Mac",
L"FreeBSD", L"Arch",
L"aaaaaa!!!!!!!aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#",
L"foo",
L"baaaa"
};
HWND hwndCombo;
enum
{
ID_COMBO = 10,
ID_BTN1,
};
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
HWND hwnd;
MSG msg ;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Application";
wc.hInstance = hInstance ;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc ;
wc.hCursor = LoadCursor(0,IDC_ARROW);
g_hinst = hInstance;
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 300, 170, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0))
{
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch(msg)
{
case WM_CREATE:
{
hwndCombo = CreateWindowW(L"Combobox", NULL,
WS_CHILD | WS_VISIBLE | CBS_DROPDOWN,
10, 10, 60, 110, hwnd, (HMENU) ID_COMBO, g_hinst, NULL);
for (int i = 0; i < COUNTOF(items); i++ )
{
SendMessageW(hwndCombo, CB_ADDSTRING, 0, (LPARAM) items[i]);
}
SetDefaultFont(hwndCombo);
HWND btn1 =
CreateWindowW(L"Button", L"Click me",
WS_CHILD | WS_VISIBLE,
5, 40, 90, 25, hwnd, (HMENU) ID_BTN1, g_hinst, NULL);
SetDefaultFont(btn1);
}
break;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case ID_BTN1:
{
int i = largestIndex();
wchar_t *s = (wchar_t*) items[i];
int len = wcslen(s);
HDC dc = GetDC(hwndCombo);
SIZE sz;
assert(GetTextExtentPoint32(dc, s, len, &sz));
ReleaseDC(hwndCombo, dc);
int weight = sz.cx + 10;
int height = sz.cy;
assert(SetWindowPos(hwndCombo, HWND_TOP, 0, 0, weight, height, SWP_NOMOVE));
}
break;
default:
break;
}
}
break;
case WM_DESTROY:
DeleteObject(hDefaultSystemFont);
hDefaultSystemFont = NULL;
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
int largestIndex()
{
int m = 0;
int idx = -1;
for(int i = 0; i < COUNTOF(items); i++) {
int l = wcslen(items[i]);
if(l > m) {
m = l;
idx = i;
}
}
return idx;
}
HFONT getDefaultFont()
{
if(hDefaultSystemFont == NULL) {
NONCLIENTMETRICS ncm;
ncm.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
hDefaultSystemFont = CreateFontIndirect(&ncm.lfMessageFont);
}
return hDefaultSystemFont;
}
void SetDefaultFont(HWND hwnd)
{
SendMessage(hwnd, WM_SETFONT, (LPARAM) getDefaultFont(), TRUE);
}
You are using different font for measurements.
Try
int len = wcslen(s);
HDC dc = GetDC(hwndCombo);
SIZE sz;
// Set correct font
HFONT old = SelectObject(dc,hDefaultSystemFont);
// or
// HFONT control_font = (HFONT)SendMessage(hwndCombo, WM_GETFONT, 0, 0);
// HFONT old = SelectObject(dc,control_font);
assert(GetTextExtentPoint32(dc, s, len, &sz));
// Restore font for cleanup
SelectObject(dc,old);
ReleaseDC(hwndCombo, dc);
int weight = sz.cx + 10;
int height = sz.cy;
I have a owner-draw combobox where I'd like to recreate the standard behavior, i.e., that blue background color over the current selected item. I've tried set X when the WM_DRAWITEM message is received without luck like this:
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT b = (LPDRAWITEMSTRUCT) lParam;
if(b->itemAction & ODA_FOCUS)
{
SetBkMode(b->hDC, TRANSPARENT);
SetBkColor(b->hDC, GetSysColor(COLOR_HIGHLIGHT));
DrawFocusRect(b->hDC, &b->rcItem);
return (INT_PTR) TRUE;
}
Example of behavior I'm refering to:
What it does look like currently:
full code:
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <Commctrl.h>
#include <assert.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void SetDefaultFont(HWND hwnd);
HFONT LoadSystemDefaultFont();
void DrawBorder(HDC hdc, RECT *rect);
void DrawLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2);
void freeBrushes();
HBRUSH getBrushAt(int index);
void drawColorRect(HDC dc, RECT *editRect, int colorIndex);
HINSTANCE g_hinst;
HFONT hDefaultSystemFont;
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
#define LIST \
X(L"Black", RGB(0, 0, 0)) \
X(L"Red", RGB(255, 0, 0)) \
X(L"Blue", RGB(0, 0, 255)) \
X(L"Green", RGB(0, 128, 0)) \
X(L"Yellow", RGB(255, 255, 0))
#define X(a, b) a,
const wchar_t *items[] = { LIST };
#undef X
#define X(a, b) b,
const int colorCodes[] = { LIST };
#undef X
HBRUSH brushes[COUNTOF(items)];
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
HWND hwnd;
MSG msg;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Application";
wc.hInstance = hInstance ;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0,IDC_ARROW);
g_hinst = hInstance;
RegisterClassW(&wc);
hwnd = CreateWindowW(wc.lpszClassName, L"Combo box",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 270, 170, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
DeleteObject(hDefaultSystemFont);
freeBrushes();
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
static HWND hwndCombo;
switch(msg)
{
case WM_CREATE:
{
hwndCombo = CreateWindow(L"Combobox", NULL,
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST |
CBS_OWNERDRAWFIXED | CBS_AUTOHSCROLL | WS_VSCROLL | WS_HSCROLL,
10, 10, 100, 110, hwnd, NULL, g_hinst, NULL);
SendMessage(hwndCombo, CB_SETMINVISIBLE, 5, 0);
SetDefaultFont(hwndCombo);
for (int i = 0; i < COUNTOF(items); ++i)
{
SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) items[i]);
}
}
break;
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT b = (LPDRAWITEMSTRUCT) lParam;
if(b->itemAction & ODA_FOCUS)
{
SetBkMode(b->hDC, TRANSPARENT);
SetBkColor(b->hDC, GetSysColor(COLOR_HIGHLIGHT));
DrawFocusRect(b->hDC, &b->rcItem);
return (INT_PTR) TRUE;
}
else if(b->itemAction & ODA_DRAWENTIRE)
{
if(b->itemAction & ODA_FOCUS)
{
DrawFocusRect(b->hDC, &b->rcItem);
}
if(b->itemID != -1)
{
drawColorRect(b->hDC, &b->rcItem, b->itemID);
DrawText(b->hDC, items[b->itemID], -1, &b->rcItem, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
else
{
//drawColorRect(b->hDC, &b->rcItem, 0);
DrawText(b->hDC, L"Select", -1, &b->rcItem, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
return (INT_PTR) TRUE;
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
HFONT LoadSystemDefaultFont()
{
if(hDefaultSystemFont == NULL) {
NONCLIENTMETRICS ncm;
ncm.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
hDefaultSystemFont = CreateFontIndirect(&ncm.lfMessageFont);
}
return hDefaultSystemFont;
}
void SetDefaultFont(HWND hwnd)
{
SendMessage(hwnd, WM_SETFONT, (LPARAM) LoadSystemDefaultFont(), TRUE);
}
void drawColorRect(HDC dc, RECT *editRect, int colorIndex)
{
assert(colorIndex >= 0 && colorIndex < COUNTOF(brushes));
assert(editRect != NULL);
RECT rt = {0};
rt.left = editRect->left + 2;
rt.top = editRect->top - 2;
rt.right = editRect->right / 5;
rt.bottom = editRect->bottom - 2;
InflateRect(&rt, -1, -1);
DrawBorder(dc, &rt);
//FrameRect(dc, &rt, getBrushAt(0));
FillRect(dc, &rt, getBrushAt(colorIndex));
}
void DrawLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2)
{
MoveToEx(hdc, x1, y1, NULL);
LineTo(hdc, x2, y2);
}
void DrawBorder(HDC hdc, RECT *rect)
{
DrawLine(hdc, rect->left, rect->top, rect->left, rect->bottom);
DrawLine(hdc, rect->left, rect->top, rect->right, rect->top);
DrawLine(hdc, rect->right, rect->top, rect->right, rect->bottom);
DrawLine(hdc, rect->left, rect->bottom, rect->right, rect->bottom);
}
HBRUSH getBrushAt(int index)
{
assert(index >= 0 && index < COUNTOF(brushes));
HBRUSH b = brushes[index];
if(b == NULL) {
int code = colorCodes[index];
brushes[index] = CreateSolidBrush(code);
b = brushes[index];
}
return b;
}
void freeBrushes()
{
for(int i = 0; i < COUNTOF(brushes); ++i){
DeleteObject(brushes[i]);
brushes[i] = NULL;
}
}
To draw the selected item with a different background and text colour, you need to check the itemState member of the given DRAWITEMSTRUCT (lParam) argument for the ODS_SELECTED flag.
Something like the following:
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT b = (LPDRAWITEMSTRUCT) lParam;
SetBkMode(b->hDC, TRANSPARENT);
if(b->itemState & ODS_SELECTED) // Selected ...
{
SetTextColor(b->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
FillRect(b->hDC, &b->rcItem, (HBRUSH)(COLOR_HIGHLIGHT+1));
}
else // Not selected ...
{
SetTextColor(b->hDC, GetSysColor(COLOR_WINDOWTEXT));
FillRect(b->hDC, &b->rcItem, (HBRUSH)(COLOR_WINDOW+1));
}
DrawText(b->hDC, items[b->itemID], -1, &b->rcItem, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
// Check for focus ...
if(b->itemAction & ODA_FOCUS)
{
DrawFocusRect(b->hDC, &b->rcItem);
}
return (I
NT_PTR) TRUE;
Note: When using a system colour for the hbr parameter of your FillRect call, you need to add one to the value. From the documentation for FillRect:
...If specifying a color value for the hbr parameter, it must be one
of the standard system colors (the value 1 must be added to the chosen
color).
I can't stop the flickering. I got the advice to add dubbel-buffering. How do I do that?
#include <iostream>
#include <windows.h>
#include <string>
#include <fstream>
#include <vector>
using namespace std;
namespace {
const int ID_NEW = 1;
const int ID_QUIT = 2;
const int ID_ABOUT = 3;
const int NORTH_BUTTON_ID = 4;
const int SOUTH_BUTTON_ID = 5;
const int WEST_BUTTON_ID = 6;
const int EAST_BUTTON_ID = 7;
const int ID_FINISHED_GAME = 8;
int x = 0;
int y = 0;
int xStart = 0;
int yStart = 0;
int windowHeight = 400;
int windowWidth = 500;
char level1[20][21];
int noOfMoves = 0;
}
void readLevel(string fileName, char level[20][21]) {
char character{};
ifstream file(fileName);
int i = 0;
int j = 0;
if (file.is_open()) {
while (file >> character) {
level[j][i] = character;
if (level[j][i] == 's') {
y = yStart = j;
x = xStart = i;
}
if (++i % 20 == 0) {
i = 0;
j++;
}
}
file.close();
}
}
void restart(){
x = xStart;
y = yStart;
noOfMoves = 0;
}
LRESULT CALLBACK WinProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
HDC hdc{ 0 };
PAINTSTRUCT ps{ 0 };
switch (msg) {
case WM_CLOSE:
PostQuitMessage(0);
return 0;
case WM_COMMAND:
switch (LOWORD(wParam)){
case ID_ABOUT:
MessageBox(hwnd, L"About this program!", L"About", MB_OK);
return 0;
case ID_NEW:
restart();
return 0;
case ID_QUIT:
if (MessageBox(0, L"Do you really want to quit?", L"Are you sure?", MB_YESNO) == IDYES) {
PostQuitMessage(0);
return 0;
}
case NORTH_BUTTON_ID:
if (level1[y - 1][x] != '1') {
y -= 1;
noOfMoves++;
}
break;
case SOUTH_BUTTON_ID:
if (level1[y + 1][x] != '1'){
y += 1;
noOfMoves++;
}
break;
case WEST_BUTTON_ID:
if (level1[y][x - 1] != '1'){
x -= 1;
noOfMoves++;
}
break;
case EAST_BUTTON_ID:
if (level1[y][x + 1] != '1') {
x += 1;
noOfMoves++;
}
break;
}
if (level1[y][x] == 'e') {
wstring moves = L"Congratulations, you finished the game with " + to_wstring(noOfMoves);
MessageBox(hwnd, moves.c_str(), L"Finished game", MB_OK);
}
case WM_PAINT: {
char wall[2] = { "W" };
char floor[2] = { 'W' };
char current[2] = { "X" };
char goal[2] = { "G" };
wstring position = L"Position = [" + to_wstring(x) + L", " + to_wstring(y) + L"]";
wstring moves = L"Move = " + to_wstring(noOfMoves);
hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 20, 200, position.c_str(), position.size());
TextOut(hdc, 20, 220, moves.c_str(), moves.size());
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
if (level1[j][i] == '1') {
SetTextColor(hdc, RGB(0, 0, 0));
SetBkColor(hdc, RGB(0, 0, 0));
TextOut(hdc, 14 * i + 190, 14 * j + 20, LPCTSTR(wall), strlen(wall));
}
SetBkColor(hdc, RGB(255, 255, 255));
if (level1[j][i] == '0') {
SetTextColor(hdc, RGB(255, 255, 255));
TextOut(hdc, 14 * i + 190, 14 * j + 20, LPCTSTR(floor), strlen(floor));
}
SetTextColor(hdc, RGB(0, 0, 0));
if (i == x && j == y)
TextOut(hdc, 14 * i + 190, 14 * j + 20, LPCTSTR(current), strlen(current));
if (level1[j][i] == 'e')
TextOut(hdc, 14 * i + 190, 14 * j + 20, LPCTSTR(goal), strlen(goal));
}
}
EndPaint(hwnd, &ps);
break;
}
case WM_ERASEBKGND:
return true;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
HMENU CreateMainMenu() {
HMENU main = CreateMenu();
HMENU file = CreateMenu();
AppendMenu(file, MF_STRING, ID_NEW, L"&New");
AppendMenu(file, MF_SEPARATOR, 0, 0);
AppendMenu(file, MF_STRING, ID_QUIT, L"&Quit");
AppendMenu(main, MF_POPUP, (UINT_PTR)file, L"&File");
HMENU help = CreateMenu();
AppendMenu(help, MF_STRING, ID_ABOUT, L"&About");
AppendMenu(main, MF_POPUP, (UINT_PTR)help, L"&Help");
return main;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
readLevel("level1.txt", level1);
WNDCLASS wc = { 0 };
wc.hbrBackground = NULL;
wc.lpfnWndProc = WinProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"MyWindowClass";
RegisterClass(&wc);
HWND hwnd = CreateWindow(L"MyWindowClass", L"The Maze",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
windowWidth, windowHeight, 0, CreateMainMenu(), hInstance, 0);
HWND buttonNorth = CreateWindow(L"BUTTON", L"NORTH", WS_CHILD | WS_VISIBLE,
10, 20, 150, 40, hwnd, (HMENU)NORTH_BUTTON_ID, hInstance, 0);
HWND buttonSouth = CreateWindow(L"BUTTON", L"SOUTH", WS_CHILD | WS_VISIBLE,
10, 60, 150, 40, hwnd, (HMENU)SOUTH_BUTTON_ID, hInstance, 0);
HWND buttonEast = CreateWindow(L"BUTTON", L"EAST", WS_CHILD | WS_VISIBLE,
10, 140, 150, 40, hwnd, (HMENU)EAST_BUTTON_ID, hInstance, 0);
HWND buttonWest = CreateWindow(L"BUTTON", L"WEST", WS_CHILD | WS_VISIBLE,
10, 100, 150, 40, hwnd, (HMENU)WEST_BUTTON_ID, hInstance, 0);
UpdateWindow(hwnd);
ShowWindow(hwnd, nCmdShow);
MSG msg = { 0 };
BOOL isRunning = true;
while (isRunning) {
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT)
isRunning = false;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
wc.hbrBackground = NULL;
InvalidateRect(hwnd, NULL, FALSE);
Sleep(10);
}
return 0;
}
As member Retired Ninja said, you make compatible device context with the original one ( hdc in your case ), and create a bitmap compatible with your original device context ( bitmap size is equal to the size of your rectangle where you paint your stuff ).
Then select this newly created bitmap into compatible device context you just created and draw everything on it.
Then you just BitBlt(...) the compatible device context into original one.
Do not forget to perform proper cleanup in order to avoid GDI leaks.
Your code should look like this:
case WM_PAINT:
{
// skipped the initialization part to preserve space
// just copy those, they are irrelevant for your problem
hdc = BeginPaint(hwnd, &ps);
// create memory DC and memory bitmap where we shall do our drawing
HDC memDC = CreateCompatibleDC( hdc );
// get window's client rectangle. We need this for bitmap creation.
RECT rcClientRectangle;
GetClientRect( hwnd, &rcClientRect );
// now we can create bitmap where we shall do our drawing
HBITMAP bmp = CreateCompatibleBitmap( hdc,
rcClientRect.right - rcClientRect.left,
rcClientRect.bottom - rcClientRect.top );
// we need to save original bitmap, and select it back when we are done,
// in order to avoid GDI leaks!
HBITMAP oldBmp = (HBITMAP)SelectObject( memDC, bmp );
// now you draw your stuff in memory dc;
// just substitute hdc with memDC in your drawing code,
// like I did below:
TextOut( memDC, //...
TextOut( memDC, //...
for (int i = 0; i < 20; i++)
{
for (int j = 0; j < 20; j++)
{
if (level1[j][i] == '1')
{
SetTextColor( memDC, //...
SetBkColor( memDC, //...
TextOut( memDC, //...
}
SetBkColor( memDC, //...
if (level1[j][i] == '0')
{
SetTextColor( memDC, //...
TextOut( memDC, //...
}
SetTextColor( memDC, //...
if (i == x && j == y)
TextOut( memDC, //...
if (level1[j][i] == 'e')
TextOut( memDC, //...
}
}
// OK, everything is drawn into memory DC,
// now is the time to draw that final result into our target DC
BitBlt( hdc, 0, 0, rcClientRect.right - rcClientRect.left,
rcClientRect.bottom - rcClientRect.top, memDC, 0, 0, SRCCOPY );
// all done, now we need to cleanup
SelectObject( memDC, oldBmp ); // select back original bitmap
DeleteObject( bmp ); // delete bitmap since it is no longer required
DeleteDC( memDC ); // delete memory DC since it is no longer required
EndPaint(hwnd, &ps);
break;
}