1373 lines
33 KiB
C
1373 lines
33 KiB
C
/*
|
|
* Process Hacker -
|
|
* GUI support functions
|
|
*
|
|
* Copyright (C) 2009-2016 wj32
|
|
*
|
|
* This file is part of Process Hacker.
|
|
*
|
|
* Process Hacker is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Process Hacker is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <ph.h>
|
|
#include <guisup.h>
|
|
#include <guisupp.h>
|
|
#include <shellapi.h>
|
|
#include <windowsx.h>
|
|
#include <uxtheme.h>
|
|
|
|
#define SCALE_DPI(Value) PhMultiplyDivide(Value, PhGlobalDpi, 96)
|
|
|
|
_ChangeWindowMessageFilter ChangeWindowMessageFilter_I;
|
|
_IsImmersiveProcess IsImmersiveProcess_I;
|
|
_RunFileDlg RunFileDlg;
|
|
_SHAutoComplete SHAutoComplete_I;
|
|
|
|
static PH_INITONCE SharedIconCacheInitOnce = PH_INITONCE_INIT;
|
|
static PPH_HASHTABLE SharedIconCacheHashtable;
|
|
static PH_QUEUED_LOCK SharedIconCacheLock = PH_QUEUED_LOCK_INIT;
|
|
|
|
VOID PhGuiSupportInitialization(
|
|
VOID
|
|
)
|
|
{
|
|
HMODULE shell32Handle;
|
|
HMODULE shlwapiHandle;
|
|
|
|
shell32Handle = LoadLibrary(L"shell32.dll");
|
|
shlwapiHandle = LoadLibrary(L"shlwapi.dll");
|
|
|
|
if (WINDOWS_HAS_UAC)
|
|
ChangeWindowMessageFilter_I = PhGetModuleProcAddress(L"user32.dll", "ChangeWindowMessageFilter");
|
|
if (WINDOWS_HAS_IMMERSIVE)
|
|
IsImmersiveProcess_I = PhGetModuleProcAddress(L"user32.dll", "IsImmersiveProcess");
|
|
RunFileDlg = PhGetProcedureAddress(shell32Handle, NULL, 61);
|
|
SHAutoComplete_I = PhGetProcedureAddress(shlwapiHandle, "SHAutoComplete", 0);
|
|
}
|
|
|
|
VOID PhSetControlTheme(
|
|
_In_ HWND Handle,
|
|
_In_ PWSTR Theme
|
|
)
|
|
{
|
|
if (WindowsVersion >= WINDOWS_VISTA)
|
|
{
|
|
SetWindowTheme(Handle, Theme, NULL);
|
|
}
|
|
}
|
|
|
|
INT PhAddListViewColumn(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ INT Index,
|
|
_In_ INT DisplayIndex,
|
|
_In_ INT SubItemIndex,
|
|
_In_ INT Format,
|
|
_In_ INT Width,
|
|
_In_ PWSTR Text
|
|
)
|
|
{
|
|
LVCOLUMN column;
|
|
|
|
column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_ORDER;
|
|
column.fmt = Format;
|
|
column.cx = Width < 0 ? -Width : SCALE_DPI(Width);
|
|
column.pszText = Text;
|
|
column.iSubItem = SubItemIndex;
|
|
column.iOrder = DisplayIndex;
|
|
|
|
return ListView_InsertColumn(ListViewHandle, Index, &column);
|
|
}
|
|
|
|
INT PhAddListViewItem(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ INT Index,
|
|
_In_ PWSTR Text,
|
|
_In_opt_ PVOID Param
|
|
)
|
|
{
|
|
LVITEM item;
|
|
|
|
item.mask = LVIF_TEXT | LVIF_PARAM;
|
|
item.iItem = Index;
|
|
item.iSubItem = 0;
|
|
item.pszText = Text;
|
|
item.lParam = (LPARAM)Param;
|
|
|
|
return ListView_InsertItem(ListViewHandle, &item);
|
|
}
|
|
|
|
INT PhFindListViewItemByFlags(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ INT StartIndex,
|
|
_In_ ULONG Flags
|
|
)
|
|
{
|
|
return ListView_GetNextItem(ListViewHandle, StartIndex, Flags);
|
|
}
|
|
|
|
INT PhFindListViewItemByParam(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ INT StartIndex,
|
|
_In_opt_ PVOID Param
|
|
)
|
|
{
|
|
LVFINDINFO findInfo;
|
|
|
|
findInfo.flags = LVFI_PARAM;
|
|
findInfo.lParam = (LPARAM)Param;
|
|
|
|
return ListView_FindItem(ListViewHandle, StartIndex, &findInfo);
|
|
}
|
|
|
|
LOGICAL PhGetListViewItemImageIndex(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ INT Index,
|
|
_Out_ PINT ImageIndex
|
|
)
|
|
{
|
|
LOGICAL result;
|
|
LVITEM item;
|
|
|
|
item.mask = LVIF_IMAGE;
|
|
item.iItem = Index;
|
|
item.iSubItem = 0;
|
|
|
|
result = ListView_GetItem(ListViewHandle, &item);
|
|
|
|
if (!result)
|
|
return result;
|
|
|
|
*ImageIndex = item.iImage;
|
|
|
|
return result;
|
|
}
|
|
|
|
LOGICAL PhGetListViewItemParam(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ INT Index,
|
|
_Out_ PVOID *Param
|
|
)
|
|
{
|
|
LOGICAL result;
|
|
LVITEM item;
|
|
|
|
item.mask = LVIF_PARAM;
|
|
item.iItem = Index;
|
|
item.iSubItem = 0;
|
|
|
|
result = ListView_GetItem(ListViewHandle, &item);
|
|
|
|
if (!result)
|
|
return result;
|
|
|
|
*Param = (PVOID)item.lParam;
|
|
|
|
return result;
|
|
}
|
|
|
|
VOID PhRemoveListViewItem(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ INT Index
|
|
)
|
|
{
|
|
ListView_DeleteItem(ListViewHandle, Index);
|
|
}
|
|
|
|
VOID PhSetListViewItemImageIndex(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ INT Index,
|
|
_In_ INT ImageIndex
|
|
)
|
|
{
|
|
LVITEM item;
|
|
|
|
item.mask = LVIF_IMAGE;
|
|
item.iItem = Index;
|
|
item.iSubItem = 0;
|
|
item.iImage = ImageIndex;
|
|
|
|
ListView_SetItem(ListViewHandle, &item);
|
|
}
|
|
|
|
VOID PhSetListViewSubItem(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ INT Index,
|
|
_In_ INT SubItemIndex,
|
|
_In_ PWSTR Text
|
|
)
|
|
{
|
|
LVITEM item;
|
|
|
|
item.mask = LVIF_TEXT;
|
|
item.iItem = Index;
|
|
item.iSubItem = SubItemIndex;
|
|
item.pszText = Text;
|
|
|
|
ListView_SetItem(ListViewHandle, &item);
|
|
}
|
|
|
|
BOOLEAN PhLoadListViewColumnSettings(
|
|
_In_ HWND ListViewHandle,
|
|
_In_ PPH_STRING Settings
|
|
)
|
|
{
|
|
#define ORDER_LIMIT 50
|
|
PH_STRINGREF remainingPart;
|
|
ULONG columnIndex;
|
|
ULONG orderArray[ORDER_LIMIT]; // HACK, but reasonable limit
|
|
ULONG maxOrder;
|
|
ULONG scale;
|
|
|
|
if (Settings->Length == 0)
|
|
return FALSE;
|
|
|
|
remainingPart = Settings->sr;
|
|
columnIndex = 0;
|
|
memset(orderArray, 0, sizeof(orderArray));
|
|
maxOrder = 0;
|
|
|
|
if (remainingPart.Length != 0 && remainingPart.Buffer[0] == '@')
|
|
{
|
|
PH_STRINGREF scalePart;
|
|
ULONG64 integer;
|
|
|
|
PhSkipStringRef(&remainingPart, sizeof(WCHAR));
|
|
PhSplitStringRefAtChar(&remainingPart, '|', &scalePart, &remainingPart);
|
|
|
|
if (scalePart.Length == 0 || !PhStringToInteger64(&scalePart, 10, &integer))
|
|
return FALSE;
|
|
|
|
scale = (ULONG)integer;
|
|
}
|
|
else
|
|
{
|
|
scale = PhGlobalDpi;
|
|
}
|
|
|
|
while (remainingPart.Length != 0)
|
|
{
|
|
PH_STRINGREF columnPart;
|
|
PH_STRINGREF orderPart;
|
|
PH_STRINGREF widthPart;
|
|
ULONG64 integer;
|
|
ULONG order;
|
|
ULONG width;
|
|
LVCOLUMN lvColumn;
|
|
|
|
PhSplitStringRefAtChar(&remainingPart, '|', &columnPart, &remainingPart);
|
|
|
|
if (columnPart.Length == 0)
|
|
return FALSE;
|
|
|
|
PhSplitStringRefAtChar(&columnPart, ',', &orderPart, &widthPart);
|
|
|
|
if (orderPart.Length == 0 || widthPart.Length == 0)
|
|
return FALSE;
|
|
|
|
// Order
|
|
|
|
if (!PhStringToInteger64(&orderPart, 10, &integer))
|
|
return FALSE;
|
|
|
|
order = (ULONG)integer;
|
|
|
|
if (order < ORDER_LIMIT)
|
|
{
|
|
orderArray[order] = columnIndex;
|
|
|
|
if (maxOrder < order + 1)
|
|
maxOrder = order + 1;
|
|
}
|
|
|
|
// Width
|
|
|
|
if (!PhStringToInteger64(&widthPart, 10, &integer))
|
|
return FALSE;
|
|
|
|
width = (ULONG)integer;
|
|
|
|
if (scale != PhGlobalDpi && scale != 0)
|
|
width = PhMultiplyDivide(width, PhGlobalDpi, scale);
|
|
|
|
lvColumn.mask = LVCF_WIDTH;
|
|
lvColumn.cx = width;
|
|
ListView_SetColumn(ListViewHandle, columnIndex, &lvColumn);
|
|
|
|
columnIndex++;
|
|
}
|
|
|
|
ListView_SetColumnOrderArray(ListViewHandle, maxOrder, orderArray);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
PPH_STRING PhSaveListViewColumnSettings(
|
|
_In_ HWND ListViewHandle
|
|
)
|
|
{
|
|
PH_STRING_BUILDER stringBuilder;
|
|
ULONG i = 0;
|
|
LVCOLUMN lvColumn;
|
|
|
|
PhInitializeStringBuilder(&stringBuilder, 20);
|
|
|
|
PhAppendFormatStringBuilder(&stringBuilder, L"@%u|", PhGlobalDpi);
|
|
|
|
lvColumn.mask = LVCF_WIDTH | LVCF_ORDER;
|
|
|
|
while (ListView_GetColumn(ListViewHandle, i, &lvColumn))
|
|
{
|
|
PhAppendFormatStringBuilder(
|
|
&stringBuilder,
|
|
L"%u,%u|",
|
|
lvColumn.iOrder,
|
|
lvColumn.cx
|
|
);
|
|
i++;
|
|
}
|
|
|
|
if (stringBuilder.String->Length != 0)
|
|
PhRemoveEndStringBuilder(&stringBuilder, 1);
|
|
|
|
return PhFinalStringBuilderString(&stringBuilder);
|
|
}
|
|
|
|
INT PhAddTabControlTab(
|
|
_In_ HWND TabControlHandle,
|
|
_In_ INT Index,
|
|
_In_ PWSTR Text
|
|
)
|
|
{
|
|
TCITEM item;
|
|
|
|
item.mask = TCIF_TEXT;
|
|
item.pszText = Text;
|
|
|
|
return TabCtrl_InsertItem(TabControlHandle, Index, &item);
|
|
}
|
|
|
|
PPH_STRING PhGetWindowText(
|
|
_In_ HWND hwnd
|
|
)
|
|
{
|
|
PPH_STRING text;
|
|
|
|
PhGetWindowTextEx(hwnd, 0, &text);
|
|
return text;
|
|
}
|
|
|
|
ULONG PhGetWindowTextEx(
|
|
_In_ HWND hwnd,
|
|
_In_ ULONG Flags,
|
|
_Out_opt_ PPH_STRING *Text
|
|
)
|
|
{
|
|
PPH_STRING string;
|
|
ULONG length;
|
|
|
|
if (Flags & PH_GET_WINDOW_TEXT_INTERNAL)
|
|
{
|
|
if (Flags & PH_GET_WINDOW_TEXT_LENGTH_ONLY)
|
|
{
|
|
WCHAR buffer[32];
|
|
length = InternalGetWindowText(hwnd, buffer, sizeof(buffer) / sizeof(WCHAR));
|
|
}
|
|
else
|
|
{
|
|
// TODO: Resize the buffer until we get the entire thing.
|
|
string = PhCreateStringEx(NULL, 256 * sizeof(WCHAR));
|
|
length = InternalGetWindowText(hwnd, string->Buffer, (ULONG)string->Length / sizeof(WCHAR) + 1);
|
|
string->Length = length * sizeof(WCHAR);
|
|
|
|
if (Text)
|
|
*Text = string;
|
|
else
|
|
PhDereferenceObject(string);
|
|
}
|
|
|
|
return length;
|
|
}
|
|
else
|
|
{
|
|
length = GetWindowTextLength(hwnd);
|
|
|
|
if (length == 0 || (Flags & PH_GET_WINDOW_TEXT_LENGTH_ONLY))
|
|
{
|
|
if (Text)
|
|
*Text = PhReferenceEmptyString();
|
|
|
|
return length;
|
|
}
|
|
|
|
string = PhCreateStringEx(NULL, length * sizeof(WCHAR));
|
|
|
|
if (GetWindowText(hwnd, string->Buffer, (ULONG)string->Length / sizeof(WCHAR) + 1))
|
|
{
|
|
if (Text)
|
|
*Text = string;
|
|
else
|
|
PhDereferenceObject(string);
|
|
|
|
return length;
|
|
}
|
|
else
|
|
{
|
|
if (Text)
|
|
*Text = PhReferenceEmptyString();
|
|
|
|
PhDereferenceObject(string);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID PhAddComboBoxStrings(
|
|
_In_ HWND hWnd,
|
|
_In_ PWSTR *Strings,
|
|
_In_ ULONG NumberOfStrings
|
|
)
|
|
{
|
|
ULONG i;
|
|
|
|
for (i = 0; i < NumberOfStrings; i++)
|
|
ComboBox_AddString(hWnd, Strings[i]);
|
|
}
|
|
|
|
PPH_STRING PhGetComboBoxString(
|
|
_In_ HWND hwnd,
|
|
_In_ INT Index
|
|
)
|
|
{
|
|
PPH_STRING string;
|
|
ULONG length;
|
|
|
|
if (Index == -1)
|
|
{
|
|
Index = ComboBox_GetCurSel(hwnd);
|
|
|
|
if (Index == -1)
|
|
return NULL;
|
|
}
|
|
|
|
length = ComboBox_GetLBTextLen(hwnd, Index);
|
|
|
|
if (length == CB_ERR)
|
|
return NULL;
|
|
if (length == 0)
|
|
return PhReferenceEmptyString();
|
|
|
|
string = PhCreateStringEx(NULL, length * 2);
|
|
|
|
if (ComboBox_GetLBText(hwnd, Index, string->Buffer) != CB_ERR)
|
|
{
|
|
return string;
|
|
}
|
|
else
|
|
{
|
|
PhDereferenceObject(string);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
INT PhSelectComboBoxString(
|
|
_In_ HWND hwnd,
|
|
_In_ PWSTR String,
|
|
_In_ BOOLEAN Partial
|
|
)
|
|
{
|
|
if (Partial)
|
|
{
|
|
return ComboBox_SelectString(hwnd, -1, String);
|
|
}
|
|
else
|
|
{
|
|
INT index;
|
|
|
|
index = ComboBox_FindStringExact(hwnd, -1, String);
|
|
|
|
if (index == CB_ERR)
|
|
return CB_ERR;
|
|
|
|
ComboBox_SetCurSel(hwnd, index);
|
|
|
|
return index;
|
|
}
|
|
}
|
|
|
|
PPH_STRING PhGetListBoxString(
|
|
_In_ HWND hwnd,
|
|
_In_ INT Index
|
|
)
|
|
{
|
|
PPH_STRING string;
|
|
ULONG length;
|
|
|
|
if (Index == -1)
|
|
{
|
|
Index = ListBox_GetCurSel(hwnd);
|
|
|
|
if (Index == -1)
|
|
return NULL;
|
|
}
|
|
|
|
length = ListBox_GetTextLen(hwnd, Index);
|
|
|
|
if (length == LB_ERR)
|
|
return NULL;
|
|
if (length == 0)
|
|
return PhReferenceEmptyString();
|
|
|
|
string = PhCreateStringEx(NULL, length * 2);
|
|
|
|
if (ListBox_GetText(hwnd, Index, string->Buffer) != LB_ERR)
|
|
{
|
|
return string;
|
|
}
|
|
else
|
|
{
|
|
PhDereferenceObject(string);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
VOID PhSetStateAllListViewItems(
|
|
_In_ HWND hWnd,
|
|
_In_ ULONG State,
|
|
_In_ ULONG Mask
|
|
)
|
|
{
|
|
ULONG i;
|
|
ULONG count;
|
|
|
|
count = ListView_GetItemCount(hWnd);
|
|
|
|
if (count == -1)
|
|
return;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
ListView_SetItemState(hWnd, i, State, Mask);
|
|
}
|
|
}
|
|
|
|
PVOID PhGetSelectedListViewItemParam(
|
|
_In_ HWND hWnd
|
|
)
|
|
{
|
|
INT index;
|
|
PVOID param;
|
|
|
|
index = PhFindListViewItemByFlags(
|
|
hWnd,
|
|
-1,
|
|
LVNI_SELECTED
|
|
);
|
|
|
|
if (index != -1)
|
|
{
|
|
if (PhGetListViewItemParam(
|
|
hWnd,
|
|
index,
|
|
¶m
|
|
))
|
|
{
|
|
return param;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
VOID PhGetSelectedListViewItemParams(
|
|
_In_ HWND hWnd,
|
|
_Out_ PVOID **Items,
|
|
_Out_ PULONG NumberOfItems
|
|
)
|
|
{
|
|
PH_ARRAY array;
|
|
ULONG index;
|
|
PVOID param;
|
|
|
|
PhInitializeArray(&array, sizeof(PVOID), 2);
|
|
index = -1;
|
|
|
|
while ((index = PhFindListViewItemByFlags(
|
|
hWnd,
|
|
index,
|
|
LVNI_SELECTED
|
|
)) != -1)
|
|
{
|
|
if (PhGetListViewItemParam(hWnd, index, ¶m))
|
|
PhAddItemArray(&array, ¶m);
|
|
}
|
|
|
|
*NumberOfItems = (ULONG)array.Count;
|
|
*Items = PhFinalArrayItems(&array);
|
|
}
|
|
|
|
VOID PhSetImageListBitmap(
|
|
_In_ HIMAGELIST ImageList,
|
|
_In_ INT Index,
|
|
_In_ HINSTANCE InstanceHandle,
|
|
_In_ LPCWSTR BitmapName
|
|
)
|
|
{
|
|
HBITMAP bitmap;
|
|
|
|
bitmap = LoadImage(InstanceHandle, BitmapName, IMAGE_BITMAP, 0, 0, 0);
|
|
|
|
if (bitmap)
|
|
{
|
|
ImageList_Replace(ImageList, Index, bitmap, NULL);
|
|
DeleteObject(bitmap);
|
|
}
|
|
}
|
|
|
|
static BOOLEAN SharedIconCacheHashtableEqualFunction(
|
|
_In_ PVOID Entry1,
|
|
_In_ PVOID Entry2
|
|
)
|
|
{
|
|
PPHP_ICON_ENTRY entry1 = Entry1;
|
|
PPHP_ICON_ENTRY entry2 = Entry2;
|
|
|
|
if (entry1->InstanceHandle != entry2->InstanceHandle ||
|
|
entry1->Width != entry2->Width ||
|
|
entry1->Height != entry2->Height)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (IS_INTRESOURCE(entry1->Name))
|
|
{
|
|
if (IS_INTRESOURCE(entry2->Name))
|
|
return entry1->Name == entry2->Name;
|
|
else
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (!IS_INTRESOURCE(entry2->Name))
|
|
return PhEqualStringZ(entry1->Name, entry2->Name, FALSE);
|
|
else
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static ULONG SharedIconCacheHashtableHashFunction(
|
|
_In_ PVOID Entry
|
|
)
|
|
{
|
|
PPHP_ICON_ENTRY entry = Entry;
|
|
ULONG nameHash;
|
|
|
|
if (IS_INTRESOURCE(entry->Name))
|
|
nameHash = PtrToUlong(entry->Name);
|
|
else
|
|
nameHash = PhHashBytes((PUCHAR)entry->Name, PhCountStringZ(entry->Name));
|
|
|
|
return nameHash ^ (PtrToUlong(entry->InstanceHandle) >> 5) ^ (entry->Width << 3) ^ entry->Height;
|
|
}
|
|
|
|
HICON PhLoadIcon(
|
|
_In_opt_ HINSTANCE InstanceHandle,
|
|
_In_ PWSTR Name,
|
|
_In_ ULONG Flags,
|
|
_In_opt_ ULONG Width,
|
|
_In_opt_ ULONG Height
|
|
)
|
|
{
|
|
static _LoadIconMetric loadIconMetric;
|
|
static _LoadIconWithScaleDown loadIconWithScaleDown;
|
|
|
|
PHP_ICON_ENTRY entry;
|
|
PPHP_ICON_ENTRY actualEntry;
|
|
HICON icon = NULL;
|
|
|
|
if (PhBeginInitOnce(&SharedIconCacheInitOnce))
|
|
{
|
|
loadIconMetric = (_LoadIconMetric)PhGetModuleProcAddress(L"comctl32.dll", "LoadIconMetric");
|
|
loadIconWithScaleDown = (_LoadIconWithScaleDown)PhGetModuleProcAddress(L"comctl32.dll", "LoadIconWithScaleDown");
|
|
SharedIconCacheHashtable = PhCreateHashtable(sizeof(PHP_ICON_ENTRY),
|
|
SharedIconCacheHashtableEqualFunction, SharedIconCacheHashtableHashFunction, 10);
|
|
PhEndInitOnce(&SharedIconCacheInitOnce);
|
|
}
|
|
|
|
if (Flags & PH_LOAD_ICON_SHARED)
|
|
{
|
|
PhAcquireQueuedLockExclusive(&SharedIconCacheLock);
|
|
|
|
entry.InstanceHandle = InstanceHandle;
|
|
entry.Name = Name;
|
|
entry.Width = PhpGetIconEntrySize(Width, Flags);
|
|
entry.Height = PhpGetIconEntrySize(Height, Flags);
|
|
actualEntry = PhFindEntryHashtable(SharedIconCacheHashtable, &entry);
|
|
|
|
if (actualEntry)
|
|
{
|
|
icon = actualEntry->Icon;
|
|
PhReleaseQueuedLockExclusive(&SharedIconCacheLock);
|
|
return icon;
|
|
}
|
|
}
|
|
|
|
if (Flags & (PH_LOAD_ICON_SIZE_SMALL | PH_LOAD_ICON_SIZE_LARGE))
|
|
{
|
|
if (loadIconMetric)
|
|
loadIconMetric(InstanceHandle, Name, (Flags & PH_LOAD_ICON_SIZE_SMALL) ? LIM_SMALL : LIM_LARGE, &icon);
|
|
}
|
|
else
|
|
{
|
|
if (loadIconWithScaleDown)
|
|
loadIconWithScaleDown(InstanceHandle, Name, Width, Height, &icon);
|
|
}
|
|
|
|
if (!icon && !(Flags & PH_LOAD_ICON_STRICT))
|
|
{
|
|
if (Flags & PH_LOAD_ICON_SIZE_SMALL)
|
|
{
|
|
static ULONG smallWidth = 0;
|
|
static ULONG smallHeight = 0;
|
|
|
|
if (!smallWidth)
|
|
smallWidth = GetSystemMetrics(SM_CXSMICON);
|
|
if (!smallHeight)
|
|
smallHeight = GetSystemMetrics(SM_CYSMICON);
|
|
|
|
Width = smallWidth;
|
|
Height = smallHeight;
|
|
}
|
|
else if (Flags & PH_LOAD_ICON_SIZE_LARGE)
|
|
{
|
|
static ULONG largeWidth = 0;
|
|
static ULONG largeHeight = 0;
|
|
|
|
if (!largeWidth)
|
|
largeWidth = GetSystemMetrics(SM_CXICON);
|
|
if (!largeHeight)
|
|
largeHeight = GetSystemMetrics(SM_CYICON);
|
|
|
|
Width = largeWidth;
|
|
Height = largeHeight;
|
|
}
|
|
|
|
icon = LoadImage(InstanceHandle, Name, IMAGE_ICON, Width, Height, 0);
|
|
}
|
|
|
|
if (Flags & PH_LOAD_ICON_SHARED)
|
|
{
|
|
if (icon)
|
|
{
|
|
if (!IS_INTRESOURCE(Name))
|
|
entry.Name = PhDuplicateStringZ(Name);
|
|
entry.Icon = icon;
|
|
PhAddEntryHashtable(SharedIconCacheHashtable, &entry);
|
|
}
|
|
|
|
PhReleaseQueuedLockExclusive(&SharedIconCacheLock);
|
|
}
|
|
|
|
return icon;
|
|
}
|
|
|
|
/**
|
|
* Gets the default icon used for executable files.
|
|
*
|
|
* \param SmallIcon A variable which receives the small default executable icon. Do not destroy the
|
|
* icon using DestroyIcon(); it is shared between callers.
|
|
* \param LargeIcon A variable which receives the large default executable icon. Do not destroy the
|
|
* icon using DestroyIcon(); it is shared between callers.
|
|
*/
|
|
VOID PhGetStockApplicationIcon(
|
|
_Out_opt_ HICON *SmallIcon,
|
|
_Out_opt_ HICON *LargeIcon
|
|
)
|
|
{
|
|
static PH_INITONCE initOnce = PH_INITONCE_INIT;
|
|
static HICON smallIcon = NULL;
|
|
static HICON largeIcon = NULL;
|
|
|
|
// This no longer uses SHGetFileInfo because it is *very* slow and causes many other DLLs to be
|
|
// loaded, increasing memory usage. The worst thing about it, however, is that it is horribly
|
|
// incompatible with multi-threading. The first time it is called, it tries to perform some
|
|
// one-time initialization. It guards this with a lock, but when multiple threads try to call
|
|
// the function at the same time, instead of waiting for initialization to finish it simply
|
|
// fails the other threads.
|
|
|
|
if (PhBeginInitOnce(&initOnce))
|
|
{
|
|
PPH_STRING systemDirectory;
|
|
PPH_STRING dllFileName;
|
|
|
|
// imageres,11 (Windows 10 and above), user32,0 (Vista and above) or shell32,2 (XP) contains
|
|
// the default application icon.
|
|
|
|
if (systemDirectory = PhGetSystemDirectory())
|
|
{
|
|
PH_STRINGREF dllBaseName;
|
|
ULONG index;
|
|
|
|
// TODO: Find a better solution.
|
|
if (WindowsVersion >= WINDOWS_10)
|
|
{
|
|
PhInitializeStringRef(&dllBaseName, L"\\imageres.dll");
|
|
index = 11;
|
|
}
|
|
else if (WindowsVersion >= WINDOWS_VISTA)
|
|
{
|
|
PhInitializeStringRef(&dllBaseName, L"\\user32.dll");
|
|
index = 0;
|
|
}
|
|
else
|
|
{
|
|
PhInitializeStringRef(&dllBaseName, L"\\shell32.dll");
|
|
index = 2;
|
|
}
|
|
|
|
dllFileName = PhConcatStringRef2(&systemDirectory->sr, &dllBaseName);
|
|
PhDereferenceObject(systemDirectory);
|
|
|
|
ExtractIconEx(dllFileName->Buffer, index, &largeIcon, &smallIcon, 1);
|
|
PhDereferenceObject(dllFileName);
|
|
}
|
|
|
|
// Fallback icons - this is bad, because the icon isn't scaled correctly.
|
|
if (!smallIcon)
|
|
smallIcon = LoadIcon(NULL, IDI_APPLICATION);
|
|
if (!largeIcon)
|
|
largeIcon = LoadIcon(NULL, IDI_APPLICATION);
|
|
|
|
PhEndInitOnce(&initOnce);
|
|
}
|
|
|
|
if (SmallIcon)
|
|
*SmallIcon = smallIcon;
|
|
if (LargeIcon)
|
|
*LargeIcon = largeIcon;
|
|
}
|
|
|
|
HICON PhGetFileShellIcon(
|
|
_In_opt_ PWSTR FileName,
|
|
_In_opt_ PWSTR DefaultExtension,
|
|
_In_ BOOLEAN LargeIcon
|
|
)
|
|
{
|
|
SHFILEINFO fileInfo;
|
|
ULONG iconFlag;
|
|
HICON icon;
|
|
|
|
if (DefaultExtension && PhEqualStringZ(DefaultExtension, L".exe", TRUE))
|
|
{
|
|
// Special case for executable files (see above for reasoning).
|
|
|
|
icon = NULL;
|
|
|
|
if (FileName)
|
|
{
|
|
ExtractIconEx(
|
|
FileName,
|
|
0,
|
|
LargeIcon ? &icon : NULL,
|
|
!LargeIcon ? &icon : NULL,
|
|
1
|
|
);
|
|
}
|
|
|
|
if (!icon)
|
|
{
|
|
PhGetStockApplicationIcon(
|
|
!LargeIcon ? &icon : NULL,
|
|
LargeIcon ? &icon : NULL
|
|
);
|
|
|
|
if (icon)
|
|
icon = DuplicateIcon(NULL, icon);
|
|
}
|
|
|
|
return icon;
|
|
}
|
|
|
|
iconFlag = LargeIcon ? SHGFI_LARGEICON : SHGFI_SMALLICON;
|
|
icon = NULL;
|
|
|
|
if (FileName && SHGetFileInfo(
|
|
FileName,
|
|
0,
|
|
&fileInfo,
|
|
sizeof(SHFILEINFO),
|
|
SHGFI_ICON | iconFlag
|
|
))
|
|
{
|
|
icon = fileInfo.hIcon;
|
|
}
|
|
|
|
if (!icon && DefaultExtension)
|
|
{
|
|
if (SHGetFileInfo(
|
|
DefaultExtension,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
&fileInfo,
|
|
sizeof(SHFILEINFO),
|
|
SHGFI_ICON | iconFlag | SHGFI_USEFILEATTRIBUTES
|
|
))
|
|
icon = fileInfo.hIcon;
|
|
}
|
|
|
|
return icon;
|
|
}
|
|
|
|
VOID PhpSetClipboardData(
|
|
_In_ HWND hWnd,
|
|
_In_ ULONG Format,
|
|
_In_ HANDLE Data
|
|
)
|
|
{
|
|
if (OpenClipboard(hWnd))
|
|
{
|
|
if (!EmptyClipboard())
|
|
goto Fail;
|
|
|
|
if (!SetClipboardData(Format, Data))
|
|
goto Fail;
|
|
|
|
CloseClipboard();
|
|
|
|
return;
|
|
}
|
|
|
|
Fail:
|
|
GlobalFree(Data);
|
|
}
|
|
|
|
VOID PhSetClipboardString(
|
|
_In_ HWND hWnd,
|
|
_In_ PPH_STRINGREF String
|
|
)
|
|
{
|
|
HANDLE data;
|
|
PVOID memory;
|
|
|
|
data = GlobalAlloc(GMEM_MOVEABLE, String->Length + sizeof(WCHAR));
|
|
memory = GlobalLock(data);
|
|
|
|
memcpy(memory, String->Buffer, String->Length);
|
|
*(PWCHAR)((PCHAR)memory + String->Length) = 0;
|
|
|
|
GlobalUnlock(memory);
|
|
|
|
PhpSetClipboardData(hWnd, CF_UNICODETEXT, data);
|
|
}
|
|
|
|
HWND PhCreateDialogFromTemplate(
|
|
_In_ HWND Parent,
|
|
_In_ ULONG Style,
|
|
_In_ PVOID Instance,
|
|
_In_ PWSTR Template,
|
|
_In_ DLGPROC DialogProc,
|
|
_In_ PVOID Parameter
|
|
)
|
|
{
|
|
HRSRC resourceInfo;
|
|
ULONG resourceSize;
|
|
HGLOBAL resourceHandle;
|
|
PDLGTEMPLATEEX dialog;
|
|
PDLGTEMPLATEEX dialogCopy;
|
|
HWND dialogHandle;
|
|
|
|
resourceInfo = FindResource(Instance, Template, MAKEINTRESOURCE(RT_DIALOG));
|
|
|
|
if (!resourceInfo)
|
|
return NULL;
|
|
|
|
resourceSize = SizeofResource(Instance, resourceInfo);
|
|
|
|
if (resourceSize == 0)
|
|
return NULL;
|
|
|
|
resourceHandle = LoadResource(Instance, resourceInfo);
|
|
|
|
if (!resourceHandle)
|
|
return NULL;
|
|
|
|
dialog = LockResource(resourceHandle);
|
|
|
|
if (!dialog)
|
|
return NULL;
|
|
|
|
dialogCopy = PhAllocateCopy(dialog, resourceSize);
|
|
|
|
if (dialogCopy->signature == 0xffff)
|
|
{
|
|
dialogCopy->style = Style;
|
|
}
|
|
else
|
|
{
|
|
((DLGTEMPLATE *)dialogCopy)->style = Style;
|
|
}
|
|
|
|
dialogHandle = CreateDialogIndirectParam(Instance, (DLGTEMPLATE *)dialogCopy, Parent, DialogProc, (LPARAM)Parameter);
|
|
|
|
PhFree(dialogCopy);
|
|
|
|
return dialogHandle;
|
|
}
|
|
|
|
BOOLEAN PhModalPropertySheet(
|
|
_Inout_ PROPSHEETHEADER *Header
|
|
)
|
|
{
|
|
// PropertySheet incorrectly discards WM_QUIT messages in certain cases, so we will use our own
|
|
// message loop. An example of this is when GetMessage (called by PropertySheet's message loop)
|
|
// dispatches a message directly from kernel-mode that causes the property sheet to close. In
|
|
// that case PropertySheet will retrieve the WM_QUIT message but will ignore it because of its
|
|
// buggy logic.
|
|
|
|
// This is also a good opportunity to introduce an auto-pool.
|
|
|
|
PH_AUTO_POOL autoPool;
|
|
HWND oldFocus;
|
|
HWND topLevelOwner;
|
|
HWND hwnd;
|
|
BOOL result;
|
|
MSG message;
|
|
|
|
PhInitializeAutoPool(&autoPool);
|
|
|
|
oldFocus = GetFocus();
|
|
topLevelOwner = Header->hwndParent;
|
|
|
|
while (topLevelOwner && (GetWindowLong(topLevelOwner, GWL_STYLE) & WS_CHILD))
|
|
topLevelOwner = GetParent(topLevelOwner);
|
|
|
|
if (topLevelOwner && (topLevelOwner == GetDesktopWindow() || EnableWindow(topLevelOwner, FALSE)))
|
|
topLevelOwner = NULL;
|
|
|
|
Header->dwFlags |= PSH_MODELESS;
|
|
hwnd = (HWND)PropertySheet(Header);
|
|
|
|
if (!hwnd)
|
|
{
|
|
if (topLevelOwner)
|
|
EnableWindow(topLevelOwner, TRUE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
while (result = GetMessage(&message, NULL, 0, 0))
|
|
{
|
|
if (result == -1)
|
|
break;
|
|
|
|
if (!PropSheet_IsDialogMessage(hwnd, &message))
|
|
{
|
|
TranslateMessage(&message);
|
|
DispatchMessage(&message);
|
|
}
|
|
|
|
PhDrainAutoPool(&autoPool);
|
|
|
|
// Destroy the window when necessary.
|
|
if (!PropSheet_GetCurrentPageHwnd(hwnd))
|
|
break;
|
|
}
|
|
|
|
if (result == 0)
|
|
PostQuitMessage((INT)message.wParam);
|
|
if (Header->hwndParent && GetActiveWindow() == hwnd)
|
|
SetActiveWindow(Header->hwndParent);
|
|
if (topLevelOwner)
|
|
EnableWindow(topLevelOwner, TRUE);
|
|
if (oldFocus && IsWindow(oldFocus))
|
|
SetFocus(oldFocus);
|
|
|
|
DestroyWindow(hwnd);
|
|
PhDeleteAutoPool(&autoPool);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID PhInitializeLayoutManager(
|
|
_Out_ PPH_LAYOUT_MANAGER Manager,
|
|
_In_ HWND RootWindowHandle
|
|
)
|
|
{
|
|
Manager->List = PhCreateList(4);
|
|
Manager->LayoutNumber = 0;
|
|
|
|
Manager->RootItem.Handle = RootWindowHandle;
|
|
GetClientRect(Manager->RootItem.Handle, &Manager->RootItem.Rect);
|
|
Manager->RootItem.OrigRect = Manager->RootItem.Rect;
|
|
Manager->RootItem.ParentItem = NULL;
|
|
Manager->RootItem.LayoutParentItem = NULL;
|
|
Manager->RootItem.LayoutNumber = 0;
|
|
Manager->RootItem.NumberOfChildren = 0;
|
|
Manager->RootItem.DeferHandle = NULL;
|
|
}
|
|
|
|
VOID PhDeleteLayoutManager(
|
|
_Inout_ PPH_LAYOUT_MANAGER Manager
|
|
)
|
|
{
|
|
ULONG i;
|
|
|
|
for (i = 0; i < Manager->List->Count; i++)
|
|
PhFree(Manager->List->Items[i]);
|
|
|
|
PhDereferenceObject(Manager->List);
|
|
}
|
|
|
|
// HACK: The math below is all horribly broken, especially the HACK for multiline tab controls.
|
|
|
|
PPH_LAYOUT_ITEM PhAddLayoutItem(
|
|
_Inout_ PPH_LAYOUT_MANAGER Manager,
|
|
_In_ HWND Handle,
|
|
_In_opt_ PPH_LAYOUT_ITEM ParentItem,
|
|
_In_ ULONG Anchor
|
|
)
|
|
{
|
|
PPH_LAYOUT_ITEM layoutItem;
|
|
RECT dummy = { 0 };
|
|
|
|
layoutItem = PhAddLayoutItemEx(
|
|
Manager,
|
|
Handle,
|
|
ParentItem,
|
|
Anchor,
|
|
dummy
|
|
);
|
|
|
|
layoutItem->Margin = layoutItem->Rect;
|
|
PhConvertRect(&layoutItem->Margin, &layoutItem->ParentItem->Rect);
|
|
|
|
if (layoutItem->ParentItem != layoutItem->LayoutParentItem)
|
|
{
|
|
// Fix the margin because the item has a dummy parent. They share the same layout parent
|
|
// item.
|
|
layoutItem->Margin.top -= layoutItem->ParentItem->Rect.top;
|
|
layoutItem->Margin.left -= layoutItem->ParentItem->Rect.left;
|
|
layoutItem->Margin.right = layoutItem->ParentItem->Margin.right;
|
|
layoutItem->Margin.bottom = layoutItem->ParentItem->Margin.bottom;
|
|
}
|
|
|
|
return layoutItem;
|
|
}
|
|
|
|
PPH_LAYOUT_ITEM PhAddLayoutItemEx(
|
|
_Inout_ PPH_LAYOUT_MANAGER Manager,
|
|
_In_ HWND Handle,
|
|
_In_opt_ PPH_LAYOUT_ITEM ParentItem,
|
|
_In_ ULONG Anchor,
|
|
_In_ RECT Margin
|
|
)
|
|
{
|
|
PPH_LAYOUT_ITEM item;
|
|
|
|
if (!ParentItem)
|
|
ParentItem = &Manager->RootItem;
|
|
|
|
item = PhAllocate(sizeof(PH_LAYOUT_ITEM));
|
|
item->Handle = Handle;
|
|
item->ParentItem = ParentItem;
|
|
item->LayoutNumber = Manager->LayoutNumber;
|
|
item->NumberOfChildren = 0;
|
|
item->DeferHandle = NULL;
|
|
item->Anchor = Anchor;
|
|
|
|
item->LayoutParentItem = item->ParentItem;
|
|
|
|
while ((item->LayoutParentItem->Anchor & PH_LAYOUT_DUMMY_MASK) &&
|
|
item->LayoutParentItem->LayoutParentItem)
|
|
{
|
|
item->LayoutParentItem = item->LayoutParentItem->LayoutParentItem;
|
|
}
|
|
|
|
item->LayoutParentItem->NumberOfChildren++;
|
|
|
|
GetWindowRect(Handle, &item->Rect);
|
|
MapWindowPoints(NULL, item->LayoutParentItem->Handle, (POINT *)&item->Rect, 2);
|
|
|
|
if (item->Anchor & PH_LAYOUT_TAB_CONTROL)
|
|
{
|
|
// We want to convert the tab control rectangle to the tab page display rectangle.
|
|
TabCtrl_AdjustRect(Handle, FALSE, &item->Rect);
|
|
}
|
|
|
|
item->Margin = Margin;
|
|
|
|
item->OrigRect = item->Rect;
|
|
|
|
PhAddItemList(Manager->List, item);
|
|
|
|
return item;
|
|
}
|
|
|
|
VOID PhpLayoutItemLayout(
|
|
_Inout_ PPH_LAYOUT_MANAGER Manager,
|
|
_Inout_ PPH_LAYOUT_ITEM Item
|
|
)
|
|
{
|
|
RECT rect;
|
|
BOOLEAN hasDummyParent;
|
|
|
|
if (Item->NumberOfChildren > 0 && !Item->DeferHandle)
|
|
Item->DeferHandle = BeginDeferWindowPos(Item->NumberOfChildren);
|
|
|
|
if (Item->LayoutNumber == Manager->LayoutNumber)
|
|
return;
|
|
|
|
// If this is the root item we must stop here.
|
|
if (!Item->ParentItem)
|
|
return;
|
|
|
|
PhpLayoutItemLayout(Manager, Item->ParentItem);
|
|
|
|
if (Item->ParentItem != Item->LayoutParentItem)
|
|
{
|
|
PhpLayoutItemLayout(Manager, Item->LayoutParentItem);
|
|
hasDummyParent = TRUE;
|
|
}
|
|
else
|
|
{
|
|
hasDummyParent = FALSE;
|
|
}
|
|
|
|
GetWindowRect(Item->Handle, &Item->Rect);
|
|
MapWindowPoints(NULL, Item->LayoutParentItem->Handle, (POINT *)&Item->Rect, 2);
|
|
|
|
if (Item->Anchor & PH_LAYOUT_TAB_CONTROL)
|
|
{
|
|
// We want to convert the tab control rectangle to the tab page display rectangle.
|
|
TabCtrl_AdjustRect(Item->Handle, FALSE, &Item->Rect);
|
|
}
|
|
|
|
if (!(Item->Anchor & PH_LAYOUT_DUMMY_MASK))
|
|
{
|
|
// Convert right/bottom into margins to make the calculations
|
|
// easier.
|
|
rect = Item->Rect;
|
|
PhConvertRect(&rect, &Item->LayoutParentItem->Rect);
|
|
|
|
if (!(Item->Anchor & (PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT)))
|
|
{
|
|
// TODO
|
|
PhRaiseStatus(STATUS_NOT_IMPLEMENTED);
|
|
}
|
|
else if (Item->Anchor & PH_ANCHOR_RIGHT)
|
|
{
|
|
if (Item->Anchor & PH_ANCHOR_LEFT)
|
|
{
|
|
rect.left = (hasDummyParent ? Item->ParentItem->Rect.left : 0) + Item->Margin.left;
|
|
rect.right = Item->Margin.right;
|
|
}
|
|
else
|
|
{
|
|
ULONG diff = Item->Margin.right - rect.right;
|
|
|
|
rect.left -= diff;
|
|
rect.right += diff;
|
|
}
|
|
}
|
|
|
|
if (!(Item->Anchor & (PH_ANCHOR_TOP | PH_ANCHOR_BOTTOM)))
|
|
{
|
|
// TODO
|
|
PhRaiseStatus(STATUS_NOT_IMPLEMENTED);
|
|
}
|
|
else if (Item->Anchor & PH_ANCHOR_BOTTOM)
|
|
{
|
|
if (Item->Anchor & PH_ANCHOR_TOP)
|
|
{
|
|
// tab control hack
|
|
rect.top = (hasDummyParent ? Item->ParentItem->Rect.top : 0) + Item->Margin.top;
|
|
rect.bottom = Item->Margin.bottom;
|
|
}
|
|
else
|
|
{
|
|
ULONG diff = Item->Margin.bottom - rect.bottom;
|
|
|
|
rect.top -= diff;
|
|
rect.bottom += diff;
|
|
}
|
|
}
|
|
|
|
// Convert the right/bottom back into co-ordinates.
|
|
PhConvertRect(&rect, &Item->LayoutParentItem->Rect);
|
|
Item->Rect = rect;
|
|
|
|
if (!(Item->Anchor & PH_LAYOUT_IMMEDIATE_RESIZE))
|
|
{
|
|
Item->LayoutParentItem->DeferHandle = DeferWindowPos(
|
|
Item->LayoutParentItem->DeferHandle, Item->Handle,
|
|
NULL, rect.left, rect.top,
|
|
rect.right - rect.left, rect.bottom - rect.top,
|
|
SWP_NOACTIVATE | SWP_NOZORDER
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// This is needed for tab controls, so that TabCtrl_AdjustRect will give us an
|
|
// up-to-date result.
|
|
SetWindowPos(
|
|
Item->Handle,
|
|
NULL, rect.left, rect.top,
|
|
rect.right - rect.left, rect.bottom - rect.top,
|
|
SWP_NOACTIVATE | SWP_NOZORDER
|
|
);
|
|
}
|
|
}
|
|
|
|
Item->LayoutNumber = Manager->LayoutNumber;
|
|
}
|
|
|
|
VOID PhLayoutManagerLayout(
|
|
_Inout_ PPH_LAYOUT_MANAGER Manager
|
|
)
|
|
{
|
|
ULONG i;
|
|
|
|
Manager->LayoutNumber++;
|
|
|
|
GetClientRect(Manager->RootItem.Handle, &Manager->RootItem.Rect);
|
|
|
|
for (i = 0; i < Manager->List->Count; i++)
|
|
{
|
|
PPH_LAYOUT_ITEM item = (PPH_LAYOUT_ITEM)Manager->List->Items[i];
|
|
|
|
PhpLayoutItemLayout(Manager, item);
|
|
}
|
|
|
|
for (i = 0; i < Manager->List->Count; i++)
|
|
{
|
|
PPH_LAYOUT_ITEM item = (PPH_LAYOUT_ITEM)Manager->List->Items[i];
|
|
|
|
if (item->DeferHandle)
|
|
{
|
|
EndDeferWindowPos(item->DeferHandle);
|
|
item->DeferHandle = NULL;
|
|
}
|
|
|
|
if (item->Anchor & PH_LAYOUT_FORCE_INVALIDATE)
|
|
{
|
|
InvalidateRect(item->Handle, NULL, FALSE);
|
|
}
|
|
}
|
|
|
|
if (Manager->RootItem.DeferHandle)
|
|
{
|
|
EndDeferWindowPos(Manager->RootItem.DeferHandle);
|
|
Manager->RootItem.DeferHandle = NULL;
|
|
}
|
|
}
|