Print a PDF file with MFC - c++

In my app (MFC, C++) I have a button that creates a PDF file in a path.
Now I want to create another button that will print the pdf starting from the path and choosing some option like orientation and number of copies... but I'm not able to do that...
I saw that CPrintDialog shows the default dialog of the printer but I'm not able to attach the PDF file using the path.
I saw also the
ShellExecute(NULL, L"print", L"C:\\Documents\\1.pdf", NULL, NULL, SW_SHOWNORMAL);
that works but in this way I cannot choose any parameter...
How I can use the CPrintDialog to print an existing PDF that is in a path?

You have to use ShellExecuteEx and verb printto to get more control over printing:
SHELLEXECUTEINFO ShellInfo;
ZeroMemory(&ShellInfo, sizeof(SHELLEXECUTEINFO));
ShellInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShellInfo.lpVerb = L"printto";
ShellInfo.lpFile = L"C:\\Documents\\1.pdf";
ShellInfo.lpParameters = szPrinter;
ShellInfo.nShow = SW_SHOWNORMAL;
ShellInfo.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NOCLOSEPROCESS;
if(::ShellExecuteEx(&ShellInfo))
{
if((int)ShellInfo.hInstApp > 32)
{
if(ShellInfo.hProcess != NULL)
{
DWORD dwExitCode = STILL_ACTIVE;
while(dwExitCode == STILL_ACTIVE)
{
if(!::GetExitCodeProcess(ShellInfo.hProcess, &dwExitCode))
{
dwExitCode = 0;
}
}
::CloseHandle(ShellInfo.hProcess);
}
}
}
To get the printer name:
CPrintDialog dlg(TRUE);
if (dlg.DoModal() == IDOK)
{
CString sPrinterName = dlg.GetDeviceName();
}

I have solved with a workaround. Instead of use the ShellExecute, I draw all the thing that I want to print using a CDC object attached to the hDC of a CPrintDialog class. Rember to manage the size of the draw depending on printer DPI like here.
A snippet only to have an idea:
CPrintDialog printDialog(FALSE);
printDialog.GetDefaults();
printDialog.m_pd.Flags &= ~PD_RETURNDEFAULT;
DEVMODE* pDevMode = printDialog.GetDevMode();
pDevMode->dmFields = DM_ORIENTATION | DM_PAPERSIZE | DM_PRINTQUALITY ;
pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
pDevMode->dmPaperSize = DMPAPER_A4;
::GlobalUnlock(printDialog.m_pd.hDevMode);
if (printDialog.DoModal() == IDOK)
{
CDC* pDC = new CDC;
pDC->Attach(printDialog.m_pd.hDC);
pDCPDF->StartDoc(_T(""));
pDCPDF->StartPage();
// ...
//draw what you want
// ...
pDCPDF->EndPage();
pDCPDF->EndDoc(); //this starts the printer
pDCPDF->DeleteDC();
}
Hope to reach soon the reputation of 15 to vote the answer of other questions.
Thanks to Andrew Komiagin answers.

Related

Receiving parent's window handle on IShellFolder/IShellFolder2 EnumObjects implementations while searching?

I'm scratching my head in receiving the parent window handle in a namespace extension project i'm working on.
The use case is as follows:
User browses to a virtual folder through windows explorer
User performs search (search box above)
I need to retrieve the search box's text before the search starts.
I've managed to do that on a test console app with ISearchBoxInfo interface (https://msdn.microsoft.com/en-us/library/windows/desktop/dd562062%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396)
There are 2 ways i can receive a pointer to this interface:
Using IObjectWithSite::SetSite call - which is not relevant as the search is conducted in a different thread, and i cannot share the COM object between those threads
Identifying the window handle and retrieve the ISearchBox through IWebBrowser2 interface.
Both methods don't work as when i perform the search, the EnumObjects is called via a different thread, and i cannot find a way to identify who is te parent explorer window.
When doing search, the hwnd that comes is always null as follows:
This is the EnumObjects code:
// Allows a client to determine the contents of a folder by
// creating an item identifier enumeration object and returning
// its IEnumIDList interface. The methods supported by that
// interface can then be used to enumerate the folder's contents.
HRESULT CFolderViewImplFolder::EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList)
{
HRESULT hr;
_fd = hwnd;
if (hwnd != NULL) // NULL when performing a search
{
const int n = GetWindowTextLength(hwnd);
wstring text(n + 1, L'#');
if (n > 0)
{
GetWindowText(hwnd, &text[0], text.length());
}
}
if (m_nLevel >= g_nMaxLevel)
{
*ppenumIDList = NULL;
hr = S_FALSE; // S_FALSE is allowed with NULL out param to indicate no contents.
}
else
{
CFolderViewImplEnumIDList *penum = new (std::nothrow) CFolderViewImplEnumIDList(grfFlags, m_nLevel + 1, this);
hr = penum ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = penum->Initialize();
if (SUCCEEDED(hr))
{
hr = penum->QueryInterface(IID_PPV_ARGS(ppenumIDList));
}
penum->Release();
}
}
return hr;
}
In addition to my tests, as i have also implementation of IShellFolderViewCB.MessageSFVCB, which runs on the correct thread where i can retrieve the IShellBrowser and thus the handle - when i conduct the search i encounter the following messages:
103, 103, 67, UnmergeMenu, WindowClosing, 106, ViewRelease, ViewRelease
Afterward, no more messages are posted (no matter if i re-search) - the first breakpoint is always at EnumObjects method which i have no context about the parent window.
Any light shedding would be nice.
== EDIT ==
I've came up with some workaround - this is not perfect for all cases but works for most of them - other options will still be nice.
Whenever EnumObjects is called with hwnd = NULL, i'm doing the following: (it's in C# - but it can be easily in C++ also)
static public string PrepareSearch(string currentFolderName, IntPtr hwnd)
{
SHDocVw.ShellWindows shellWindows = new ShellWindows();
SHDocVw.IWebBrowser2 foundBrowser = null;
bool wasFound = false;
string foundTxt = null;
foreach (SHDocVw.IWebBrowser2 eie in shellWindows)
{
// as the search is conducted in another thread, while the main window is "free" and in a search mode, it should be first busy.
string locName = eie.LocationName;
string exeName = eie.FullName;
if (!string.IsNullOrEmpty(exeName) && exeName.IndexOf("explorer.exe", StringComparison.OrdinalIgnoreCase) >= 0 &&
!string.IsNullOrEmpty(locName) &&
eie.Busy && eie.ReadyState == tagREADYSTATE.READYSTATE_LOADING)
{
// in here we're ok, we would also want to get the window title to make sure we're searching correctly.
string title = NSEFolder.WindowText((IntPtr)eie.HWND);
if (!string.IsNullOrEmpty(title) &&
title.IndexOf(currentFolderName) >= 0)
{
// one or more windows were found, ignore the quick search.
if (wasFound)
{
return null;
}
wasFound = true;
foundTxt = locName;
}
}
}
if (wasFound && !string.IsNullOrEmpty(foundTxt))
{
return foundTxt;
}
return null;
}
Basically i'm going over all explorer windows, trying to find one that is indeed "explorer.exe" + not empty search string (LocationName) + busy... + title contains name of the current folder name.
it will fail when 2 windows are busy and have the same folder name in the title - but this might be good enough... Not sure here.
So, after a talk with microsoft - it's not possible to do so.
the workaround i've added is a valid one (thoguh not perfect).

Showing a Windows Explorer Dialog from a C++ application

I have a windows application written in C++.
The application generates certain configuration files in a hidden directory.
I want to give user an option to open that directory from my application.
Clicking that option should open a windows explorer like dialog with an input directory location.
I spend time searching for a similar api, but end up with certain dialogs like "DlgDirListComboBoxW" or "GetOpenFileName" or "GetSaveFileName".
I am looking for an api to open normal Windows explorer like Dialog with an input directory location.
It would be really helpful if the api belongs to CommonDialogs section.
You can use the SHBrowseForFolder
It shows a dialog similar to this:
This is a example for how to use it:
BOOL GetFolder(LPCSTR folderpath,
LPCSTR szCaption,
HWND hOwner /*= NULL*/)
{
BOOL retVal = FALSE;
// The BROWSEINFO struct tells the shell
// how it should display the dialog.
BROWSEINFO bi;
memset(&bi, 0, sizeof(bi));
bi.ulFlags = BIF_USENEWUI;
bi.hwndOwner = hOwner;
bi.lpszTitle = szCaption;
// must call this if using BIF_USENEWUI
::OleInitialize(NULL);
// Show the dialog and get the itemIDList for the
// selected folder.
LPITEMIDLIST pIDL = ::SHBrowseForFolder(&bi);
if(pIDL != NULL)
{
// Create a buffer to store the path, then
// get the path.
char buffer[_MAX_PATH] = {'\0'};
if(::SHGetPathFromIDList(pIDL, buffer) != 0)
{
// Set the string value.
folderpath = buffer;
retVal = TRUE;
}
// free the item id list
CoTaskMemFree(pIDL);
}
::OleUninitialize();
return retVal;
}
How about:
HWND hWndOwner = NULL;
ShellExecute(
hWndOwner,
_T("explore"),
_T("c:\\some\\path"),
NULL,
NULL,
SW_SHOWNORMAL);
You can set hWndOwner to your main window handle if you're so inclined and can choose from a variety of other options.
For more information and usage details, check out the MSDN page on ShellExecute.

open path folder don't click main window mfc

i working with mfc open save path, i want click open save path don't allow click main window, my code open save path :
void CFTPDlg::OnBnClickedButtonSave()
{
BROWSEINFO bi;
ZeroMemory(&bi, sizeof(bi));
TCHAR szDisplayName[MAX_PATH];
szDisplayName[0] = ' ';
bi.hwndOwner = NULL;
bi.pidlRoot = NULL;
bi.pszDisplayName = szDisplayName;
bi.lpszTitle = _T("Please select a folder for storing received files :");
bi.ulFlags = BIF_RETURNONLYFSDIRS;
bi.lParam = NULL;
bi.iImage = 0;
LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
TCHAR szPathName[MAX_PATH];
if (NULL != pidl)
{
BOOL bRet = SHGetPathFromIDList(pidl,szPathName);
if(FALSE == bRet)
return;
//AfxMessageBox(szPathName);
m_editSave = szPathName;
((CEdit*)GetDlgItem(IDC_EDIT_SAVE))->SetWindowText(szPathName);
}
}
thanks a lot
Set the owner window correct to your main window. Otherwise the modal dialog can't disable your window.

Problems using IFileDialog on Windows 7

I'm facing some weird (at least for me) behavior on using the Common Item Dialogs in my MFC Windows application running on Windows 7 or Vista.
According to the MSDN http://msdn.microsoft.com/en-us/library/windows/desktop/bb776913(v=vs.85).aspx I'm using the new interfaces to display file open and save dialogs:
bool OpenFileDialog(CString& strFile, CString strTitle, CStringArray& astrFilter, CStringArray& astrFilterExtension, ULONG nFlags, HWND hParentWnd)
{
USES_CONVERSION;
INT_PTR nResult = 0;
INT_PTR nFilterCount = astrFilter.GetCount();
IFileDialog* pfod = 0;
HRESULT hr = ::CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfod));
if(SUCCEEDED(hr))
{
// New dialog starting with Vista/Windows 7
COMDLG_FILTERSPEC* pOpenTypes = 0;
if((nFilterCount > 0) && (nFilterCount == astrFilterExtension.GetCount()))
{
pOpenTypes = new COMDLG_FILTERSPEC[nFilterCount];
for(int nIdx = 0; nIdx < nFilterCount; nIdx++)
{
pOpenTypes[nIdx].pszName = astrFilter[nIdx].GetBuffer();
pOpenTypes[nIdx].pszSpec = astrFilterExtension[nIdx].GetBuffer();
}
}
// Set the file types to display.
if(pOpenTypes)
{
hr = pfod->SetFileTypes(nFilterCount, pOpenTypes);
if(SUCCEEDED(hr))
hr = pfod->SetFileTypeIndex(0);
}
if(!strFile.IsEmpty())
pfod->SetFileName(strFile);
if(!strTitle.IsEmpty())
pfod->SetTitle(strTitle);
if(SUCCEEDED(hr))
{
// Ensure the dialog only returns file system paths.
DWORD dwFlags;
hr = pfod->GetOptions(&dwFlags);
if(SUCCEEDED(hr))
{
dwFlags |= FOS_FORCEFILESYSTEM;
if(nFlags & OFN_FILEMUSTEXIST)
dwFlags |= FOS_FILEMUSTEXIST;
if(nFlags & OFN_PATHMUSTEXIST)
dwFlags |= FOS_PATHMUSTEXIST;
hr = pfod->SetOptions(dwFlags);
if(SUCCEEDED(hr))
{
// Create an event handling object, and hook it up to the dialog.
IFileDialogEvents* pfde = NULL;
DWORD dwCookie;
// Actually only added for debugging purposes
/*hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if(SUCCEEDED(hr))
{
// Hook up the event handler.
hr = pfod->Advise(pfde, &dwCookie);
if(!SUCCEEDED(hr))
{
pfde->Release();
pfde = 0;
}
}*/
// Now show the dialog. Usually called with hParent == 0
if(hParentWnd)
hr = pfod->Show(::GetWindow(hParentWnd, GW_OWNER));
else
hr = pfod->Show(0);
// do something with the path when the dialog was closed...
So the dialog appears and works fine if I want to select a file from a normal drive. I can navigate through the folders and select any file I want. On leaving the dialog I also get the correct file information.
But it doesn't work for one of the Libraries in the navigation pane on the left side. Whenever I try to select a Library like Documents, Videos or Pictures the dialog doesn't update the right pane which shows the folder/library content.
What I noticed is that on clicking a Library in the file open/save dialog the OnFolderChanging() event of the IFileDialogEvents interface is fired but the OnFolderChange() and OnSelectionChange() are not. Those events are fired if I click and navigate on a "normal" drive like C.
I also tried to call the dialogs early in my InitInstance method to avoid possible side-effects with my other code but this didn't help either.
Is there someone who had the same behavior and was able to resolve this?
Thanks a lot!
So I finally found the answer to this issue. Creating the new MFC project for the application was the actual hint to solve this. The reason was that the "Stack reserve size" was too big. The settings in the old VS6.0 project had the stack size increased to more than 100MB. Apparently the IFileDialog based dialogs do not work properly when the reserved stack size is simply too large (other thing might don't work also as expected). So I had to set it back to 15MB in my case.

CFileDialog :: Browse folders

When I try to instantiate a CFileDialog object it shows both the folders and files. How do you create a CFileDialog that browses for folders alone?
It is very simple, really.
Use CFolderPickerDialog which is derived from the class CFileDialog!
You can't do it with CFileDialog.
Either you will use SHBrowseForFolder Function or a wrapper for it, like CFolderDialog - Selecting Folders.
Starting from Vista it's recommended to use IFileDialog with the FOS_PICKFOLDERS option (see msdn):
CFileDialog od(TRUE/*bOpenFileDialog*/, NULL, NULL,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT , NULL, NULL, 0,
TRUE/*bVistaStyle*/);
IFileOpenDialog * openDlgPtr = od.GetIFileOpenDialog();
if ( openDlgPtr != NULL )
{
openDlgPtr->SetOptions(FOS_PICKFOLDERS);
openDlgPtr->Release();
}
od.DoModal();
Like someone mentioned, use CFolderPickerDialog which works great. I would like to give you example how to use it especially when using the multi select flag:
CFolderPickerDialog folderPickerDialog(initialFolder, OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_ENABLESIZING, this,
sizeof(OPENFILENAME));
CString folderPath;
if (folderPickerDialog.DoModal() == IDOK)
{
POSITION pos = folderPickerDialog.GetStartPosition();
while (pos)
{
folderPath = folderPickerDialog.GetNextPathName(pos);
}
}
starting from windows vista,you can use the Common Item Dialog .
void CQiliRegrvDlg::OnBnClickedSelectDir()
{
HRESULT hr = S_OK;
// Create a new common open file dialog.
IFileOpenDialog *pfd = NULL;
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Set the dialog as a folder picker.
DWORD dwOptions;
hr = pfd->GetOptions(&dwOptions);
if (SUCCEEDED(hr))
{
hr = pfd->SetOptions(dwOptions | FOS_PICKFOLDERS);
}
// Set the title of the dialog.
if (SUCCEEDED(hr))
{
hr = pfd->SetTitle(L"Folder");
}
// Show the open file dialog.
if (SUCCEEDED(hr))
{
hr = pfd->Show(m_hWnd);
if (SUCCEEDED(hr))
{
// Get the selection from the user.
IShellItem *psiResult = NULL;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
PWSTR pszPath = NULL;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
if (SUCCEEDED(hr))
{
m_appDir = pszPath;
SetDlgItemText(IDC_STATIC, m_appDir);
CoTaskMemFree(pszPath);
}
psiResult->Release();
}
}
}
pfd->Release();
}
}
Seems to me the answer you are asking for is inside the code of
CMFCPropertyGridFileProperty::OnClickButton(CPoint /*point*/)
of the
<Your Visual Studio installation folder>\VC\atlmfc\src\mfc\afxpropertygridctrl.cpp
file.
If you do not have access to the code, I will post the essential part of it:
CString strPath = m_varValue.bstrVal;
BOOL bUpdate = FALSE;
if (m_bIsFolder)
{
if (afxShellManager == NULL)
{
CWinAppEx* pApp = DYNAMIC_DOWNCAST(CWinAppEx, AfxGetApp());
if (pApp != NULL)
{
pApp->InitShellManager();
}
}
if (afxShellManager == NULL)
{
ASSERT(FALSE);
}
else
{
bUpdate = afxShellManager->BrowseForFolder(strPath, m_pWndList, strPath);
}
}
else
{
CFileDialog dlg(m_bOpenFileDialog, m_strDefExt, strPath, m_dwFileOpenFlags, m_strFilter, m_pWndList);
if (dlg.DoModal() == IDOK)
{
bUpdate = TRUE;
strPath = dlg.GetPathName();
}
}
As you see, Microsoft itself does not use the Cfiledialog class when wants to open a dialog for picking folders.
For using code like that, your application class MUST be derived from CWinAppEx, not CWinApp
Actually there is a way to do this - I found it in codeguru: "Selected files and folders in CFileDialog"
If you are willing to make your own implementation of CFileDialog such as:
class CMyFileDialog : public CFileDialog
You can add the following code and it should work (It is slightly different from the codeguru example):
// This code overrides the OnNotify message of the CFileDialog
// and catches the CDN_SELCHANGE, this way you can also do
// something with the selected folders.
BOOL CMyFileDialog::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
NMHDR* pNotificationParam = (NMHDR*)lParam;
// Check that we got to the selection change notification.
int code = pNotificationParam->code;
if (code == CDN_SELCHANGE)
{
CStringArray theSelection;
GetListControllSelectedItems(theSelection);
// Do as you want with theSelection.
}
return CFileDialog::OnNotify(wParam, lParam, pResult);
}
// The following Code is accessing the selection in the CFileDialog
// and filling a string array with the selected names
BOOL CMyFileDialog::GetListControllSelectedItems(CStringArray& selectedItemNames)
{
BOOL rc = FALSE;
// Get the list control of the file dialog.
CWnd* pParentWnd = GetParent();
CWnd* pListControlWnd = pParentWnd->GetDlgItem(lst2);
if (pListControlWnd) {
// Get the selection from the list control.
CListCtrl* pListCtrl = (CListCtrl*)(pListControlWnd->GetDlgItem(1));
UINT selectionCount = pListCtrl->GetSelectedCount();
// When there are items selected.
if (selectionCount) {
rc = TRUE;
selectedItemNames.RemoveAll();
POSITION itemPos = pListCtrl->GetFirstSelectedItemPosition();
while (itemPos != NULL)
{
int itemNum = pListCtrl->GetNextSelectedItem(itemPos);
CString currentItemName = pListCtrl->GetItemText(itemNum, 0);
selectedItemNames.Add(currentItemName);
}
}
}
return rc;
}
Note: In CFileDialog::OnFileNameChange of the Microsoft MFC documentation they actually do hint toward this solution, but without elaborating too much.
I had a problem in my very old, legacy code, where I have a customized file dialog that actually needs to save a folder!!!
After twenty two years of hardship and pain, my code is now complete...