[BROWSEUI] CExplorerBand: Refresh on change notification (#7141)

Update UI on filesystem change.
JIRA issue: CORE-12619
- Add Refresh and RefreshRecurse
  methods.
- Add IsTreeItemInEnum,
  TreeItemHasThisChild,
  GetItemEnum, and
  ItemHasAnyChild helper
  methods.
- Delete RenameItem, DeleteItem
  and RefreshTreePidl methods
  and _ReparsePIDL function.
- Add WM_TIMER message
  handler (OnTimer) to delay
  refreshing.
- Refresh TreeView on change
  notification.
This commit is contained in:
Katayama Hirofumi MZ 2024-07-16 21:21:41 +09:00 committed by GitHub
parent 6f277e9766
commit 580824ae1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 196 additions and 241 deletions

View file

@ -69,71 +69,6 @@ HRESULT GetDisplayName(LPCITEMIDLIST pidlDirectory,TCHAR *szDisplayName,UINT cch
return hr;
}
/*
This is a Windows hack, because shell event messages in Windows gives an
ill-formed PIDL stripped from useful data that parses incorrectly with SHGetFileInfo.
So we need to re-enumerate subfolders until we find one with the same name.
*/
HRESULT _ReparsePIDL(
_In_ LPCITEMIDLIST buggyPidl,
_Out_ LPITEMIDLIST *cleanPidl)
{
HRESULT hr;
CComPtr<IShellFolder> folder;
CComPtr<IPersistFolder2> persist;
CComPtr<IEnumIDList> pEnumIDList;
LPITEMIDLIST childPidl;
LPITEMIDLIST correctChild;
LPITEMIDLIST correctParent;
ULONG fetched;
DWORD EnumFlags;
EnumFlags = SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN;
hr = SHBindToParent(buggyPidl, IID_PPV_ARG(IShellFolder, &folder), (LPCITEMIDLIST*)&childPidl);
*cleanPidl = NULL;
if (!SUCCEEDED(hr))
{
ERR("Can't bind to parent folder\n");
return hr;
}
hr = folder->QueryInterface(IID_PPV_ARG(IPersistFolder2, &persist));
if (!SUCCEEDED(hr))
{
ERR("PIDL doesn't belong to the shell namespace, aborting\n");
return hr;
}
hr = persist->GetCurFolder(&correctParent);
if (!SUCCEEDED(hr))
{
ERR("Unable to get current folder\n");
return hr;
}
hr = folder->EnumObjects(NULL,EnumFlags,&pEnumIDList);
// avoid broken IShellFolder implementations that return null pointer with success
if (!SUCCEEDED(hr) || !pEnumIDList)
{
ERR("Can't enum the folder !\n");
return hr;
}
while(SUCCEEDED(pEnumIDList->Next(1, &correctChild, &fetched)) && correctChild && fetched)
{
if (!folder->CompareIDs(0, childPidl, correctChild))
{
*cleanPidl = ILCombine(correctParent, correctChild);
ILFree(correctChild);
goto Cleanup;
}
ILFree(correctChild);
}
Cleanup:
ILFree(correctParent);
return hr;
}
CExplorerBand::CExplorerBand()
: m_pSite(NULL)
, m_fVisible(FALSE)
@ -155,15 +90,11 @@ CExplorerBand::~CExplorerBand()
void CExplorerBand::InitializeExplorerBand()
{
// Init the treeview here
HRESULT hr;
LPITEMIDLIST pidl;
CComPtr<IWebBrowser2> browserService;
SHChangeNotifyEntry shcne;
hr = SHGetDesktopFolder(&m_pDesktop);
HRESULT hr = SHGetDesktopFolder(&m_pDesktop);
if (FAILED_UNEXPECTEDLY(hr))
return;
LPITEMIDLIST pidl;
hr = SHGetFolderLocation(m_hWnd, CSIDL_DESKTOP, NULL, 0, &pidl);
if (FAILED_UNEXPECTEDLY(hr))
return;
@ -192,21 +123,25 @@ void CExplorerBand::InitializeExplorerBand()
// Navigate to current folder position
NavigateToCurrentFolder();
#define TARGET_EVENTS ( \
SHCNE_DRIVEADD | SHCNE_MKDIR | SHCNE_CREATE | SHCNE_DRIVEREMOVED | SHCNE_RMDIR | \
SHCNE_DELETE | SHCNE_RENAMEFOLDER | SHCNE_RENAMEITEM | SHCNE_UPDATEDIR | \
SHCNE_UPDATEITEM | SHCNE_ASSOCCHANGED \
)
// Register shell notification
shcne.pidl = pidl;
shcne.fRecursive = TRUE;
m_shellRegID = SHChangeNotifyRegister(
m_hWnd,
SHCNRF_NewDelivery | SHCNRF_ShellLevel | SHCNRF_InterruptLevel | SHCNRF_RecursiveInterrupt,
SHCNE_DISKEVENTS | SHCNE_RENAMEFOLDER | SHCNE_RMDIR | SHCNE_MKDIR,
WM_USER_SHELLEVENT,
1,
&shcne);
SHChangeNotifyEntry shcne = { pidl, TRUE };
m_shellRegID = SHChangeNotifyRegister(m_hWnd,
SHCNRF_NewDelivery | SHCNRF_ShellLevel,
TARGET_EVENTS,
WM_USER_SHELLEVENT,
1, &shcne);
if (!m_shellRegID)
{
ERR("Something went wrong, error %08x\n", GetLastError());
}
// Register browser connection endpoint
CComPtr<IWebBrowser2> browserService;
hr = IUnknown_QueryService(m_pSite, SID_SWebBrowserApp, IID_PPV_ARG(IWebBrowser2, &browserService));
if (FAILED_UNEXPECTEDLY(hr))
return;
@ -375,14 +310,21 @@ BOOL CExplorerBand::OnTreeItemExpanding(LPNMTREEVIEW pnmtv)
BOOL CExplorerBand::OnTreeItemDeleted(LPNMTREEVIEW pnmtv)
{
// Navigate to parent when deleting selected item
HTREEITEM hItem = pnmtv->itemOld.hItem;
HTREEITEM hParent = TreeView_GetParent(m_hWnd, hItem);
if (hParent && TreeView_GetSelection(m_hWnd) == hItem)
TreeView_SelectItem(m_hWnd, hParent);
/* Destroy memory associated to our node */
NodeInfo* ptr = GetNodeInfo(pnmtv->itemOld.hItem);
if (ptr)
{
ILFree(ptr->relativePidl);
ILFree(ptr->absolutePidl);
delete ptr;
}
NodeInfo* pNode = GetNodeInfo(hItem);
if (!pNode)
return FALSE;
ILFree(pNode->relativePidl);
ILFree(pNode->absolutePidl);
delete pNode;
return TRUE;
}
@ -591,46 +533,179 @@ LRESULT CExplorerBand::OnShellEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOO
return 0;
}
BOOL
CExplorerBand::IsTreeItemInEnum(
_In_ HTREEITEM hItem,
_In_ IEnumIDList *pEnum)
{
NodeInfo* pNodeInfo = GetNodeInfo(hItem);
if (!pNodeInfo)
return FALSE;
pEnum->Reset();
CComHeapPtr<ITEMIDLIST_RELATIVE> pidlTemp;
while (pEnum->Next(1, &pidlTemp, NULL) == S_OK)
{
if (ILIsEqual(pidlTemp, pNodeInfo->relativePidl))
return TRUE;
pidlTemp.Free();
}
return FALSE;
}
BOOL
CExplorerBand::TreeItemHasThisChild(
_In_ HTREEITEM hItem,
_In_ PCITEMID_CHILD pidlChild)
{
for (hItem = TreeView_GetChild(m_hWnd, hItem); hItem;
hItem = TreeView_GetNextSibling(m_hWnd, hItem))
{
NodeInfo* pNodeInfo = GetNodeInfo(hItem);
if (ILIsEqual(pNodeInfo->relativePidl, pidlChild))
return TRUE;
}
return FALSE;
}
HRESULT
CExplorerBand::GetItemEnum(
_Out_ CComPtr<IEnumIDList>& pEnum,
_In_ HTREEITEM hItem)
{
NodeInfo* pNodeInfo = GetNodeInfo(hItem);
CComPtr<IShellFolder> psfDesktop;
HRESULT hr = SHGetDesktopFolder(&psfDesktop);
if (FAILED(hr))
return hr;
CComPtr<IShellFolder> pFolder;
hr = psfDesktop->BindToObject(pNodeInfo->absolutePidl, NULL, IID_PPV_ARG(IShellFolder, &pFolder));
if (FAILED(hr))
return hr;
return pFolder->EnumObjects(NULL, SHCONTF_FOLDERS, &pEnum);
}
BOOL CExplorerBand::ItemHasAnyChild(_In_ HTREEITEM hItem)
{
CComPtr<IEnumIDList> pEnum;
HRESULT hr = GetItemEnum(pEnum, hItem);
if (FAILED(hr))
return FALSE;
CComHeapPtr<ITEMIDLIST_RELATIVE> pidlTemp;
hr = pEnum->Next(1, &pidlTemp, NULL);
return SUCCEEDED(hr);
}
void CExplorerBand::RefreshRecurse(_In_ HTREEITEM hTarget)
{
NodeInfo* pNodeInfo = GetNodeInfo(hTarget);
CComPtr<IEnumIDList> pEnum;
HRESULT hrEnum = GetItemEnum(pEnum, hTarget);
// Delete zombie items
HTREEITEM hItem, hNextItem;
for (hItem = TreeView_GetChild(m_hWnd, hTarget); hItem; hItem = hNextItem)
{
hNextItem = TreeView_GetNextSibling(m_hWnd, hItem);
if (SUCCEEDED(hrEnum) && !IsTreeItemInEnum(hItem, pEnum))
TreeView_DeleteItem(m_hWnd, hItem);
}
pEnum = NULL;
hrEnum = GetItemEnum(pEnum, hTarget);
// Insert new items and update items
if (SUCCEEDED(hrEnum))
{
CComHeapPtr<ITEMIDLIST_RELATIVE> pidlTemp;
while (pEnum->Next(1, &pidlTemp, NULL) == S_OK)
{
if (!TreeItemHasThisChild(hTarget, pidlTemp))
{
CComHeapPtr<ITEMIDLIST> pidlAbsolute(ILCombine(pNodeInfo->absolutePidl, pidlTemp));
InsertItem(hTarget, pidlAbsolute, pidlTemp, TRUE);
}
pidlTemp.Free();
}
}
// Update children and recurse
for (hItem = TreeView_GetChild(m_hWnd, hTarget); hItem; hItem = hNextItem)
{
hNextItem = TreeView_GetNextSibling(m_hWnd, hItem);
TV_ITEMW item = { TVIF_HANDLE | TVIF_CHILDREN };
item.hItem = hItem;
item.cChildren = ItemHasAnyChild(hItem);
TreeView_SetItem(m_hWnd, &item);
if (TreeView_GetItemState(m_hWnd, hItem, TVIS_EXPANDEDONCE) & TVIS_EXPANDEDONCE)
RefreshRecurse(hItem);
}
}
void CExplorerBand::Refresh()
{
SendMessage(WM_SETREDRAW, FALSE, 0);
RefreshRecurse(m_hRoot);
SendMessage(WM_SETREDRAW, TRUE, 0);
InvalidateRect(NULL, TRUE);
}
#define TIMER_ID_REFRESH 9999
LRESULT CExplorerBand::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
if (wParam != TIMER_ID_REFRESH)
return 0;
KillTimer(TIMER_ID_REFRESH);
// FIXME: Avoid full refresh and optimize for speed
Refresh();
return 0;
}
void
CExplorerBand::OnChangeNotify(
_In_opt_ LPCITEMIDLIST pidl0,
_In_opt_ LPCITEMIDLIST pidl1,
_In_ LONG lEvent)
{
LPITEMIDLIST clean;
HTREEITEM pItem;
/* TODO: handle shell notifications */
switch (lEvent)
{
case SHCNE_MKDIR:
if (!SUCCEEDED(_ReparsePIDL(pidl0, &clean)))
case SHCNE_DRIVEADD:
case SHCNE_MKDIR:
case SHCNE_CREATE:
case SHCNE_DRIVEREMOVED:
case SHCNE_RMDIR:
case SHCNE_DELETE:
case SHCNE_RENAMEFOLDER:
case SHCNE_RENAMEITEM:
case SHCNE_UPDATEDIR:
case SHCNE_UPDATEITEM:
case SHCNE_ASSOCCHANGED:
{
ERR("Can't reparse PIDL to a valid one\n");
KillTimer(TIMER_ID_REFRESH);
SetTimer(TIMER_ID_REFRESH, 500, NULL);
break;
}
NavigateToPIDL(clean, &pItem, FALSE, TRUE, FALSE);
ILFree(clean);
break;
case SHCNE_RMDIR:
DeleteItem(pidl0);
break;
case SHCNE_RENAMEFOLDER:
if (!SUCCEEDED(_ReparsePIDL(pidl1, &clean)))
default:
{
ERR("Can't reparse PIDL to a valid one\n");
TRACE("lEvent: 0x%08lX\n", lEvent);
break;
}
if (NavigateToPIDL(pidl0, &pItem, FALSE, FALSE, FALSE))
RenameItem(pItem, clean);
ILFree(clean);
break;
case SHCNE_UPDATEDIR:
// We don't take care of this message
TRACE("Directory updated\n");
break;
default:
TRACE("Unhandled message\n");
}
}
@ -940,130 +1015,6 @@ BOOL CExplorerBand::NavigateToCurrentFolder()
return result;
}
BOOL CExplorerBand::DeleteItem(LPCITEMIDLIST idl)
{
HTREEITEM toDelete;
TVITEM tvItem;
HTREEITEM parentNode;
if (!NavigateToPIDL(idl, &toDelete, FALSE, FALSE, FALSE))
return FALSE;
// TODO: check that the treeview item is really deleted
parentNode = TreeView_GetParent(m_hWnd, toDelete);
// Navigate to parent when deleting child item
if (!m_pDesktop->CompareIDs(0, idl, m_pidlCurrent))
{
TreeView_SelectItem(m_hWnd, parentNode);
}
// Remove the child item
TreeView_DeleteItem(m_hWnd, toDelete);
// Probe parent to see if it has children
if (!TreeView_GetChild(m_hWnd, parentNode))
{
// Decrement parent's child count
ZeroMemory(&tvItem, sizeof(tvItem));
tvItem.mask = TVIF_CHILDREN;
tvItem.hItem = parentNode;
tvItem.cChildren = 0;
TreeView_SetItem(m_hWnd, &tvItem);
}
return TRUE;
}
BOOL CExplorerBand::RenameItem(HTREEITEM toRename, LPCITEMIDLIST newPidl)
{
WCHAR wszDisplayName[MAX_PATH];
TVITEM itemInfo;
LPCITEMIDLIST relPidl;
NodeInfo *treeInfo;
TVSORTCB sortCallback;
HTREEITEM child;
ZeroMemory(&itemInfo, sizeof(itemInfo));
itemInfo.mask = TVIF_PARAM;
itemInfo.hItem = toRename;
// Change PIDL associated to the item
relPidl = ILFindLastID(newPidl);
TreeView_GetItem(m_hWnd, &itemInfo);
if (!itemInfo.lParam)
{
ERR("Unable to fetch lParam\n");
return FALSE;
}
SendMessage(WM_SETREDRAW, FALSE, 0);
treeInfo = (NodeInfo*)itemInfo.lParam;
ILFree(treeInfo->absolutePidl);
ILFree(treeInfo->relativePidl);
treeInfo->absolutePidl = ILClone(newPidl);
treeInfo->relativePidl = ILClone(relPidl);
// Change the display name
GetDisplayName(newPidl, wszDisplayName, MAX_PATH, SHGDN_INFOLDER);
ZeroMemory(&itemInfo, sizeof(itemInfo));
itemInfo.hItem = toRename;
itemInfo.mask = TVIF_TEXT;
itemInfo.pszText = wszDisplayName;
TreeView_SetItem(m_hWnd, &itemInfo);
if((child = TreeView_GetChild(m_hWnd, toRename)) != NULL)
{
RefreshTreePidl(child, newPidl);
}
// Sorting
sortCallback.hParent = TreeView_GetParent(m_hWnd, toRename);
sortCallback.lpfnCompare = CompareTreeItems;
sortCallback.lParam = (LPARAM)this;
SendMessage(TVM_SORTCHILDRENCB, 0, (LPARAM)&sortCallback);
SendMessage(WM_SETREDRAW, TRUE, 0);
return TRUE;
}
BOOL CExplorerBand::RefreshTreePidl(HTREEITEM tree, LPCITEMIDLIST pidlParent)
{
HTREEITEM tmp;
NodeInfo *pInfo;
// Update our node data
pInfo = GetNodeInfo(tree);
if (!pInfo)
{
WARN("No tree info !\n");
return FALSE;
}
ILFree(pInfo->absolutePidl);
pInfo->absolutePidl = ILCombine(pidlParent, pInfo->relativePidl);
if (!pInfo->absolutePidl)
{
WARN("PIDL allocation failed\n");
return FALSE;
}
// Recursively update children
if ((tmp = TreeView_GetChild(m_hWnd, tree)) != NULL)
{
RefreshTreePidl(tmp, pInfo->absolutePidl);
}
tmp = TreeView_GetNextSibling(m_hWnd, tree);
while(tmp != NULL)
{
pInfo = GetNodeInfo(tmp);
if(!pInfo)
{
WARN("No tree info !\n");
continue;
}
ILFree(pInfo->absolutePidl);
pInfo->absolutePidl = ILCombine(pidlParent, pInfo->relativePidl);
tmp = TreeView_GetNextSibling(m_hWnd, tmp);
}
return TRUE;
}
// *** Tree item sorting callback ***
int CALLBACK CExplorerBand::CompareTreeItems(LPARAM p1, LPARAM p2, LPARAM p3)
{

View file

@ -27,7 +27,6 @@ class CExplorerBand :
public IDropTarget,
public CWindowImpl<CExplorerBand, CWindow, CControlWinTraits>
{
private:
class NodeInfo
{
@ -77,6 +76,7 @@ private:
LRESULT OnShellEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
LRESULT OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
// *** Helper functions ***
NodeInfo* GetNodeInfo(HTREEITEM hItem);
@ -94,9 +94,6 @@ private:
_In_ BOOL bSort);
BOOL InsertSubitems(HTREEITEM hItem, NodeInfo *pNodeInfo);
BOOL NavigateToPIDL(LPCITEMIDLIST dest, HTREEITEM *item, BOOL bExpand, BOOL bInsert, BOOL bSelect);
BOOL DeleteItem(LPCITEMIDLIST toDelete);
BOOL RenameItem(HTREEITEM toRename, LPCITEMIDLIST newPidl);
BOOL RefreshTreePidl(HTREEITEM tree, LPCITEMIDLIST pidlParent);
BOOL NavigateToCurrentFolder();
HRESULT GetCurrentLocation(PIDLIST_ABSOLUTE &pidl);
HRESULT IsCurrentLocation(PCIDLIST_ABSOLUTE pidl);
@ -104,6 +101,12 @@ private:
_In_opt_ LPCITEMIDLIST pidl0,
_In_opt_ LPCITEMIDLIST pidl1,
_In_ LONG lEvent);
void Refresh();
void RefreshRecurse(_In_ HTREEITEM hItem);
BOOL IsTreeItemInEnum(_In_ HTREEITEM hItem, _In_ IEnumIDList *pEnum);
BOOL TreeItemHasThisChild(_In_ HTREEITEM hItem, _In_ PCITEMID_CHILD pidlChild);
HRESULT GetItemEnum(_Out_ CComPtr<IEnumIDList>& pEnum, _In_ HTREEITEM hItem);
BOOL ItemHasAnyChild(_In_ HTREEITEM hItem);
// *** Tree item sorting callback ***
static int CALLBACK CompareTreeItems(LPARAM p1, LPARAM p2, LPARAM p3);
@ -205,6 +208,7 @@ public:
MESSAGE_HANDLER(WM_USER_SHELLEVENT, OnShellEvent)
MESSAGE_HANDLER(WM_RBUTTONDOWN, ContextMenuHack)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
MESSAGE_HANDLER(WM_TIMER, OnTimer)
// MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)
END_MSG_MAP()
};