waldemar21214
Freshman Member
Posts: 94
OS: Windows 10
Theme: Classic
CPU: i5-12400f
RAM: 16 GB DDR4 3600mhz
GPU: PNY 3060TI
|
Post by waldemar21214 on Jan 9, 2024 4:30:14 GMT -8
If you push the button but move the mouse out, the button remains pushed on hover even if you do not click it. Move the mouse out the button bounds, or out the NamespaceTreeControl window (treeview + folder pane with the button)? The "button" isn't actually a winapi button control, I just made the code draw a 3d frame in the correct spot under certain conditions. Anyways, I'll investigate your report and propably have it fixed in the next update. Other than that, does it work correctly and not hang explorer.exe? It works perfectly when I enabled "Lines At Root". Otherwise, clicking the plus / minus button does nothing. That's weird, can you try using WinSpy to see if TVS_HASBUTTONS style is enabled on the treeview control when my mod is used? My mod relies on this style being set, because it doesn't actually have any code to handle clicks on the +/- buttons, the control does it itself. God, this is beautiful. Worked pretty much flawlessly for me, gad some padding issues on start, but enabling "Lines on root" fixed this. Upper grey area flashes white when window updates but i think it is normal. Amazing job. Your padding issues were propably caused by the fact that you did not set the "Full size offsets for subfolders" in openshell explorer settings. The folder band is flashing because micr soft code tries to fight my mod when window resizing occurs. But my mod is so strong and awesome that it always wins in the end.
|
|
Cynosphere
Freshman Member
Posts: 46
OS: Windows 10 Home 22H2
Theme: Classic (Scheme: Amora Focus)
CPU: AMD Ryzen 7 3700X
RAM: 32 GB
GPU: AMD Radeon RX 7900 XT
|
Post by Cynosphere on Jan 9, 2024 19:00:07 GMT -8
I got nitpicky and made it more accurate and also added an option to draw the lines using the highlight color for better visibility on dark themes {Spoiler} // ==WindhawkMod== // @id classic-explorer-treeview // @name Classic Explorer Treeview // @description Modifies Folder Treeview in file explorer so as to make it look more classic. // @version 0.3 // @author Waldemar // @github https://github.com/CyprinusCarpio // @include explorer.exe // @compilerOptions -lcomdlg32 -lcomctl32 -lgdi32 -lshlwapi -loleaut32 // ==/WindhawkMod==
// ==WindhawkModSettings== /* - FoldersText: Folders $name: Folders Pane Text $description: This is the text to be displayed on top of the folders pane. - DrawLines: true $name: Draw Dotted Lines $description: Use TVS_HASLINES style. Disable for a more XP-like look. - LinesAtRoot: false $name: Lines At Root $description: Use TVS_LINESATROOT style. - DrawButtons: true $name: Draw +/- Buttons $description: Should the mod draw it's own +/- buttons. - UseHighlight: false $name: Use Highlight color $description: Use highlight color instead of shadow color for drawing lines. May produce better results on some dark themes. */ // ==/WindhawkModSettings==
// ==WindhawkModReadme== /* # Classic Explorer Treeview
This mod changes the Explorer Treeview (otherwise known as Folder Pane or Navigation Pane) to look more classic. It draws it's own Folder Band with a functional X button. If you are using OpenShell, then you need to set the correct settings for Classic Explorer in the Navigation Pane tab: Tree item spacing -2, Full size offset for sub-folders. If you are using Windows 11 23H2, then set the tree item spacing to -6, and enable compact spacing in the Explorer folder settings.
This mod is WIP and may not work correctly.
# Changelog: ## 0.3 - Proper positioning and sizing of the folder band (Cynosphere) - Proper close button "glyph" (Cynosphere) - Draw bottom rebar line on the folder band (Cynosphere) - Add option to use highlight color for drawing lines for better visibility on dark themes (Cynosphere)
## 0.2 - Fixed a bug with drawing lines when LinesAtRoot is enabled. - Added a 3D border to the X button when mouse is hovering over it. - Added a option not to draw +/- buttons so that the mod can be used when themes are enabled. - Fixed the treeview being drawn over in certain circumstances.
## 0.1 - Initial release. */ // ==/WindhawkModReadme==
#include <commctrl.h> #include <shlwapi.h> #include <windhawk_api.h> #include <windhawk_utils.h> #include <windowsx.h> #include <wingdi.h>
#include <string> #include <vector>
enum NSCTExtraFlags { MOUSE_OVER_BUTTON = 1 << 0, MOUSE_PRESSED = 1 << 1 };
struct NSCTExtra { HWND hWnd; int eFlags = 0;
NSCTExtra(HWND h) { hWnd = h; } };
std::vector<NSCTExtra> g_subclassedNSTCs; bool g_drawDottedLines = true; bool g_linesAtRoot = false; bool g_drawButtons = true; bool g_useHighlight = false; std::wstring g_foldersPaneText = L"Folders";
void DrawDottedLinesAndButtons(HWND hTree, HDC& hdc) { HTREEITEM hItem = TreeView_GetRoot(hTree); RECT rect; while (hItem != NULL) { TreeView_GetItemRect(hTree, hItem, &rect, TRUE); rect.left -= 33; rect.top += 6; TVITEM tvi; tvi.mask = TVIF_CHILDREN; tvi.hItem = hItem;
bool isTopLevel = rect.left <= (g_linesAtRoot ? 10 : -10);
if (g_drawDottedLines) { LOGBRUSH LogBrush; LogBrush.lbColor = GetSysColor(g_useHighlight ? COLOR_3DHIGHLIGHT : COLOR_3DSHADOW); LogBrush.lbStyle = PS_SOLID; HPEN hPen = ExtCreatePen(PS_COSMETIC | PS_ALTERNATE, 1, &LogBrush, 0, NULL);
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen); if (!isTopLevel) { HTREEITEM nextItem = TreeView_GetNextSibling(hTree, hItem); MoveToEx(hdc, rect.left + 4, isTopLevel ? rect.top + 4 : rect.top - 4, NULL); if (nextItem != NULL) { RECT nextItemRect; TreeView_GetItemRect(hTree, nextItem, &nextItemRect, TRUE); LineTo(hdc, rect.left + 4, nextItemRect.top + 5); } else { LineTo(hdc, rect.left + 4, rect.bottom - 7); } }
MoveToEx(hdc, rect.left + 6, rect.top + 4, NULL); LineTo(hdc, rect.left + 14, rect.top + 4); SelectObject(hdc, hOldPen); DeleteObject(hPen); } if (g_drawButtons && !(isTopLevel && !g_linesAtRoot) && TreeView_GetItem(hTree, &tvi) && tvi.cChildren > 0) { UINT state = TreeView_GetItemState(hTree, hItem, TVIS_EXPANDED);
HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_WINDOW)); HBRUSH original = (HBRUSH)SelectObject(hdc, brush); RECT buttonRect; buttonRect.left = rect.left; buttonRect.top = rect.top; buttonRect.right = rect.left + 9; buttonRect.bottom = rect.top + 9; FillRect(hdc, &buttonRect, brush); DeleteObject(brush); brush = CreateSolidBrush(GetSysColor(g_useHighlight ? COLOR_3DHIGHLIGHT : COLOR_3DSHADOW)); FrameRect(hdc, &buttonRect, brush); DeleteObject(brush); SelectObject(hdc, original);
HPEN hPen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT)); HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
if (state & TVIS_EXPANDED) { MoveToEx(hdc, rect.left + 2, rect.top + 4, NULL); LineTo(hdc, rect.left + 7, rect.top + 4); } else { MoveToEx(hdc, rect.left + 2, rect.top + 4, NULL); LineTo(hdc, rect.left + 7, rect.top + 4); MoveToEx(hdc, rect.left + 4, rect.top + 2, NULL); LineTo(hdc, rect.left + 4, rect.top + 7); }
SelectObject(hdc, hOldPen); DeleteObject(hPen); }
HTREEITEM child = TreeView_GetChild(hTree, hItem); if (child != NULL) { hItem = child; continue; }
HTREEITEM sibling = TreeView_GetNextSibling(hTree, hItem); if (sibling != NULL) { hItem = sibling; continue; }
hItem = TreeView_GetNextVisible(hTree, hItem); } }
LRESULT CALLBACK NSCSubclassProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ DWORD_PTR dwRefData) { switch (uMsg) { case WM_NOTIFY: { switch (((LPNMHDR)lParam)->code) { case NM_CUSTOMDRAW: { HWND hTree = FindWindowEx(hWnd, NULL, L"SysTreeView32", NULL); LPNMTVCUSTOMDRAW pCustomDraw = (LPNMTVCUSTOMDRAW)lParam; switch (pCustomDraw->nmcd.dwDrawStage) { case CDDS_PREPAINT: return CDRF_NOTIFYPOSTPAINT; case CDDS_POSTPAINT: DrawDottedLinesAndButtons(hTree, pCustomDraw->nmcd.hdc); return S_OK; break; default: break; } } } } break; case WM_PAINT: { HDC hdc = GetWindowDC(hWnd); PAINTSTRUCT ps; HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE)); HBRUSH original = (HBRUSH)SelectObject(hdc, brush);
BeginPaint(hWnd, &ps);
RECT rect; GetClientRect(hWnd, &rect); long origBottom = rect.bottom; rect.bottom = rect.top + 20;
FillRect(hdc, &rect, brush);
long origTop = rect.top;
// draw the bottom rebar line HBRUSH rebarLineTop = CreateSolidBrush(GetSysColor(COLOR_3DSHADOW)); rect.top = origTop + 22; rect.bottom = rect.top + 1; FillRect(hdc, &rect, rebarLineTop);
HBRUSH rebarLineBottom = CreateSolidBrush(GetSysColor(COLOR_3DHIGHLIGHT)); rect.top += 1; rect.bottom += 1; FillRect(hdc, &rect, rebarLineBottom);
long origLeft = rect.left; long origRight = rect.right;
// draw the close button HBRUSH closeColor = CreateSolidBrush(GetSysColor(COLOR_BTNTEXT));
// ## ## rect.top = origTop + 7; rect.left = origRight - 17; rect.bottom = rect.top + 1; rect.right = rect.left + 2; FillRect(hdc, &rect, closeColor);
rect.left += 6; rect.right += 6; FillRect(hdc, &rect, closeColor);
// ## ## rect.top += 1; rect.bottom += 1; rect.left -= 1; rect.right -= 1; FillRect(hdc, &rect, closeColor);
rect.left -= 4; rect.right -= 4; FillRect(hdc, &rect, closeColor);
// #### rect.top += 1; rect.bottom += 1; rect.left += 1; rect.right += 3; FillRect(hdc, &rect, closeColor);
// ## rect.top += 1; rect.bottom += 1; rect.left += 1; rect.right -= 1; FillRect(hdc, &rect, closeColor);
// reset and draw last three lines in reverse cause easier math // ## ## rect.top = origTop + 13; rect.left = origRight - 17; rect.bottom = rect.top + 1; rect.right = rect.left + 2; FillRect(hdc, &rect, closeColor);
rect.left += 6; rect.right += 6; FillRect(hdc, &rect, closeColor);
// ## ## rect.top -= 1; rect.bottom -= 1; rect.left -= 1; rect.right -= 1; FillRect(hdc, &rect, closeColor);
rect.left -= 4; rect.right -= 4; FillRect(hdc, &rect, closeColor);
// #### rect.top -= 1; rect.bottom -= 1; rect.left += 1; rect.right += 3; FillRect(hdc, &rect, closeColor);
rect.top = origTop; rect.bottom = origBottom; rect.left = origLeft; rect.right = origRight;
SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT)); SetBkColor(hdc, GetSysColor(COLOR_BTNFACE)); //TextOut(hdc, rect.right - 14, 0, L"x", 1);
DrawEdge(hdc, &rect, EDGE_ETCHED, BF_RECT);
int eFlags = 0; size_t s = g_subclassedNSTCs.size(); for (size_t i = 0; i < s; i++) { if (hWnd == g_subclassedNSTCs[i].hWnd) { eFlags = g_subclassedNSTCs[i].eFlags; break; } } if (eFlags & MOUSE_OVER_BUTTON) { RECT buttonFrame; GetClientRect(hWnd, &buttonFrame); buttonFrame.left = buttonFrame.right - 23; buttonFrame.right = buttonFrame.right - 3; buttonFrame.bottom = buttonFrame.top + 19; buttonFrame.top = buttonFrame.top + 2; DrawEdge( hdc, &buttonFrame, eFlags & MOUSE_PRESSED ? BDR_SUNKENOUTER : BDR_RAISEDINNER, BF_RECT); }
NONCLIENTMETRICS ncm; ncm.cbSize = sizeof(NONCLIENTMETRICS); SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);
HFONT hFont = CreateFontIndirect(&ncm.lfMessageFont); SelectObject(hdc, hFont); SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
TextOut(hdc, 8, 5, g_foldersPaneText.c_str(), g_foldersPaneText.length());
SelectObject(hdc, original); EndPaint(hWnd, &ps); ReleaseDC(hWnd, hdc); } break; case WM_MOUSEMOVE: { int xPos = GET_X_LPARAM(lParam); int yPos = GET_Y_LPARAM(lParam); RECT rect; GetClientRect(hWnd, &rect); rect.left = rect.right - 23; rect.right -= 3; rect.bottom = rect.top + 19; rect.top += 2; bool mouseInside = false; if (xPos >= rect.left && xPos <= rect.right && yPos >= rect.top && yPos <= rect.bottom) { mouseInside = true; } size_t s = g_subclassedNSTCs.size(); for (size_t i = 0; i < s; i++) { if (hWnd == g_subclassedNSTCs[i].hWnd) { if (mouseInside) { InvalidateRect(hWnd, &rect, FALSE); g_subclassedNSTCs[i].eFlags |= MOUSE_OVER_BUTTON; } else if (g_subclassedNSTCs[i].eFlags & MOUSE_OVER_BUTTON){ g_subclassedNSTCs[i].eFlags &= ~MOUSE_OVER_BUTTON; InvalidateRect(hWnd, &rect, FALSE); } break; } }
} break; case WM_LBUTTONDOWN: { size_t s = g_subclassedNSTCs.size(); for (size_t i = 0; i < s; i++) { if (hWnd == g_subclassedNSTCs[i].hWnd) { RECT rect; GetClientRect(hWnd, &rect); rect.left = rect.right - 23; rect.right -= 3; rect.bottom = rect.top + 19; rect.top += 2; InvalidateRect(hWnd, &rect, FALSE); g_subclassedNSTCs[i].eFlags |= MOUSE_PRESSED; break; } } } break; case WM_LBUTTONUP: { size_t s = g_subclassedNSTCs.size(); for (size_t i = 0; i < s; i++) { if (hWnd == g_subclassedNSTCs[i].hWnd && g_subclassedNSTCs[i].eFlags & MOUSE_PRESSED) { g_subclassedNSTCs[i].eFlags &= ~MOUSE_PRESSED; break; } } int xPos = GET_X_LPARAM(lParam); int yPos = GET_Y_LPARAM(lParam); RECT rect; GetClientRect(hWnd, &rect); rect.left = rect.right - 22; rect.right -= 3; rect.bottom = rect.top + 20; rect.top += 2; if (xPos >= rect.left && xPos <= rect.right && yPos >= rect.top && yPos <= rect.bottom) { for (size_t i = 0; i < s; i++) { if (hWnd == g_subclassedNSTCs[i].hWnd) { g_subclassedNSTCs[i].eFlags &= ~MOUSE_OVER_BUTTON; break; } } static GUID SID_FrameManager = { 0x31e4fa78, 0x02b4, 0x419f, {0x94, 0x30, 0x7b, 0x75, 0x85, 0x23, 0x7c, 0x77}}; static GUID SID_PerBrowserPropertyBag = { 0xa3b24a0a, 0x7b68, 0x448d, {0x99, 0x79, 0xc7, 0x00, 0x05, 0x9c, 0x3a, 0xd1}}; static GUID GUIDIPropertyBag = { 0x55272a00, 0x42cb, 0x11ce, {0x81, 0x35, 0x00, 0xaa, 0x00, 0x4b, 0xb8, 0x51}}; static GUID GUIDIUnknown = { 0x00000000, 0x0000, 0x0000, {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
IOleWindow* browser = (IOleWindow*)SendMessage( GetParent(GetParent(GetParent(GetParent(hWnd)))), WM_USER + 7, 0, 0); browser->AddRef(); IPropertyBag* bag; IUnknown* frame; IUnknown_QueryService(browser, SID_FrameManager, GUIDIUnknown, (void**)&frame); frame->AddRef(); IUnknown_QueryService(frame, SID_PerBrowserPropertyBag, GUIDIPropertyBag, (void**)&bag); bag->AddRef(); VARIANT val = {VT_EMPTY}; VariantClear(&val); val.vt = VT_BOOL; val.boolVal = VARIANT_FALSE; bag->Write(L"PageSpaceControlSizer_Visible", &val); frame->Release(); browser->Release(); bag->Release(); } } break;
case WM_SIZE: { HWND treeview = FindWindowEx(hWnd, NULL, L"SysTreeView32", NULL); RECT rect; GetClientRect(GetParent(treeview), &rect); SetWindowPos(treeview, 0, 2, 24, rect.right - 4, rect.bottom - 24, SWP_DRAWFRAME | SWP_NOZORDER); InvalidateRect(hWnd, &rect, true); return S_OK; } break;
case WM_DESTROY: g_subclassedNSTCs.erase(std::remove_if( g_subclassedNSTCs.begin(), g_subclassedNSTCs.end(), [hWnd](NSCTExtra ne) { return ne.hWnd == hWnd; })); return 0; break; }
return DefSubclassProc(hWnd, uMsg, wParam, lParam); }
using CreateWindowExW_t = decltype(&CreateWindowExW); CreateWindowExW_t pOriginalCreateWindowExW; HWND WINAPI CreateWindowExWHook(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) { BOOL bTextualClassName = ((ULONG_PTR)lpClassName & ~(ULONG_PTR)0xffff) != 0; if (hWndParent != NULL && bTextualClassName && wcsicmp(lpClassName, L"SysTreeView32") == 0) { wchar_t bufr[32]; GetClassName(hWndParent, bufr, 32);
if (lstrcmpW(bufr, L"NamespaceTreeControl") == 0) { dwExStyle |= WS_EX_COMPOSITED; dwStyle = TVS_HASBUTTONS | TVS_EDITLABELS | TVS_SHOWSELALWAYS | WS_TABSTOP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE | WS_CHILD; if (g_linesAtRoot) dwStyle |= TVS_LINESATROOT; Y += 20; nHeight -= 20;
HWND hWnd = pOriginalCreateWindowExW( dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
TreeView_SetIndent(hWnd, 20); TreeView_SetItemHeight(hWnd, 6);
return hWnd; } }
HWND hWnd = pOriginalCreateWindowExW(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
if (bTextualClassName && wcsicmp(lpClassName, L"NamespaceTreeControl") == 0) { if (WindhawkUtils::SetWindowSubclassFromAnyThread(hWnd, NSCSubclassProc, NULL)) { g_subclassedNSTCs.push_back(NSCTExtra(hWnd)); } }
return hWnd; }
typedef HRESULT (*INameSpaceTreeControl2__s_SetStateImageList_t)( struct _IMAGELIST*); INameSpaceTreeControl2__s_SetStateImageList_t INameSpaceTreeControl2__s_SetStateImageList_orig; HRESULT INameSpaceTreeControl2__s_SetStateImageList_Hook( struct _IMAGELIST* himagelist) { return S_OK; }
typedef HRESULT (*INameSpaceTreeControl2__s_ApplyTopMargin_t)(void); INameSpaceTreeControl2__s_ApplyTopMargin_t INameSpaceTreeControl2__s_ApplyTopMargin_orig; HRESULT INameSpaceTreeControl2__s_ApplyTopMargin_Hook() { return S_OK; }
BOOL Wh_ModInit() { Wh_Log(L"Classic Explorer Treeview Init"); if (!Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExWHook, (void**)&pOriginalCreateWindowExW)) { Wh_Log(L"Failed to hook function CreateWindowExW"); return FALSE; }
HMODULE hExplorerFrame = GetModuleHandle(L"explorerframe.dll");
WindhawkUtils::SYMBOL_HOOK hooks[] = { {{L"private: long __cdecl CNscTree::ApplyTopMargin(void)"}, (void**)&INameSpaceTreeControl2__s_ApplyTopMargin_orig, (void*)INameSpaceTreeControl2__s_ApplyTopMargin_Hook, FALSE}, {{L"public: virtual long __cdecl CNscTree::SetStateImageList(struct " L"_IMAGELIST *)"}, (void**)&INameSpaceTreeControl2__s_SetStateImageList_orig, (void*)INameSpaceTreeControl2__s_SetStateImageList_Hook, FALSE}};
if (!WindhawkUtils::HookSymbols(hExplorerFrame, hooks, 2)) { Wh_Log(L"Failed to hook CNscTree methods"); return FALSE; } g_drawDottedLines = Wh_GetIntSetting(L"DrawLines"); g_linesAtRoot = Wh_GetIntSetting(L"LinesAtRoot"); g_drawButtons = Wh_GetIntSetting(L"DrawButtons"); g_foldersPaneText = std::wstring(Wh_GetStringSetting(L"FoldersText")); g_useHighlight = Wh_GetIntSetting(L"UseHighlight"); Wh_Log(L"Classic Explorer Treeview init completed successfully."); return TRUE; }
void Wh_ModBeforeUninit(void) { Wh_Log(L"Classic Explorer Treeview Uninit"); size_t s = g_subclassedNSTCs.size(); Wh_Log(L"Removing subclasses from %i controls.", s); for (size_t i = 0; i < s; i++) { WindhawkUtils::RemoveWindowSubclassFromAnyThread( g_subclassedNSTCs[i].hWnd, NSCSubclassProc); } } void Wh_ModSettingsChanged(void) { g_drawDottedLines = Wh_GetIntSetting(L"DrawLines"); g_linesAtRoot = Wh_GetIntSetting(L"LinesAtRoot"); g_drawButtons = Wh_GetIntSetting(L"DrawButtons"); g_foldersPaneText = std::wstring(Wh_GetStringSetting(L"FoldersText")); g_useHighlight = Wh_GetIntSetting(L"UseHighlight"); }
|
|
waldemar21214
Freshman Member
Posts: 94
OS: Windows 10
Theme: Classic
CPU: i5-12400f
RAM: 16 GB DDR4 3600mhz
GPU: PNY 3060TI
|
Post by waldemar21214 on Feb 7, 2024 10:43:47 GMT -8
Released update 0.5 Quite a few problems were fixed. The folder band is now scaled up to accomodate larger Menu font sizes, although it's not fully accurate to the real InfoBand. Many memory leaks were fixed, which should improve stability. Gaps to the sides of the treeview should be fixed now. Scrollbar buttons are fixed.
Also, forget everything I said about the gap between the ReBar and the folder band, this was caused by a inaccurate tutorial which advised offsetting the Organize bar by 28px, not 32px as it should. I've now corrected this problem on my main install and all looks fine.
The explorer.exe instability issue still eludes me, unfortunately. When it will finally be squished, the mod will be fully released. After that, I may add a NT4 mode, which will look like so: The item view can have the header text added in the same way I do it for the treeview, and the OpenShell buttons can get 3d borders. This will bring us much closer to pixel-perfect NT4 file explorer.
|
|