だらだら〜自由自在〜

インディーゲーム制作チーム GAME GABURI でプログラム担当してます

Windowsのゴミ箱をつかってファイルやフォルダを削除する

QtでWindows向けのファイラーを作っているのですが、OSのゴミ箱に選択項目を捨てる処理を実装したくて調べました。 Qt自体もQFileSystemModel::removeなど削除機能はもっているのですが、OSのゴミ箱ではないどこか彼方へ(そういえばどこに…?)消し去ってくれるものなようです。

[QTBUG-47703] Move a file or directory to the trash - Qt Bug Tracker

ちょうどQtにもビルトインでゴミ箱送り機能つけてくれというプロポーザルがあるようで、このリンクはそれのIssueチケットらしいのですが。 そのImplementsDetailsに記載があります。

Windowsのゴミ箱に異界送りするための手段としては2つあるようです。

on Windows, SHFileOperation (FO_DELETE with FOF_ALLOWUNDO) is available, but deprecated, and it doesn't return the location of the file in the trash, which makes it a bad choice. In that case, we also have no way to prevent the deletion of the file if it is not moved to the trash. A better alternative uses IFileManager, which via a IFileOperationProgressSink allows us to learn about the location of the file after the trashing, and also allows us to cancel the operation if the file would be deleted (ie because it resides on a network share or otherwise volume that doesn't support recycling). The only drawback is that this requires an up-to-date SDK; building with MinGW has been problematic.

SHFileOperation という選択肢があるが、これは今や推奨されてないのでIFileManagerを使うほうが良いとのことです。 ということで参考サイトで色々調べて最終的に下記のようにしました。

実装コード

#include <ShObjIdl.h>
#include <ShlObj_Core.h>

int PlatformCompat::MoveToTrash(QList<QString> itemPathList)
{
    if (itemPathList.size() == 0)
        return -1;

    HRESULT hr;

    int itemNum = itemPathList.size();
    PCIDLIST_ABSOLUTE_ARRAY pIdlArray = new LPCITEMIDLIST[itemNum];
    PIDLIST_ABSOLUTE abSourcePidl;
    SFGAOF attrs;
    for (int i = 0; i < itemNum; ++i)
    {
        LPCWSTR cstr = reinterpret_cast<LPCWSTR>(itemPathList[i].replace('/', '\\').utf16());
        hr = SHParseDisplayName(cstr, NULL, &abSourcePidl, 0, &attrs);
        if (FAILED(hr))
        {
            goto EXIT;
        }
        pIdlArray[i] = abSourcePidl;
    }


    //
    // Initialize COM as STA.
    //
    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        IFileOperation* pfo;

        //
        // Create the IFileOperation interface 
        //
        hr = CoCreateInstance(CLSID_FileOperation,
            NULL,
            CLSCTX_ALL,
            IID_PPV_ARGS(&pfo));
        if (SUCCEEDED(hr))
        {
            hr = pfo->SetOperationFlags(FOF_ALLOWUNDO);
            if (SUCCEEDED(hr))
            {
                IShellItemArray* pShellItemArr = NULL;
                hr = SHCreateShellItemArrayFromIDLists(itemNum, pIdlArray, &pShellItemArr);
                if (SUCCEEDED(hr))
                {
                    hr = pfo->DeleteItems(pShellItemArr);
                    if (SUCCEEDED(hr))
                    {
                        hr = pfo->PerformOperations();
                    }
                }
                pShellItemArr->Release();
            }
            pfo->Release();
        }

        CoUninitialize();
    }

EXIT:
    if (pIdlArray)
    {
        delete[] pIdlArray;
    }

    return hr;
}

参考サイト