Feature: add menu-driven compress/extract via custom file+folder picker

This commit is contained in:
yuanyuanxiang
2026-05-30 17:18:45 +02:00
parent 9fe8ab746a
commit c846d11efa
12 changed files with 511 additions and 5 deletions

View File

@@ -0,0 +1,260 @@
#include "stdafx.h"
#include "ZstaPickerDlg.h"
#include "LangManager.h"
#include <shobjidl.h>
#include <atlconv.h>
#include <algorithm>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
namespace {
bool IsDirectoryPath(const std::string& p)
{
DWORD attr = GetFileAttributesA(p.c_str());
return (attr != INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY);
}
// 构造一个无控件的 DLGTEMPLATEcdit=0控件由 OnInitDialog 动态创建。
void BuildDialogTemplate(std::vector<BYTE>& out, LPCWSTR caption,
short cx, short cy)
{
out.clear();
auto append = [&](const void* p, size_t n) {
const BYTE* b = (const BYTE*)p;
out.insert(out.end(), b, b + n);
};
auto appendW = [&](WORD v) {
out.push_back((BYTE)(v & 0xFF));
out.push_back((BYTE)((v >> 8) & 0xFF));
};
auto appendWStr = [&](LPCWSTR s) {
size_t n = wcslen(s);
append(s, (n + 1) * sizeof(WCHAR));
};
DLGTEMPLATE dt = { 0 };
dt.style = DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER |
WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME;
dt.dwExtendedStyle = 0;
dt.cdit = 0;
dt.x = 0; dt.y = 0;
dt.cx = cx; dt.cy = cy;
append(&dt, sizeof(dt));
appendW(0); // no menu
appendW(0); // default dialog class
appendWStr(caption); // caption
appendW(8); // font point size
appendWStr(L"MS Shell Dlg"); // typeface
while (out.size() % 4) out.push_back(0); // DWORD align
}
} // namespace
CZstaPickerDlg::CZstaPickerDlg(CWnd* parent)
: CDialog((LPCTSTR)NULL, parent)
{
}
BEGIN_MESSAGE_MAP(CZstaPickerDlg, CDialog)
ON_BN_CLICKED(IDC_ZSTA_ADDFILES, &CZstaPickerDlg::OnAddFiles)
ON_BN_CLICKED(IDC_ZSTA_ADDFOLDERS, &CZstaPickerDlg::OnAddFolders)
ON_BN_CLICKED(IDC_ZSTA_REMOVE, &CZstaPickerDlg::OnRemove)
ON_WM_SIZE()
END_MESSAGE_MAP()
INT_PTR CZstaPickerDlg::DoModal()
{
CString title = _TR("选择要压缩的文件 / 文件夹");
USES_CONVERSION;
BuildDialogTemplate(m_Template, T2CW(title), 360, 220);
InitModalIndirect((LPCDLGTEMPLATE)m_Template.data());
return CDialog::DoModal();
}
BOOL CZstaPickerDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect cli;
GetClientRect(&cli);
// 占位 rect真正布局在 LayoutControls 里
CRect r0(0, 0, 10, 10);
m_List.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP |
LVS_REPORT | LVS_SHOWSELALWAYS,
r0, this, IDC_ZSTA_LIST);
m_List.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
m_List.InsertColumn(0, _TR("类型"), LVCFMT_LEFT, 60);
m_List.InsertColumn(1, _TR("路径"), LVCFMT_LEFT, 400);
auto mkBtn = [&](CButton& b, LPCTSTR text, int id, DWORD extra = 0) {
b.Create(text,
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | extra,
r0, this, id);
};
mkBtn(m_BtnAddFiles, _TR("添加文件..."), IDC_ZSTA_ADDFILES);
mkBtn(m_BtnAddFolders, _TR("添加文件夹..."), IDC_ZSTA_ADDFOLDERS);
mkBtn(m_BtnRemove, _TR("移除选中"), IDC_ZSTA_REMOVE);
mkBtn(m_BtnOK, _TR("确定"), IDOK, BS_DEFPUSHBUTTON);
mkBtn(m_BtnCancel, _TR("取消"), IDCANCEL);
// 子控件继承对话框的字体 (DS_SETFONT)
HFONT hFont = (HFONT)::SendMessage(GetSafeHwnd(), WM_GETFONT, 0, 0);
if (hFont) {
m_List.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_BtnAddFiles.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_BtnAddFolders.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_BtnRemove.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_BtnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_BtnCancel.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
}
LayoutControls(cli.Width(), cli.Height());
RefreshList();
return TRUE;
}
void CZstaPickerDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
if (m_List.GetSafeHwnd()) LayoutControls(cx, cy);
}
void CZstaPickerDlg::LayoutControls(int cx, int cy)
{
const int margin = 10;
const int btnW = 120;
const int btnH = 26;
const int gap = 6;
int listRight = cx - margin - btnW - margin;
if (listRight < margin + 100) listRight = margin + 100;
m_List.MoveWindow(margin, margin, listRight - margin, cy - margin * 2);
int x = listRight + margin;
int y = margin;
m_BtnAddFiles.MoveWindow(x, y, btnW, btnH); y += btnH + gap;
m_BtnAddFolders.MoveWindow(x, y, btnW, btnH); y += btnH + gap;
m_BtnRemove.MoveWindow(x, y, btnW, btnH);
int bottomY = cy - margin - btnH;
m_BtnCancel.MoveWindow(x, bottomY, btnW, btnH);
m_BtnOK.MoveWindow(x, bottomY - btnH - gap, btnW, btnH);
// 让"路径"列填满剩余宽度
if (m_List.GetSafeHwnd()) {
CRect lr;
m_List.GetClientRect(&lr);
int w0 = m_List.GetColumnWidth(0);
int w1 = lr.Width() - w0 - GetSystemMetrics(SM_CXVSCROLL) - 4;
if (w1 > 100) m_List.SetColumnWidth(1, w1);
}
}
void CZstaPickerDlg::AddPath(const std::string& path)
{
if (path.empty()) return;
if (std::find(m_Paths.begin(), m_Paths.end(), path) == m_Paths.end()) {
m_Paths.push_back(path);
}
}
void CZstaPickerDlg::RefreshList()
{
m_List.DeleteAllItems();
for (size_t i = 0; i < m_Paths.size(); ++i) {
bool isDir = IsDirectoryPath(m_Paths[i]);
m_List.InsertItem((int)i, isDir ? _TR("文件夹") : _TR("文件"));
m_List.SetItemText((int)i, 1, CString(m_Paths[i].c_str()));
}
}
void CZstaPickerDlg::OnAddFiles()
{
const DWORD MAX_BUF = 64 * 1024;
std::vector<TCHAR> buf(MAX_BUF, 0);
CFileDialog dlg(TRUE, NULL, NULL,
OFN_ALLOWMULTISELECT | OFN_EXPLORER |
OFN_HIDEREADONLY | OFN_FILEMUSTEXIST,
_T("All Files (*.*)|*.*||"), this);
dlg.m_ofn.lpstrFile = buf.data();
dlg.m_ofn.nMaxFile = MAX_BUF;
CString title = _TR("选择文件 (可多选)");
dlg.m_ofn.lpstrTitle = title;
if (dlg.DoModal() != IDOK) return;
POSITION pos = dlg.GetStartPosition();
while (pos) {
CString p = dlg.GetNextPathName(pos);
AddPath(std::string(CT2A(p.GetString())));
}
RefreshList();
}
void CZstaPickerDlg::OnAddFolders()
{
IFileOpenDialog* pfd = nullptr;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
if (FAILED(hr) || !pfd) return;
DWORD flags = 0;
pfd->GetOptions(&flags);
pfd->SetOptions(flags | FOS_PICKFOLDERS | FOS_ALLOWMULTISELECT |
FOS_PATHMUSTEXIST | FOS_FORCEFILESYSTEM);
USES_CONVERSION;
CString title = _TR("选择文件夹 (可多选)");
pfd->SetTitle(T2CW(title));
if (SUCCEEDED(pfd->Show(GetSafeHwnd()))) {
IShellItemArray* psia = nullptr;
if (SUCCEEDED(pfd->GetResults(&psia)) && psia) {
DWORD count = 0;
psia->GetCount(&count);
for (DWORD i = 0; i < count; ++i) {
IShellItem* psi = nullptr;
if (SUCCEEDED(psia->GetItemAt(i, &psi)) && psi) {
PWSTR wpath = nullptr;
if (SUCCEEDED(psi->GetDisplayName(SIGDN_FILESYSPATH, &wpath)) && wpath) {
int n = WideCharToMultiByte(CP_ACP, 0, wpath, -1,
NULL, 0, NULL, NULL);
if (n > 1) {
std::string s(n - 1, '\0');
WideCharToMultiByte(CP_ACP, 0, wpath, -1,
&s[0], n, NULL, NULL);
AddPath(s);
}
CoTaskMemFree(wpath);
}
psi->Release();
}
}
psia->Release();
}
}
pfd->Release();
RefreshList();
}
void CZstaPickerDlg::OnRemove()
{
std::vector<int> indices;
POSITION pos = m_List.GetFirstSelectedItemPosition();
while (pos) indices.push_back(m_List.GetNextSelectedItem(pos));
std::sort(indices.begin(), indices.end(), std::greater<int>());
for (int idx : indices) {
if (idx >= 0 && idx < (int)m_Paths.size()) {
m_Paths.erase(m_Paths.begin() + idx);
}
}
RefreshList();
}